Skip to content

Commit 4bdcb8c

Browse files
committed
Merge pull request #149 from CodaFi/readme-and-weep
Spruce up the Readme
2 parents 23cf589 + c5636a2 commit 4bdcb8c

File tree

1 file changed

+242
-116
lines changed

1 file changed

+242
-116
lines changed

README.md

Lines changed: 242 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -5,154 +5,280 @@ Swiftz
55

66
Swiftz 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.
93245
let 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

110255
Operators
111256
---------
112257

258+
Swiftz introduces the following operators at global scope
259+
113260
Operator | 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

Comments
 (0)