@@ -5,154 +5,280 @@ Swiftz
55
66Swiftz is a Swift library for functional programming.
77
8- It defines purely functional data structures and functions.
8+ It defines functional data structures, functions, idioms, and extensions that augment
9+ the Swift standard library.
910
10- Examples
11- --------
11+ For a small, simpler way to introduce functional primitives into any codebase,
12+ see [ Swiftx ] ( https://github.com/typelift/Swiftx ) .
1213
13- ** Data abstractions:**
14+ Setup
15+ -----
16+
17+ To add Swiftz to your application:
18+
19+ ** Using Carthage**
20+
21+ - Add Swiftz to your Cartfile
22+ - Run ` carthage update `
23+ - Drag the relevant copy of Swiftz into your project.
24+ - Expand the Link Binary With Libraries phase
25+ - Click the + and add Swiftz
26+ - Click the + at the top left corner to add a Copy Files build phase
27+ - Set the directory to ` Frameworks `
28+ - Click the + and add Swiftz
29+
30+ ** Using Git Submodules**
31+
32+ - Clone Swiftz as a submodule into the directory of your choice
33+ - Run ` git submodule init -i --recursive `
34+ - Drag ` Swiftz.xcodeproj ` or ` Swiftz-iOS.xcodeproj ` into your project tree as a subproject
35+ - Under your project's Build Phases, expand Target Dependencies
36+ - Click the + and add Swiftz
37+ - Expand the Link Binary With Libraries phase
38+ - Click the + and add Swiftz
39+ - Click the + at the top left corner to add a Copy Files build phase
40+ - Set the directory to ` Frameworks `
41+ - Click the + and add Swiftz
42+
43+ Introduction
44+ ------------
45+
46+ Swiftz draws inspiration from a number of functional libraries
47+ and languages. Chief among them are [ Scalaz] ( https://github.com/scalaz/scalaz ) ,
48+ [ Prelude/Base] ( https://hackage.haskell.org/package/base ) , [ SML
49+ Basis] ( http://sml-family.org/Basis/ ) , and the [ OCaml Standard
50+ Library] ( http://caml.inria.fr/pub/docs/manual-ocaml/stdlib.html ) . Elements of
51+ the library rely on their combinatorial semantics to allow declarative ideas to
52+ be expressed more clearly in Swift.
53+
54+ Swiftz is a proper superset of [ Swiftx] ( https://github.com/typelift/Swiftx ) that
55+ implements higher-level data types like Lenses, Zippers, HLists, and a number of
56+ typeclasses integral to programming with the maximum amount of support from the
57+ type system.
58+
59+ To illustrate use of these abstractions, take these few examples:
60+
61+ ** Lists**
1462
1563``` swift
16- let xs: [Int8 ] = [1 , 2 , 0 , 3 , 4 ]
64+ import struct Swiftz .List
65+
66+ /// Cycles a finite list of numbers into an infinite list.
67+ let finite : List<UInt > = [1 , 2 , 3 , 4 , 5 ]
68+ let infiniteCycle = finite.cycle ()
69+
70+ /// Lists also support the standard map, filter, and reduce operators.
71+ let l : List<Int > = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]
1772
18- // we can use the Min semigroup to find the minimal element in xs
19- sconcat (Min (), 2 , xs) // 0
73+ let twoToEleven = l.map (+ 1 ) // [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
74+ let even = l.filter ((== 0 ) • (% 2 )) // [2, 4, 6, 8, 10]
75+ let sum = l.reduce (curry (+ ), initial : 0 ) // 55
2076
21- // we can use the Sum monoid to find the sum of xs
22- mconcat (Sum< Int8 , NInt8> (i : { return nint8 }), xs) // 10
77+ /// Plus a few more.
78+ let partialSums = l.scanl (curry (+ ), initial : 0 ) // [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
79+ let firstHalf = l.take (5 ) // [1, 2, 3, 4, 5]
80+ let lastHalf = l.drop (5 ) // [6, 7, 8, 9, 10]
2381```
2482
25- ** Either and Result: **
83+ ** JSON **
2684
2785``` swift
28- // Result represents something that could work or be an NSError.
29- // Say we have 2 functions, the first fetches from a web services,
30- // the second decodes the string into a User.
31- // Both *could* fail with an NSError, so we use a Result<A>.
32- func getWeb () -> Result<String > {
33- var e: NSError?
34- let str = doStuff (" foo" , e)
35- return Result (e, str)
86+ import protocol Swiftz .JSONDecode
87+
88+ final class User : JSONDecode {
89+ typealias J = User
90+ let name : String
91+ let age : Int
92+ let tweets : [String ]
93+ let attrs : Dictionary <String , String >
94+
95+ public init (_ n : String , _ a : Int , _ t : [String ], _ r : Dictionary <String , String >) {
96+ name = n
97+ age = a
98+ tweets = t
99+ attrs = r
100+ }
101+
102+ // JSON
103+ public class func create (x : String ) -> Int -> [String ] -> Dictionary <String , String > -> User {
104+ return { (y : Int ) in { (z : [String ]) in { User (x, y, z, $0 ) } } }
105+ }
106+
107+ public class func fromJSON (x : JSONValue) -> User? {
108+ var n: String ?
109+ var a: Int ?
110+ var t: [String ]?
111+ var r: Dictionary <String , String >?
112+ switch x {
113+ case let .JSONObject (d):
114+ n = d[" name" ] >>- JString.fromJSON
115+ a = d[" age" ] >>- JInt.fromJSON
116+ t = d[" tweets" ] >>- JArray< String , JString> .fromJSON
117+ r = d[" attrs" ] >>- JDictionary< String , JString> .fromJSON
118+ // alternatively, if n && a && t... { return User(n!, a!, ...
119+ return (User.create <^> n <*> a <*> t <*> r)
120+ default :
121+ return .None
122+ }
123+ }
124+
125+ public class func luserName () -> Lens<User, User, String , String > {
126+ return Lens { user in IxStore (user.name ) { User ($0 , user.age , user.tweets , user.attrs ) } }
127+ }
36128}
37129
38- func decodeWeb (str : String ) -> Result<User> {
39- var e: NSError?
40- let user = decode (str, e)
41- return Result (e, user)
42- }
130+ public func == (lhs : User, rhs : User) -> Bool {
131+ return lhs.name == rhs.name && lhs.age == rhs.age && lhs.tweets == rhs.tweets && lhs.attrs == rhs.attrs
132+ }
133+ ```
134+
135+ ** Lenses**
136+
137+ ``` swift
138+ import struct Swiftz .Lens
139+ import struct Swiftz .IxStore
140+
141+ /// A party has a host, who is a user.
142+ final class Party {
143+ let host : User
43144
44- // We can compose these two functions with the `>>-` function.
145+ init (h : User) {
146+ host = h
147+ }
45148
46- let getUser: Result<User> = getWeb () >>- decodeWeb
149+ class func lpartyHost () -> Lens<Party, Party, User, User> {
150+ let getter = { (party : Party) -> User in
151+ party.host
152+ }
47153
48- switch (getUser) {
49- case let .Error (e):
50- println (" NSError: \( e ) " )
51- case let .Value (user):
52- println (user.name )
154+ let setter = { (party : Party, host : User) -> Party in
155+ Party (h : host)
156+ }
157+
158+ return Lens (get : getter, set : setter)
159+ }
160+ }
161+
162+ /// A Lens for the User's name.
163+ extension User {
164+ public class func luserName () -> Lens<User, User, String , String > {
165+ return Lens { user in IxStore (user.name ) { User ($0 , user.age , user.tweets , user.attrs ) } }
166+ }
53167}
54168
55- // If we use getUser and getWeb fails, the NSError will be from doStuff.
56- // If decodeWeb fails, then it will be an NSError from decode.
57- // If both steps work, then it will be a User!
169+ /// Let's throw a party now.
170+ let party = Party (h : User (" max" , 1 , [], Dictionary ()))
171+
172+ /// A lens for a party host's name.
173+ let hostnameLens = Party.lpartyHost () • User.luserName ()
174+
175+ /// Retrieve our gracious host's name.
176+ let name = hostnameLens.get (party) // "max"
177+
178+ /// Our party seems to be lacking in proper nouns.
179+ let updatedParty = (Party.lpartyHost () • User.luserName ()).set (party, " Max" )
180+ let properName = hostnameLens.get (updatedParty) // "Max"
181+ ```
182+
183+ ** Semigroups and Monoids**
184+
185+ ``` swift
186+ let xs = [1 , 2 , 0 , 3 , 4 ]
187+
188+ import protocol Swiftz .Semigroup
189+ import func Swiftz .sconcat
190+ import struct Swiftz .Min
191+
192+ /// The least element of a list can be had with the Min Semigroup.
193+ let smallestElement = sconcat (Min (), 2 , xs) // 0
194+
195+ import protocol Swiftz .Monoid
196+ import func Swiftz .mconcat
197+ import struct Swiftz .Sum
198+
199+ /// Or the sum of a list with the Sum Monoid.
200+ let sum = mconcat (Sum< Int8 , NInt8> (i : nint8), xs) // 10
201+
202+ import struct Swiftz .Product
203+
204+ /// Or the product of a list with the Product Monoid.
205+ let product = mconcat (Product< Int8 , NInt8> (i : nint8), xs) // 0
58206```
59207
60- ** JSON: **
208+ ** Arrows **
61209
62210``` swift
63- let js: NSData = (" [1,\" foo\" ]" ).dataUsingEncoding (NSUTF8StringEncoding,
64- allowLossyConversion : false )
65- let lhs: JSValue = JSValue.decode (js)
66- let rhs: JSValue = .JSArray ([.JSNumber (1 ), .JSString (" foo" )])
67- XCTAssert (lhs == rhs)
68- XCTAssert (rhs.encode () == js)
69-
70- // The User class blob/fc9fead44/swiftzTests/swiftzTests.swift#L14-L48
71- // implements JSONDecode, so we can decode JSON into it and get a `User?`
72- let userjs: NSData = (" {\" name\" : \" max\" , \" age\" : 10, \" tweets\" :
73- [\" hello\" ], \" attrs\" : {\" one\" : \" 1\" }}" )
74- .dataUsingEncoding (NSUTF8StringEncoding, allowLossyConversion : false )
75- let user: User? = JSValue.decode (userjs) >>- User.fromJSON
76- XCTAssert (user! == User (" max" , 10 , [" hello" ], [" one" : " 1" ]))
211+ import struct Swiftz .Function
212+ import struct Swiftz .Either
213+
214+ /// An Arrow is a function just like any other. Only this time around we
215+ /// can treat them like a full algebraic structure and introduce a number
216+ /// of operators to augment them.
217+ let comp = Function.arr (+ 3 ) • Function.arr (* 6 ) • Function.arr (/ 2 )
218+ let both = comp.apply (10 ) // 33
219+
220+ /// An Arrow that runs both operations on its input and combines both
221+ /// results into a tuple.
222+ let add5AndMultiply2 = Function.arr (+ 5 ) &&& Function.arr (* 2 )
223+ let both = add5AndMultiply2.apply (10 ) // (15, 20)
224+
225+ /// Produces an Arrow that chooses a particular function to apply
226+ /// when presented with the side of an Either.
227+ let divideLeftMultiplyRight = Function.arr (/ 2 ) ||| Function.arr (* 2 )
228+ let left = divideLeftMultiplyRight.apply (Either.left (4 )) // 2
229+ let right = divideLeftMultiplyRight.apply (Either.right (7 )) // 14
77230```
78231
79- ** Concurrency: **
232+ ** Concurrency**
80233
81234``` swift
82- // we can delay computations with futures
83- let x: Future<Int > = Future (exec : gcdExecutionContext, {
84- sleep (1 )
85- return 4
86- })
87- x.result () == x.result () // true, returns in 1 second
235+ import class Swiftz .Chan
88236
89- // Channels
90- let chan: Chan<Int > = Chan ()
91- chan.write (1 )
92- chan.write (2 ) // this could happen asynchronously
237+ /// A Channel is an unbounded FIFO stream of values with special semantics
238+ /// for reads and writes.
239+ let chan : Chan<Int > = Chan ()
240+
241+ /// All writes to the Channel always succeed. The Channel now contains `1`.
242+ chan.write (1 ) // happens immediately
243+
244+ /// Reads to non-empty Channels occur immediately. The Channel is now empty.
93245let x1 = chan.read ()
94- let x2 = chan.read ()
95- println ((x1, x2)) // 1, 2
96-
97- // we can map and flatMap over futures
98- x.map ({ $0 .description }).result () // "4", returns instantly
99- x.flatMap ({ (x : Int ) -> Future< Int > in
100- return Future (exec : gcdExecutionContext, { sleep (1 ); return x + 1 })
101- }).result () // sleeps another second, then returns 5
102- ```
103246
104- Swiftz Core
105- -----------
247+ /// But if we read from an empty Channel the read blocks until we write to the Channel again.
248+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW, 1 * Double (NSEC_PER_SEC)), dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), {
249+ chan.write (2 ) // Causes the read to suceed and unblocks the reading thread.
250+ })
106251
107- Swiftz Core only contains interfaces to concepts and implementations
108- for data abstractions from the standard library.
252+ let x2 = chan. read () // Blocks until the dispatch block is executed and the Channel becomes non-empty.
253+ ```
109254
110255Operators
111256---------
112257
258+ Swiftz introduces the following operators at global scope
259+
113260Operator | Name | Type
114261-------- | --------- | ------------------------------------------
115- ` pure ` | pure | ` pure<A>(a: A) -> F<A> `
116- ` <^> ` | fmap | ` <^><A, B>(f: A -> B, a: F<A>) -> F<B> `
117- ` <^^> ` | imap | ` <^^><I, J, A>(f: I -> J, f: F<I, A>) -> F<J, A> `
118- ` <!> ` | contramap | ` <^><I, J, A>(f: J -> I, f: F<I, A>) -> F<J, A> `
119- ` <*> ` | apply | ` <*><A, B>(f: F<A -> B>, a: F<A>) -> F<B> `
120- ` >>- ` | bind | ` >>-<A, B>(a: F<A>, f: A -> F<B>) -> F<B> `
121- ` ->> ` | extend | ` ->><A, B>(a: F<A>, f: F<A> -> B) -> F<B> `
122-
123- Types with instances of these operators:
124-
125- - ` Optional `
126- - ` Array ` (non-determinism, cross product)
127- - ` Either ` (right bias)
128- - ` Result `
129- - ` ImArray `
130- - ` Set ` (except ` <*> ` )
131-
132- * Note: these functions are not in any protocol. They are in global scope.*
133-
134- Adding Swiftz to a Project
135- --------------------------
136-
137- 1 . Build the ` .framework `
138- 2 . Copy it to your project
139- 3 . Add a build phase to copy frameworks, and add that swiftz to the list
140- 4 . Add ` --deep ` to "Other Code Signing Flags"
141- 5 . Check ` Versions/A/Frameworks/ ` doesn't contain the Swift runtime (it will
142- be duplicated with the App's copy of the runtime, causing a 4mb increase
143- in file size)
144-
145- Implementation
146- --------------
147-
148- ** Implemented:**
149-
150- - ` Future<A> ` , ` MVar<A> ` and ` Chan<A> ` concurrency abstractions
151- - ` JSON ` types and encode / decode protocols
152- - Lenses
153- - ` Semigroup<A> ` and ` Monoid<A> ` with some instances
154- - ` Num ` protocol
155- - ` Either<L, R> ` and ` Result<V> `
156- - ` maybe ` for ` Optional<A> ` ,
157- - ` Dictionary ` and ` Array ` extensions
158- - Immutable ` Set<A: Hashable> ` and ` ImArray<A> `
262+ ` • ` | compose | ` • <A, B, C>(B -> C, A -> B) -> A -> C `
263+ ` <| ` | apply | ` <| <A, B>(A -> B, A) -> B `
264+ ` |> ` | thrush | ` |> <A, B>(A, A -> B) -> B `
265+ ` <- ` | extract | ` <- <A>(M<A>, A) -> Void `
266+ ` ∪ ` | union | ` ∪ <A>(Set<A>, Set<A>) -> Set<A> `
267+ ` ∩ ` | intersect | ` ∩ <A>(Set<A>, Set<A>) -> Set<A> `
268+ ` <^> ` | fmap | ` <^> <A, B>(A -> B, a: F<A>) -> F<B> `
269+ ` <^^> ` | imap | ` <^^> <I, J, A>(I -> J, F<I, A>) -> F<J, A> `
270+ ` <!> ` | contramap | ` <^> <I, J, A>(J -> I, F<I, A>) -> F<J, A> `
271+ ` <*> ` | apply | ` <*> <A, B>(F<A -> B>, F<A>) -> F<B> `
272+ ` >>- ` | bind | ` >>- <A, B>(F<A>, A -> F<B>) -> F<B> `
273+ ` ->> ` | extend | ` ->> <A, B>(F<A>, F<A> -> B) -> F<B> `
274+
275+ System Requirements
276+ ===================
277+
278+ Swiftz supports OS X 10.9+ and iOS 7.0+.
279+
280+ License
281+ =======
282+
283+ Swiftz is released under the BSD license.
284+
0 commit comments