1
1
mirror of https://github.com/github/semantic.git synced 2024-11-29 21:52:59 +03:00

Merge pull request #56 from github/free-and-fix

Free, Fix, and Patch
This commit is contained in:
Rob Rix 2015-10-05 15:12:04 -04:00
commit ab36a6e36c
7 changed files with 201 additions and 17 deletions

View File

@ -22,6 +22,9 @@
D45A36CD1BBC75DF00BE3DDE /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45A36CC1BBC75DF00BE3DDE /* Info.swift */; settings = {ASSET_TAGS = (); }; };
D49C2CA41BBD72E300949127 /* AnyHashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49C2CA31BBD72E300949127 /* AnyHashable.swift */; settings = {ASSET_TAGS = (); }; };
D49FCBAD1BBD7A3100C5E9C3 /* AnyEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49FCBAC1BBD7A3100C5E9C3 /* AnyEquatable.swift */; settings = {ASSET_TAGS = (); }; };
D49FCBC21BBEF2C600C5E9C3 /* Fix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49FCBC11BBEF2C600C5E9C3 /* Fix.swift */; settings = {ASSET_TAGS = (); }; };
D49FCBC41BBEF98E00C5E9C3 /* Free.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49FCBC31BBEF98E00C5E9C3 /* Free.swift */; settings = {ASSET_TAGS = (); }; };
D49FCBC61BBF214300C5E9C3 /* Patch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49FCBC51BBF214300C5E9C3 /* Patch.swift */; settings = {ASSET_TAGS = (); }; };
D4A71DC51BB45B850051416D /* Vertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A71DC41BB45B850051416D /* Vertex.swift */; settings = {ASSET_TAGS = (); }; };
D4A71DC71BB4AC9E0051416D /* VertexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A71DC61BB4AC9E0051416D /* VertexTests.swift */; settings = {ASSET_TAGS = (); }; };
D4AAE50E1B5AE22E004E581F /* Doubt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4AAE4FD1B5AE22E004E581F /* Doubt.framework */; };
@ -99,6 +102,9 @@
D45A36CC1BBC75DF00BE3DDE /* Info.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = "<group>"; };
D49C2CA31BBD72E300949127 /* AnyHashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyHashable.swift; sourceTree = "<group>"; };
D49FCBAC1BBD7A3100C5E9C3 /* AnyEquatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEquatable.swift; sourceTree = "<group>"; };
D49FCBC11BBEF2C600C5E9C3 /* Fix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fix.swift; sourceTree = "<group>"; };
D49FCBC31BBEF98E00C5E9C3 /* Free.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Free.swift; sourceTree = "<group>"; };
D49FCBC51BBF214300C5E9C3 /* Patch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Patch.swift; sourceTree = "<group>"; };
D4A71DC41BB45B850051416D /* Vertex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vertex.swift; sourceTree = "<group>"; };
D4A71DC61BB4AC9E0051416D /* VertexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VertexTests.swift; sourceTree = "<group>"; };
D4AAE4FD1B5AE22E004E581F /* Doubt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Doubt.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -205,6 +211,9 @@
D45A36CC1BBC75DF00BE3DDE /* Info.swift */,
D49C2CA31BBD72E300949127 /* AnyHashable.swift */,
D49FCBAC1BBD7A3100C5E9C3 /* AnyEquatable.swift */,
D49FCBC11BBEF2C600C5E9C3 /* Fix.swift */,
D49FCBC31BBEF98E00C5E9C3 /* Free.swift */,
D49FCBC51BBF214300C5E9C3 /* Patch.swift */,
D4AAE5001B5AE22E004E581F /* Supporting Files */,
);
path = Doubt;
@ -387,9 +396,12 @@
D4D7F3171BBB22E500AAB0C0 /* Hash.swift in Sources */,
D45A36C91BBC667D00BE3DDE /* Category.swift in Sources */,
D4AAE5401B5AE2D0004E581F /* Array.swift in Sources */,
D49FCBC21BBEF2C600C5E9C3 /* Fix.swift in Sources */,
D49FCBC41BBEF98E00C5E9C3 /* Free.swift in Sources */,
D4A71DC51BB45B850051416D /* Vertex.swift in Sources */,
D4AAE54A1B5AE2D0004E581F /* Syntax.swift in Sources */,
D432D4731BA9C55300F3FABC /* Stream.swift in Sources */,
D49FCBC61BBF214300C5E9C3 /* Patch.swift in Sources */,
D4AAE5421B5AE2D0004E581F /* Doc.swift in Sources */,
D49C2CA41BBD72E300949127 /* AnyHashable.swift in Sources */,
D49FCBAD1BBD7A3100C5E9C3 /* AnyEquatable.swift in Sources */,

42
prototype/Doubt/Fix.swift Normal file
View File

@ -0,0 +1,42 @@
/// The fixpoint of `Syntax`.
///
/// `Syntax` is a non-recursive type parameterized by the type of its child nodes. Instantiating it to `Fix` makes it into a recursive tree by tying the knoteach child node of `Syntax<Fix, A>` is represented by a `Fix` which in turn contains a `Syntax<Fix, A>`. So in the same way that the `fix` function allows one to tie a non-recursive function into a recursive one, `Fix` allows one to tie a non-recursive type into a recursive one. Unfortunately, due to Swifts lack of higher-rank types, this cannot currently be abstracted over the type which is made recursive, and thus it is hard-coded to `Syntax<Fix, A>` rather than provided by a type parameter `F` applied to `Fix<F>`.
public enum Fix<A> {
/// A recursive instantiation of `Syntax`, unrolling another iteration of the recursive type.
indirect case In(Syntax<Fix, A>)
public var out: Syntax<Fix, A> {
switch self {
case let .In(s):
return s
}
}
}
// MARK: - Equality
extension Fix {
public static func equals(param: (A, A) -> Bool)(_ left: Fix, _ right: Fix) -> Bool {
return Syntax.equals(ifLeaf: param, ifRecur: equals(param))(left.out, right.out)
}
}
public func == <A: Equatable> (left: Fix<A>, right: Fix<A>) -> Bool {
return Fix.equals(==)(left, right)
}
// MARK: - Hashing
extension Fix {
public func hash(param: A -> Hash) -> Hash {
return out.hash(ifLeaf: param, ifRecur: { $0.hash(param) })
}
}
extension Fix where A: Hashable {
var hash: Hash {
return hash(Hash.init)
}
}

View File

@ -0,0 +1,73 @@
/// The free monad over `Syntax`.
///
/// This is free in the sense of unconstrained rather than zero-cost; its 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.
public enum Free<A, B> {
/// The injection of a value of type `B` into the `Syntax` tree.
case Pure(B)
/// A recursive instantiation of `Syntax`, unrolling another iteration of the recursive type.
indirect case Roll(Syntax<Free, A>)
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Syntax<Free, A> -> C) -> C {
switch self {
case let .Pure(b):
return ifPure(b)
case let .Roll(s):
return ifRoll(s)
}
}
// MARK: Functor
public func map<C>(@noescape transform: B -> C) -> Free<A, C> {
return analysis(ifPure: { .Pure(transform($0)) }, ifRoll: { .Roll($0.map { $0.map(transform) }) })
}
// MARK: Monad
public func flatMap<C>(@noescape transform: B -> Free<A, C>) -> Free<A, C> {
return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) })
}
}
// 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
}
}
}
public func == <A: Equatable, B: Equatable> (left: Free<A, B>, right: Free<A, B>) -> Bool {
return Free.equals(ifPure: ==, ifRoll: ==)(left, right)
}
// 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) }) })
}
}
extension Free where A: Hashable, B: Hashable {
var hash: Hash {
return hash(ifPure: Hash.init, ifRoll: Hash.init)
}
}

View File

@ -48,6 +48,11 @@ infix operator </> {
precedence 140
}
infix operator >>> {
associativity right
precedence 170
}
prefix operator ^ {}
postfix operator * {}

View File

@ -0,0 +1,41 @@
/// A patch to some part of a `Syntax` tree.
public enum Patch<A> {
case Replace(Fix<A>?, Fix<A>?)
public static func Insert(term: Fix<A>) -> Patch {
return .Replace(nil, term)
}
public static func Delete(term: Fix<A>) -> Patch {
return .Replace(term, nil)
}
public var state: (before: Fix<A>?, after: Fix<A>?) {
switch self {
case let .Replace(a, b):
return (a, b)
}
}
}
// MARK: - Equality
extension Patch {
public static func equals(param: (A, A) -> Bool)(_ left: Patch, _ right: Patch) -> Bool {
return Optional.equals(Fix.equals(param))(left.state.before, right.state.before)
&& Optional.equals(Fix.equals(param))(left.state.after, right.state.after)
}
}
// MARK: - Hashing
extension Patch {
public func hash(param: A -> Hash) -> Hash {
return Hash.Ordered([
state.before.map { $0.hash(param) } ?? Hash.Empty,
state.after.map { $0.hash(param) } ?? Hash.Empty
])
}
}

View File

@ -8,12 +8,20 @@ func const<A, B>(a: A)(_ b: B) -> A {
}
infix operator >>> {
associativity right
precedence 170
}
func >>> <T, U, V> (f: T -> U, g: U -> V) -> T -> V {
return { g(f($0)) }
}
extension Optional {
static func equals(param: (Wrapped, Wrapped) -> Bool)(_ left: Wrapped?, _ right: Wrapped?) -> Bool {
switch (left, right) {
case let (.Some(a), .Some(b)):
return param(a, b)
case (.None, .None):
return true
default:
return false
}
}
}

View File

@ -25,7 +25,7 @@ public enum Term<A: Equatable>: CustomDebugStringConvertible, CustomDocConvertib
}
public func == <A: Equatable> (left: Term<A>, right: Term<A>) -> Bool {
return Syntax.equals(==)(left.syntax, right.syntax)
return Syntax.equals(ifLeaf: ==, ifRecur: ==)(left.syntax, right.syntax)
}
@ -72,13 +72,16 @@ public enum Syntax<Recur, A>: CustomDebugStringConvertible, CustomDocConvertible
}
}
extension Syntax where A: Equatable {
public static func equals(recur: (Recur, Recur) -> Bool)(_ left: Syntax<Recur, A>, _ right: Syntax<Recur, A>) -> Bool {
// MARK: - Equality
extension Syntax {
public static func equals(ifLeaf ifLeaf: (A, A) -> Bool, ifRecur: (Recur, Recur) -> Bool)(_ left: Syntax<Recur, A>, _ right: Syntax<Recur, A>) -> Bool {
switch (left, right) {
case let (.Leaf(l1), .Leaf(l2)):
return l1 == l2
return ifLeaf(l1, l2)
case let (.Branch(v1), .Branch(v2)):
return recur(v1, v2)
return ifRecur(v1, v2)
default:
return false
}
@ -86,29 +89,29 @@ extension Syntax where A: Equatable {
}
public func == <F: Equatable, A: Equatable> (left: Syntax<F, A>, right: Syntax<F, A>) -> Bool {
return Syntax.equals(==)(left, right)
return Syntax.equals(ifLeaf: ==, ifRecur: ==)(left, right)
}
extension Term where A: Hashable {
public var hash: Hash {
return syntax.hash { $0.hash }
return syntax.hash(ifLeaf: Hash.init, ifRecur: { $0.hash })
}
}
extension Syntax where A: Hashable {
public func hash(recur: Recur -> Hash) -> Hash {
extension Syntax {
public func hash(ifLeaf ifLeaf: A -> Hash, ifRecur: Recur -> Hash) -> Hash {
switch self {
case let .Leaf(n):
return Hash("Leaf", Hash(n))
return Hash("Leaf", ifLeaf(n))
case let .Branch(x):
return Hash("Branch", recur(x))
return Hash("Branch", ifRecur(x))
}
}
}
extension Syntax where Recur: Hashable, A: Hashable {
public var hash: Hash {
return hash(Hash.init)
return hash(ifLeaf: Hash.init, ifRecur: Hash.init)
}
}