Swiftz is a Swift library for functional programming.
It defines functional data structures, functions, idioms, and extensions that augment the Swift standard library.
For a small, simpler way to introduce functional primitives into any codebase, see Swiftx.
To add Swiftz to your application:
Using Carthage
- Add Swiftz to your Cartfile
- Run
carthage update
- Drag the relevant copy of Swiftz into your project.
- Expand the Link Binary With Libraries phase
- Click the + and add Swiftz
- Click the + at the top left corner to add a Copy Files build phase
- Set the directory to
Frameworks
- Click the + and add Swiftz
Using Git Submodules
- Clone Swiftz as a submodule into the directory of your choice
- Run
git submodule init -i --recursive
- Drag
Swiftz.xcodeproj
orSwiftz-iOS.xcodeproj
into your project tree as a subproject - Under your project's Build Phases, expand Target Dependencies
- Click the + and add Swiftz
- Expand the Link Binary With Libraries phase
- Click the + and add Swiftz
- Click the + at the top left corner to add a Copy Files build phase
- Set the directory to
Frameworks
- Click the + and add Swiftz
Swiftz draws inspiration from a number of functional libraries and languages. Chief among them are Scalaz, Prelude/Base, SML Basis, and the OCaml Standard Library. Elements of the library rely on their combinatorial semantics to allow declarative ideas to be expressed more clearly in Swift.
Swiftz is a proper superset of Swiftx that implements higher-level data types like Lenses, Zippers, HLists, and a number of typeclasses integral to programming with the maximum amount of support from the type system.
To illustrate use of these abstractions, take these few examples:
Lists
import struct Swiftz.List
/// Cycles a finite list of numbers into an infinite list.
let finite : List<UInt> = [1, 2, 3, 4, 5]
let infiniteCycle = finite.cycle()
/// Lists also support the standard map, filter, and reduce operators.
let l : List<Int> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let twoToEleven = l.map(+1) // [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
let even = l.filter((==0) • (%2)) // [2, 4, 6, 8, 10]
let sum = l.reduce(curry(+), initial: 0) // 55
/// Plus a few more.
let partialSums = l.scanl(curry(+), initial: 0) // [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
let firstHalf = l.take(5) // [1, 2, 3, 4, 5]
let lastHalf = l.drop(5) // [6, 7, 8, 9, 10]
JSON
import protocol Swiftz.JSONDecode
import struct Swiftz.JSONKeypath
public class User : JSONDecodable {
typealias J = User
let name : String
let age : Int
let tweets : [String]
let attr : String
public init(_ n : String, _ a : Int, _ t : [String], _ r : String) {
name = n
age = a
tweets = t
attr = r
}
// JSON
public class func create(x : String) -> Int -> ([String] -> String -> User) {
return { y in { z in { User(x, y, z, $0) } } }
}
public class func fromJSON(x : JSONValue) -> User? {
return User.create
<^> x <? "name"
<*> x <? "age"
<*> x <? "tweets"
<*> x <? "attrs" <> "one" // A nested keypath
}
// lens example
public class func luserName() -> Lens<User, User, String, String> {
return Lens { user in IxStore(user.name) { User($0, user.age, user.tweets, user.attr) } }
}
}
public func ==(lhs : User, rhs : User) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age && lhs.tweets == rhs.tweets && lhs.attr == rhs.attr
}
let userjs = "{\"name\": \"max\", \"age\": 10, \"tweets\": [\"hello\"], \"attrs\": {\"one\": \"1\"}}"
/// The JSON we've decoded works perfectly with the User structure we defined above. In case it didn't,
/// the user would be nil.
let user : User? = JSONValue.decode(userjs) >>- User.fromJSON // .Some( User("max", 10, ["hello"], "1") )
Lenses
import struct Swiftz.Lens
import struct Swiftz.IxStore
/// A party has a host, who is a user.
final class Party {
let host : User
init(h : User) {
host = h
}
class func lpartyHost() -> Lens<Party, Party, User, User> {
let getter = { (party : Party) -> User in
party.host
}
let setter = { (party : Party, host : User) -> Party in
Party(h: host)
}
return Lens(get: getter, set: setter)
}
}
/// A Lens for the User's name.
extension User {
public class func luserName() -> Lens<User, User, String, String> {
return Lens { user in IxStore(user.name) { User($0, user.age, user.tweets, user.attrs) } }
}
}
/// Let's throw a party now.
let party = Party(h: User("max", 1, [], Dictionary()))
/// A lens for a party host's name.
let hostnameLens = Party.lpartyHost() • User.luserName()
/// Retrieve our gracious host's name.
let name = hostnameLens.get(party) // "max"
/// Our party seems to be lacking in proper nouns.
let updatedParty = (Party.lpartyHost() • User.luserName()).set(party, "Max")
let properName = hostnameLens.get(updatedParty) // "Max"
Semigroups and Monoids
let xs = [1, 2, 0, 3, 4]
import protocol Swiftz.Semigroup
import func Swiftz.sconcat
import struct Swiftz.Min
/// The least element of a list can be had with the Min Semigroup.
let smallestElement = sconcat(Min(2), xs.map { Min($0) }).value() // 0
import protocol Swiftz.Monoid
import func Swiftz.mconcat
import struct Swiftz.Sum
/// Or the sum of a list with the Sum Monoid.
let sum = mconcat(xs.map { Sum($0) }).value() // 10
import struct Swiftz.Product
/// Or the product of a list with the Product Monoid.
let product = mconcat(xs.map { Product($0) }).value() // 0
Arrows
import struct Swiftz.Function
import struct Swiftz.Either
/// An Arrow is a function just like any other. Only this time around we
/// can treat them like a full algebraic structure and introduce a number
/// of operators to augment them.
let comp = Function.arr(+3) • Function.arr(*6) • Function.arr(/2)
let both = comp.apply(10) // 33
/// An Arrow that runs both operations on its input and combines both
/// results into a tuple.
let add5AndMultiply2 = Function.arr(+5) &&& Function.arr(*2)
let both = add5AndMultiply2.apply(10) // (15, 20)
/// Produces an Arrow that chooses a particular function to apply
/// when presented with the side of an Either.
let divideLeftMultiplyRight = Function.arr(/2) ||| Function.arr(*2)
let left = divideLeftMultiplyRight.apply(Either.left(4)) // 2
let right = divideLeftMultiplyRight.apply(Either.right(7)) // 14
Concurrency
import class Swiftz.Chan
/// A Channel is an unbounded FIFO stream of values with special semantics
/// for reads and writes.
let chan : Chan<Int> = Chan()
/// All writes to the Channel always succeed. The Channel now contains `1`.
chan.write(1) // happens immediately
/// Reads to non-empty Channels occur immediately. The Channel is now empty.
let x1 = chan.read()
/// But if we read from an empty Channel the read blocks until we write to the Channel again.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * Double(NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
chan.write(2) // Causes the read to suceed and unblocks the reading thread.
})
let x2 = chan.read() // Blocks until the dispatch block is executed and the Channel becomes non-empty.
Swiftz introduces the following operators at global scope
Operator | Name | Type |
---|---|---|
• |
compose | • <A, B, C>(B -> C, A -> B) -> A -> C |
`< | ` | apply |
` | >` | thrush |
<- |
extract | <- <A>(M<A>, A) -> Void |
∪ |
union | ∪ <A>(Set<A>, Set<A>) -> Set<A> |
∩ |
intersect | ∩ <A>(Set<A>, Set<A>) -> Set<A> |
!! |
from | !! <A, ..., F>(NSErrorPointer, A, ..., F) -> Result<F> |
<> |
op | <> <A : Monoid>(A, A) -> A |
<? |
retrieve | <? <A : JSONDecodable>(JSONValue, JSONKeypath) -> A? |
<! |
force retrieve | <! <A : JSONDecodable>(JSONValue, JSONKeypath) -> A |
<^> |
fmap | <^> <A, B>(A -> B, a: F<A>) -> F<B> |
<^^> |
imap | <^^> <I, J, A>(I -> J, F<I, A>) -> F<J, A> |
<!> |
contramap | <^> <I, J, A>(J -> I, F<I, A>) -> F<J, A> |
<*> |
apply | <*> <A, B>(F<A -> B>, F<A>) -> F<B> |
>>- |
bind | >>- <A, B>(F<A>, A -> F<B>) -> F<B> |
->> |
extend | ->> <A, B>(F<A>, F<A> -> B) -> F<B> |
<<< |
r-t-l compose | >>> <C, A, B, C>(C<A, B>, C<B, C>) -> C<A, C> |
>>> |
l-t-r compose | <<< <C, A, B, C>(C<B, C>, C<A, B>) -> C<A, C> |
&&& |
split | &&& <A, B, C, D>(A<B, C>, A<B, D>) -> A<B, (C, D)> |
*** |
fanout | *** <A, B, C, D, E>(A<B, C>, A<D, E>) -> A<(B, D), (C, E)> |
+++ |
splat | +++ <A, B, C, D, E>(A<B, C>, A<D, E>) -> A<Either<D, B>, Either<C, E>> |
` | ||
<+> |
op | <+> <A, B, C>(A<B, C>, A<B, C>) -> A<B, C> |
Swiftz supports OS X 10.9+ and iOS 7.0+.
Swiftz is released under the BSD license.