2015-10-02 20:54:39 +03:00
|
|
|
|
/// The free monad over `Syntax`.
|
|
|
|
|
///
|
|
|
|
|
/// This is “free” in the sense of “unconstrained” rather than “zero-cost”; it’s the monad obtained by taking a functor (in this case `Syntax`) and adding the minimum necessary details (the `Pure` case) to satisfy the monad laws.
|
|
|
|
|
///
|
|
|
|
|
/// `Syntax` is a non-recursive type parameterized by the type of its child nodes. Instantiating it to `Free` makes it recursive through the `Roll` case, and allows it to wrap values of type `B` through the `Pure` case.
|
|
|
|
|
///
|
|
|
|
|
/// In Doubt, this allows us to represent diffs as values of the `Free` monad obtained from `Syntax`, injecting `Patch` into the tree; or otherwise put, a diff is a tree of mutually-recursive `Free.Roll`/`Syntax` nodes with `Pure` nodes injecting the actual changes.
|
2015-10-02 20:43:43 +03:00
|
|
|
|
public enum Free<A, B> {
|
2015-10-02 21:07:29 +03:00
|
|
|
|
/// The injection of a value of type `B` into the `Syntax` tree.
|
2015-10-02 20:43:43 +03:00
|
|
|
|
case Pure(B)
|
2015-10-02 21:07:29 +03:00
|
|
|
|
|
2015-10-02 21:08:03 +03:00
|
|
|
|
/// A recursive instantiation of `Syntax`, unrolling another iteration of the recursive type.
|
2015-10-02 20:43:43 +03:00
|
|
|
|
indirect case Roll(Syntax<Free, A>)
|
2015-10-02 20:59:55 +03:00
|
|
|
|
|
2015-10-02 21:29:45 +03:00
|
|
|
|
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Syntax<Free, A> -> C) -> C {
|
2015-10-02 21:26:53 +03:00
|
|
|
|
switch self {
|
|
|
|
|
case let .Pure(b):
|
|
|
|
|
return ifPure(b)
|
|
|
|
|
case let .Roll(s):
|
|
|
|
|
return ifRoll(s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-02 21:27:37 +03:00
|
|
|
|
|
|
|
|
|
// MARK: Functor
|
|
|
|
|
|
2015-10-02 20:59:55 +03:00
|
|
|
|
public func map<C>(@noescape transform: B -> C) -> Free<A, C> {
|
2015-10-02 21:35:18 +03:00
|
|
|
|
return analysis(ifPure: { .Pure(transform($0)) }, ifRoll: { .Roll($0.map { $0.map(transform) }) })
|
2015-10-02 20:59:55 +03:00
|
|
|
|
}
|
2015-10-02 21:03:34 +03:00
|
|
|
|
|
2015-10-02 21:27:37 +03:00
|
|
|
|
|
|
|
|
|
// MARK: Monad
|
|
|
|
|
|
2015-10-02 21:03:34 +03:00
|
|
|
|
public func flatMap<C>(@noescape transform: B -> Free<A, C>) -> Free<A, C> {
|
2015-10-02 21:36:01 +03:00
|
|
|
|
return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) })
|
2015-10-02 21:03:34 +03:00
|
|
|
|
}
|
2015-10-02 20:43:43 +03:00
|
|
|
|
}
|
2015-10-02 21:15:17 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Equality
|
|
|
|
|
|
|
|
|
|
extension Free {
|
|
|
|
|
public static func equals(ifPure ifPure: (B, B) -> Bool, ifRoll: (A, A) -> Bool)(_ left: Free, _ right: Free) -> Bool {
|
|
|
|
|
switch (left, right) {
|
|
|
|
|
case let (.Pure(a), .Pure(b)):
|
|
|
|
|
return ifPure(a, b)
|
|
|
|
|
case let (.Roll(a), .Roll(b)):
|
|
|
|
|
return Syntax.equals(ifLeaf: ifRoll, ifRecur: equals(ifPure: ifPure, ifRoll: ifRoll))(a, b)
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-02 21:18:43 +03:00
|
|
|
|
|
|
|
|
|
public func == <A: Equatable, B: Equatable> (left: Free<A, B>, right: Free<A, B>) -> Bool {
|
|
|
|
|
return Free.equals(ifPure: ==, ifRoll: ==)(left, right)
|
|
|
|
|
}
|
2015-10-02 21:35:07 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Hashing
|
|
|
|
|
|
|
|
|
|
extension Free {
|
|
|
|
|
public func hash(ifPure ifPure: B -> Hash, ifRoll: A -> Hash) -> Hash {
|
|
|
|
|
return analysis(ifPure: ifPure, ifRoll: { $0.hash(ifLeaf: ifRoll, ifRecur: { $0.hash(ifPure: ifPure, ifRoll: ifRoll) }) })
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-02 23:27:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extension Free where A: Hashable, B: Hashable {
|
|
|
|
|
var hash: Hash {
|
|
|
|
|
return hash(ifPure: Hash.init, ifRoll: Hash.init)
|
|
|
|
|
}
|
|
|
|
|
}
|