From 2aa104ab8cb5adad3128075719492098cc071bc9 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:10:56 -0400 Subject: [PATCH 01/55] Rename the A type parameter to Leaf. --- prototype/Doubt/Free.swift | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index ab3b90e10..b3d2476a8 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -5,21 +5,21 @@ /// `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: CustomDebugStringConvertible, SyntaxConvertible { +public enum Free: CustomDebugStringConvertible, SyntaxConvertible { /// 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) + indirect case Roll(Syntax) /// Recursively copies a `Term: TermType where Term.LeafType == A` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. - public init(_ term: Term) { + public init(_ term: Term) { self = .Roll(term.unwrap.map(Free.init)) } - public func analysis(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Syntax -> C) -> C { + public func analysis(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Syntax -> C) -> C { switch self { case let .Pure(b): return ifPure(b) @@ -54,7 +54,7 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { /// While not every function on a given `Free` can be computed using `iterate`, these guarantees of termination and complexity, as well as the brevity and focus on the operation being performed n times, make it a desirable scaffolding for any function which can. /// /// For a lucid, in-depth tutorial on recursion schemes, I recommend [Patrick Thomson](https://twitter.com/importantshock)’s _[An Introduction to Recursion Schemes](http://patrickthomson.ghost.io/an-introduction-to-recursion-schemes/)_ and _[Recursion Schemes, Part 2: A Mob of Morphisms](http://patrickthomson.ghost.io/recursion-schemes-part-2/)_. - public func iterate(transform: Syntax -> B) -> B { + public func iterate(transform: Syntax -> B) -> B { return analysis( ifPure: id, ifRoll: { transform($0.map { $0.iterate(transform) }) }) @@ -83,14 +83,14 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { // MARK: Functor - public func map(@noescape transform: B -> C) -> Free { + public func map(@noescape transform: B -> C) -> Free { return analysis(ifPure: { .Pure(transform($0)) }, ifRoll: { .Roll($0.map { $0.map(transform) }) }) } // MARK: Monad - public func flatMap(@noescape transform: B -> Free) -> Free { + public func flatMap(@noescape transform: B -> Free) -> Free { return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) }) } @@ -109,13 +109,13 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { // MARK: SyntaxConvertible - public init(syntax: Syntax) { + public init(syntax: Syntax) { self = .Roll(syntax) } } -extension Free where B: PatchType, B.Element == Cofree { +extension Free where B: PatchType, B.Element == Cofree { public typealias Term = B.Element public func merge(transform: B -> Term) -> Term { @@ -126,7 +126,7 @@ extension Free where B: PatchType, B.Element == Cofree { return map(transform).iterate(Free.discardNullTerms) } - private static func discardNullTerms(syntax: Syntax) -> Term? { + private static func discardNullTerms(syntax: Syntax) -> Term? { switch syntax { case let .Leaf(a): return Cofree((), .Leaf(a)) @@ -172,7 +172,7 @@ extension Free where B: PatchType { // MARK: - Equality extension Free { - public static func equals(ifPure ifPure: (B, B) -> Bool, ifRoll: (A, A) -> Bool)(_ left: Free, _ right: Free) -> Bool { + public static func equals(ifPure ifPure: (B, B) -> Bool, ifRoll: (Leaf, Leaf) -> Bool)(_ left: Free, _ right: Free) -> Bool { switch (left, right) { case let (.Pure(a), .Pure(b)): return ifPure(a, b) @@ -196,7 +196,7 @@ public func == (left: Free Doubt.JSON, ifLeaf: A -> Doubt.JSON) -> Doubt.JSON { + public func JSON(ifPure ifPure: B -> Doubt.JSON, ifLeaf: Leaf -> Doubt.JSON) -> Doubt.JSON { return analysis( ifPure: ifPure, ifRoll: { @@ -205,7 +205,7 @@ extension Free { } } -extension Free where A: CustomJSONConvertible { +extension Free where Leaf: CustomJSONConvertible { public func JSON(ifPure: B -> Doubt.JSON) -> Doubt.JSON { return JSON(ifPure: ifPure, ifLeaf: { $0.JSON }) } From 7eed54dcdc88d7b7b94337f45080d92cb52e3ff9 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:12:52 -0400 Subject: [PATCH 02/55] Rename the B type parameter to Value. --- prototype/Doubt/Free.swift | 56 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index b3d2476a8..dd7d4aef9 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -2,24 +2,24 @@ /// /// 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. +/// `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 `Value` 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: CustomDebugStringConvertible, SyntaxConvertible { - /// The injection of a value of type `B` into the `Syntax` tree. - case Pure(B) +public enum Free: CustomDebugStringConvertible, SyntaxConvertible { + /// The injection of a value of type `Value` into the `Syntax` tree. + case Pure(Value) /// A recursive instantiation of `Syntax`, unrolling another iteration of the recursive type. indirect case Roll(Syntax) - /// Recursively copies a `Term: TermType where Term.LeafType == A` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. + /// Recursively copies a `Term: TermType where Term.LeafType == Leaf` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. public init(_ term: Term) { self = .Roll(term.unwrap.map(Free.init)) } - public func analysis(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Syntax -> C) -> C { + public func analysis(@noescape ifPure ifPure: Value -> C, @noescape ifRoll: Syntax -> C) -> C { switch self { case let .Pure(b): return ifPure(b) @@ -34,11 +34,11 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { /// /// This forms a _catamorphism_ (from the Greek “cata”, “downwards”; compare “catastrophe”), a generalization of folds over regular trees (and datatypes isomorphic to them). It operates at the leaves first, and then branches near the periphery, recursively collapsing values by whatever is computed by `transform`. Catamorphisms are themselves an example of _recursion schemes_, which characterize specific well-behaved patterns of recursion. This gives `iterate` some useful properties for computations performed over trees. /// - /// Due to the character of recursion captured by catamorphisms, `iterate` ensures that computation will not only halt, but will further be linear in the size of the receiver. (Nesting a call to `iterate` will therefore result in O(n²) complexity.) This guarantee is achieved by careful composition of calls to `map` with recursive calls to `iterate`, only calling `transform` once the recursive call has completed. `transform` is itself non-recursive, receiving a `Syntax` whose recurrences have already been flattened to `B`. + /// Due to the character of recursion captured by catamorphisms, `iterate` ensures that computation will not only halt, but will further be linear in the size of the receiver. (Nesting a call to `iterate` will therefore result in O(n²) complexity.) This guarantee is achieved by careful composition of calls to `map` with recursive calls to `iterate`, only calling `transform` once the recursive call has completed. `transform` is itself non-recursive, receiving a `Syntax` whose recurrences have already been flattened to `Value`. /// /// The linearity of `iterate` in the size of the receiver makes it trivial to compute said size, by counting leaves as 1 and summing branches’ children: /// - /// func size(free: Free) -> Int { + /// func size(free: Free) -> Int { /// return free.iterate { flattenedSyntax in /// switch flattenedSyntax { /// case .Leaf: @@ -54,7 +54,7 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { /// While not every function on a given `Free` can be computed using `iterate`, these guarantees of termination and complexity, as well as the brevity and focus on the operation being performed n times, make it a desirable scaffolding for any function which can. /// /// For a lucid, in-depth tutorial on recursion schemes, I recommend [Patrick Thomson](https://twitter.com/importantshock)’s _[An Introduction to Recursion Schemes](http://patrickthomson.ghost.io/an-introduction-to-recursion-schemes/)_ and _[Recursion Schemes, Part 2: A Mob of Morphisms](http://patrickthomson.ghost.io/recursion-schemes-part-2/)_. - public func iterate(transform: Syntax -> B) -> B { + public func iterate(transform: Syntax -> Value) -> Value { return analysis( ifPure: id, ifRoll: { transform($0.map { $0.iterate(transform) }) }) @@ -62,7 +62,7 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { /// Reduces the receiver top-down, left-to-right, starting from an `initial` value, and applying `combine` to successive values. - public func reduce(initial: B, combine: (B, B) -> B) -> B { + public func reduce(initial: Value, combine: (Value, Value) -> Value) -> Value { return iterate { switch $0 { case .Leaf: @@ -76,21 +76,21 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { } /// Returns a function which sums `Free`s by first `transform`ing `Pure` values into integers, and then summing these. - public static func sum(transform: B -> Int)(_ free: Free) -> Int { + public static func sum(transform: Value -> Int)(_ free: Free) -> Int { return free.map(transform).reduce(0, combine: +) } // MARK: Functor - public func map(@noescape transform: B -> C) -> Free { + public func map(@noescape transform: Value -> C) -> Free { return analysis(ifPure: { .Pure(transform($0)) }, ifRoll: { .Roll($0.map { $0.map(transform) }) }) } // MARK: Monad - public func flatMap(@noescape transform: B -> Free) -> Free { + public func flatMap(@noescape transform: Value -> Free) -> Free { return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) }) } @@ -115,14 +115,14 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { } -extension Free where B: PatchType, B.Element == Cofree { - public typealias Term = B.Element +extension Free where Value: PatchType, Value.Element == Cofree { + public typealias Term = Value.Element - public func merge(transform: B -> Term) -> Term { + public func merge(transform: Value -> Term) -> Term { return map(transform).iterate { Cofree((), $0) } } - public func merge(transform: B -> Term?) -> Term? { + public func merge(transform: Value -> Term?) -> Term? { return map(transform).iterate(Free.discardNullTerms) } @@ -149,17 +149,17 @@ extension Free where B: PatchType, B.Element == Cofree { // MARK: - Patch construction -extension Free where B: PatchType { - public static func Replace(before: B.Element, _ after: B.Element) -> Free { - return .Pure(B(replacing: before, with: after)) +extension Free where Value: PatchType { + public static func Replace(before: Value.Element, _ after: Value.Element) -> Free { + return .Pure(Value(replacing: before, with: after)) } - public static func Insert(after: B.Element) -> Free { - return .Pure(B(inserting: after)) + public static func Insert(after: Value.Element) -> Free { + return .Pure(Value(inserting: after)) } - public static func Delete(before: B.Element) -> Free { - return .Pure(B(deleting: before)) + public static func Delete(before: Value.Element) -> Free { + return .Pure(Value(deleting: before)) } @@ -172,7 +172,7 @@ extension Free where B: PatchType { // MARK: - Equality extension Free { - public static func equals(ifPure ifPure: (B, B) -> Bool, ifRoll: (Leaf, Leaf) -> Bool)(_ left: Free, _ right: Free) -> Bool { + public static func equals(ifPure ifPure: (Value, Value) -> Bool, ifRoll: (Leaf, Leaf) -> Bool)(_ left: Free, _ right: Free) -> Bool { switch (left, right) { case let (.Pure(a), .Pure(b)): return ifPure(a, b) @@ -184,7 +184,7 @@ extension Free { } } -public func == (left: Free, right: Free) -> Bool { +public func == (left: Free, right: Free) -> Bool { return Free.equals(ifPure: ==, ifRoll: ==)(left, right) } @@ -196,7 +196,7 @@ public func == (left: Free Doubt.JSON, ifLeaf: Leaf -> Doubt.JSON) -> Doubt.JSON { + public func JSON(ifPure ifPure: Value -> Doubt.JSON, ifLeaf: Leaf -> Doubt.JSON) -> Doubt.JSON { return analysis( ifPure: ifPure, ifRoll: { @@ -206,7 +206,7 @@ extension Free { } extension Free where Leaf: CustomJSONConvertible { - public func JSON(ifPure: B -> Doubt.JSON) -> Doubt.JSON { + public func JSON(ifPure: Value -> Doubt.JSON) -> Doubt.JSON { return JSON(ifPure: ifPure, ifLeaf: { $0.JSON }) } } From 03aa95caf9652ce083d7db3a64a32d4774be700a Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:17:51 -0400 Subject: [PATCH 03/55] Rename the TermType.LeafType associated type to Leaf. --- prototype/Doubt/Algorithm.swift | 2 +- prototype/Doubt/Free.swift | 6 +++--- prototype/Doubt/Interpreter.swift | 4 ++-- prototype/Doubt/Patch.swift | 2 +- prototype/Doubt/TermType.swift | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/prototype/Doubt/Algorithm.swift b/prototype/Doubt/Algorithm.swift index e2cc28c03..555f71f0b 100644 --- a/prototype/Doubt/Algorithm.swift +++ b/prototype/Doubt/Algorithm.swift @@ -8,7 +8,7 @@ public enum Algorithm { public typealias Patch = Doubt.Patch /// The type of `Diff`s which `Algorithm`s produce. - public typealias Diff = Free + public typealias Diff = Free /// The injection of a value of type `Result` into an `Operation`. /// diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index dd7d4aef9..538101e39 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -13,8 +13,8 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { indirect case Roll(Syntax) - /// Recursively copies a `Term: TermType where Term.LeafType == Leaf` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. - public init(_ term: Term) { + /// Recursively copies a `Term: TermType where Term.Leaf == Leaf` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. + public init(_ term: Term) { self = .Roll(term.unwrap.map(Free.init)) } @@ -188,7 +188,7 @@ public func == (left: Free, rig return Free.equals(ifPure: ==, ifRoll: ==)(left, right) } -public func == (left: Free>, right: Free>) -> Bool { +public func == (left: Free>, right: Free>) -> Bool { return Free.equals(ifPure: Patch.equals(Term.equals(==)), ifRoll: ==)(left, right) } diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 3b327c3fe..071634370 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -1,7 +1,7 @@ /// An interpreter of `Algorithm`s. public struct Interpreter { /// The type of diffs constructed by `Interpreter`s. - public typealias Diff = Free> + public typealias Diff = Free> /// Constructs an `Interpreter` parameterized by the `equal` and `comparable` tests on `Term`s, and the `cost` function for `Diff`s. /// @@ -92,7 +92,7 @@ public struct Interpreter { // MARK: - Constrained constructors -extension Interpreter where Term.LeafType: Equatable { +extension Interpreter where Term.Leaf: Equatable { public init(comparable: (Term, Term) -> Bool, cost: Diff -> Int) { self.init(equal: Term.equals(==), comparable: comparable, cost: cost) } diff --git a/prototype/Doubt/Patch.swift b/prototype/Doubt/Patch.swift index 153f6d7f2..094abb6a7 100644 --- a/prototype/Doubt/Patch.swift +++ b/prototype/Doubt/Patch.swift @@ -130,7 +130,7 @@ extension Patch where A: CustomJSONConvertible { // MARK: - PatchType -/// A hack to enable constrained extensions on `Free`. +/// A hack to enable constrained extensions on `Free`. public protocol PatchType { typealias Element diff --git a/prototype/Doubt/TermType.swift b/prototype/Doubt/TermType.swift index 06408d430..e4dcf8e99 100644 --- a/prototype/Doubt/TermType.swift +++ b/prototype/Doubt/TermType.swift @@ -1,8 +1,8 @@ /// The type of terms. public protocol TermType { - typealias LeafType + typealias Leaf - var unwrap: Syntax { get } + var unwrap: Syntax { get } } @@ -10,14 +10,14 @@ extension TermType { /// Catamorphism over `TermType`s. /// /// Folds the tree encoded by the receiver into a single value by recurring top-down through the tree, applying `transform` to leaves, then to branches, and so forth. - public func cata(transform: Syntax -> Result) -> Result { + public func cata(transform: Syntax -> Result) -> Result { return self |> ({ $0.unwrap } >>> { $0.map { $0.cata(transform) } } >>> transform) } /// Paramorphism over `TermType`s. /// /// Folds the tree encoded by the receiver into a single value by recurring top-down through the tree, applying `transform` to leaves, then to branches, and so forth. Each recursive instance is made available in the `Syntax` alongside the result value at that node. - public func para(transform: Syntax<(Self, Result), LeafType> -> Result) -> Result { + public func para(transform: Syntax<(Self, Result), Leaf> -> Result) -> Result { return self |> ({ $0.unwrap } >>> { $0.map { ($0, $0.para(transform)) } } >>> transform) } @@ -46,7 +46,7 @@ extension Cofree: TermType {} // MARK: - Equality extension TermType { - public static func equals(leaf: (LeafType, LeafType) -> Bool)(_ a: Self, _ b: Self) -> Bool { + public static func equals(leaf: (Leaf, Leaf) -> Bool)(_ a: Self, _ b: Self) -> Bool { return Syntax.equals(ifLeaf: leaf, ifRecur: equals(leaf))(a.unwrap, b.unwrap) } } From 234c7f59bdde49fcb810aefbb4efc2bb93594702 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:18:27 -0400 Subject: [PATCH 04/55] Rename more typealiases to drop the Type suffix. --- prototype/Doubt/Syntax.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index 0c32c333e..288afc0ce 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -91,22 +91,22 @@ extension Syntax { /// SyntaxConvertible types can be constructed with the same constructors available on Syntax itself, as a convenience. public protocol SyntaxConvertible { - typealias RecurType - typealias LeafType + typealias Recur + typealias Leaf - init(syntax: Syntax) + init(syntax: Syntax) } extension SyntaxConvertible { - public static func Leaf(value: LeafType) -> Self { + public static func Leaf(value: Leaf) -> Self { return Self(syntax: .Leaf(value)) } - public static func Indexed(children: [RecurType]) -> Self { + public static func Indexed(children: [Recur]) -> Self { return Self(syntax: .Indexed(children)) } - public static func Keyed(children: [String:RecurType]) -> Self { + public static func Keyed(children: [String:Recur]) -> Self { return Self(syntax: .Keyed(children)) } } From c75b36512cf75d5ad14a601f9080b3806c24a295 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:21:04 -0400 Subject: [PATCH 05/55] CofreeType implies TermType. --- prototype/Doubt/Cofree.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 6078fc1c2..9cb1409dd 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -114,12 +114,10 @@ extension Cofree where B: Categorizable { // MARK: - CofreeType -public protocol CofreeType { +public protocol CofreeType: TermType { typealias Annotation - typealias Leaf var extract: Annotation { get } - var unwrap: Syntax { get } } extension Cofree: CofreeType {} From b3fea32706d6b2bed2b21250cee7cb2363b79b34 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:21:29 -0400 Subject: [PATCH 06/55] Remove the explicit conformance to TermType. --- prototype/Doubt/TermType.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/prototype/Doubt/TermType.swift b/prototype/Doubt/TermType.swift index e4dcf8e99..4ecb857a5 100644 --- a/prototype/Doubt/TermType.swift +++ b/prototype/Doubt/TermType.swift @@ -40,9 +40,6 @@ extension TermType { } -extension Cofree: TermType {} - - // MARK: - Equality extension TermType { From 810c6081b97b058e5cc6b82558231578ac9d63f3 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:22:21 -0400 Subject: [PATCH 07/55] Rename the B parameter to Annotation. --- prototype/Doubt/Cofree.swift | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 9cb1409dd..4397cad09 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -1,10 +1,10 @@ /// The cofree comonad over `Syntax`. /// -/// This is “free” in the sense of “unconstrained” rather than “zero-cost”; it’s the comonad obtained by taking a functor (in this case `Syntax`) and adding the minimum necessary details (the `B` paired with it) to satisfy the comonad laws. +/// This is “free” in the sense of “unconstrained” rather than “zero-cost”; it’s the comonad obtained by taking a functor (in this case `Syntax`) and adding the minimum necessary details (the `Annotation` paired with it) to satisfy the comonad laws. /// -/// This type is dual to `Free`. Where `Free` is inhabited by syntax trees where some terms are replaced with `B`s, `Cofree` is inhabited by syntax trees where all terms are annotated with `B`s. In Doubt, this allows us to e.g. annotate terms with source range information, categorization, etc. -public enum Cofree { - indirect case Unroll(B, Syntax) +/// This type is dual to `Free`. Where `Free` is inhabited by syntax trees where some terms are replaced with `Annotation`s, `Cofree` is inhabited by syntax trees where all terms are annotated with `Annotation`s. In Doubt, this allows us to e.g. annotate terms with source range information, categorization, etc. +public enum Cofree { + indirect case Unroll(Annotation, Syntax) public var unwrap: Syntax { switch self { @@ -14,7 +14,7 @@ public enum Cofree { } - public init(_ annotation: B, _ syntax: Syntax) { + public init(_ annotation: Annotation, _ syntax: Syntax) { self = .Unroll(annotation, syntax) } @@ -24,7 +24,7 @@ public enum Cofree { /// The initial seed is used as the annotation of the returned value. The continuation of the structure is unpacked by applying `annotate` to the seed and mapping the resulting syntax’s values recursively. In this manner, the structure is unfolded bottom-up, starting with `seed` and ending at the leaves. /// /// As this is the dual of `Free.iterate`, it’s unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree. - public static func coiterate(annotate: B -> Syntax)(_ seed: B) -> Cofree { + public static func coiterate(annotate: Annotation -> Syntax)(_ seed: Annotation) -> Cofree { return .Unroll(seed, annotate(seed).map(coiterate(annotate))) } } @@ -42,7 +42,7 @@ extension Cofree: CustomDebugStringConvertible { // MARK: - Functor extension Cofree { - public func map(transform: B -> Other) -> Cofree { + public func map(transform: Annotation -> Other) -> Cofree { return .Unroll(transform(extract), unwrap.map { $0.map(transform) }) } } @@ -52,7 +52,7 @@ extension Cofree { extension Cofree { /// Returns the value annotating the syntax tree at this node. - public var extract: B { + public var extract: Annotation { switch self { case let .Unroll(b, _): return b @@ -65,7 +65,7 @@ extension Cofree { } /// Returns a new `Cofree` constructed by recursively annotating each subtree with itself. - public var duplicate: Cofree> { + public var duplicate: Cofree> { return extend(id) } } @@ -74,13 +74,13 @@ extension Cofree { // MARK: - Equality extension Cofree { - public static func equals(annotation annotation: (B, B) -> Bool, leaf: (A, A) -> Bool)(_ left: Cofree, _ right: Cofree) -> Bool { + public static func equals(annotation annotation: (Annotation, Annotation) -> Bool, leaf: (A, A) -> Bool)(_ left: Cofree, _ right: Cofree) -> Bool { return annotation(left.extract, right.extract) && Syntax.equals(ifLeaf: leaf, ifRecur: Cofree.equals(annotation: annotation, leaf: leaf))(left.unwrap, right.unwrap) } } -public func == (left: Cofree, right: Cofree) -> Bool { +public func == (left: Cofree, right: Cofree) -> Bool { return Cofree.equals(annotation: ==, leaf: ==)(left, right) } @@ -88,7 +88,7 @@ public func == (left: Cofree, right: Cofree Doubt.JSON, leaf: A -> Doubt.JSON) -> Doubt.JSON { + public func JSON(annotation annotation: Annotation -> Doubt.JSON, leaf: A -> Doubt.JSON) -> Doubt.JSON { return [ "extract": annotation(extract), "unwrap": unwrap.JSON(ifLeaf: leaf, ifRecur: { $0.JSON(annotation: annotation, leaf: leaf) }) @@ -96,7 +96,7 @@ extension Cofree { } } -extension Cofree where A: CustomJSONConvertible, B: CustomJSONConvertible { +extension Cofree where A: CustomJSONConvertible, Annotation: CustomJSONConvertible { public var JSON: Doubt.JSON { return JSON(annotation: { $0.JSON }, leaf: { $0.JSON }) } @@ -105,8 +105,8 @@ extension Cofree where A: CustomJSONConvertible, B: CustomJSONConvertible { // MARK: - Categorizable -extension Cofree where B: Categorizable { - var categories: Set { +extension Cofree where Annotation: Categorizable { + var categories: Set { return extract.categories } } From 20ef96b483b288484438c137869e14c2961bba03 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:23:20 -0400 Subject: [PATCH 08/55] Rename the A type parameter to Leaf. --- prototype/Doubt/Cofree.swift | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 4397cad09..1577e3915 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -3,10 +3,10 @@ /// This is “free” in the sense of “unconstrained” rather than “zero-cost”; it’s the comonad obtained by taking a functor (in this case `Syntax`) and adding the minimum necessary details (the `Annotation` paired with it) to satisfy the comonad laws. /// /// This type is dual to `Free`. Where `Free` is inhabited by syntax trees where some terms are replaced with `Annotation`s, `Cofree` is inhabited by syntax trees where all terms are annotated with `Annotation`s. In Doubt, this allows us to e.g. annotate terms with source range information, categorization, etc. -public enum Cofree { - indirect case Unroll(Annotation, Syntax) +public enum Cofree { + indirect case Unroll(Annotation, Syntax) - public var unwrap: Syntax { + public var unwrap: Syntax { switch self { case let .Unroll(_, rest): return rest @@ -14,7 +14,7 @@ public enum Cofree { } - public init(_ annotation: Annotation, _ syntax: Syntax) { + public init(_ annotation: Annotation, _ syntax: Syntax) { self = .Unroll(annotation, syntax) } @@ -24,7 +24,7 @@ public enum Cofree { /// The initial seed is used as the annotation of the returned value. The continuation of the structure is unpacked by applying `annotate` to the seed and mapping the resulting syntax’s values recursively. In this manner, the structure is unfolded bottom-up, starting with `seed` and ending at the leaves. /// /// As this is the dual of `Free.iterate`, it’s unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree. - public static func coiterate(annotate: Annotation -> Syntax)(_ seed: Annotation) -> Cofree { + public static func coiterate(annotate: Annotation -> Syntax)(_ seed: Annotation) -> Cofree { return .Unroll(seed, annotate(seed).map(coiterate(annotate))) } } @@ -42,7 +42,7 @@ extension Cofree: CustomDebugStringConvertible { // MARK: - Functor extension Cofree { - public func map(transform: Annotation -> Other) -> Cofree { + public func map(transform: Annotation -> Other) -> Cofree { return .Unroll(transform(extract), unwrap.map { $0.map(transform) }) } } @@ -60,12 +60,12 @@ extension Cofree { } /// Returns a new `Cofree` by recursively applying `transform` to each node, producing the annotations for the copy. - public func extend(transform: Cofree -> Other) -> Cofree { + public func extend(transform: Cofree -> Other) -> Cofree { return .Unroll(transform(self), unwrap.map { $0.extend(transform) }) } /// Returns a new `Cofree` constructed by recursively annotating each subtree with itself. - public var duplicate: Cofree> { + public var duplicate: Cofree> { return extend(id) } } @@ -74,13 +74,13 @@ extension Cofree { // MARK: - Equality extension Cofree { - public static func equals(annotation annotation: (Annotation, Annotation) -> Bool, leaf: (A, A) -> Bool)(_ left: Cofree, _ right: Cofree) -> Bool { + public static func equals(annotation annotation: (Annotation, Annotation) -> Bool, leaf: (Leaf, Leaf) -> Bool)(_ left: Cofree, _ right: Cofree) -> Bool { return annotation(left.extract, right.extract) && Syntax.equals(ifLeaf: leaf, ifRecur: Cofree.equals(annotation: annotation, leaf: leaf))(left.unwrap, right.unwrap) } } -public func == (left: Cofree, right: Cofree) -> Bool { +public func == (left: Cofree, right: Cofree) -> Bool { return Cofree.equals(annotation: ==, leaf: ==)(left, right) } @@ -88,7 +88,7 @@ public func == (left: Cofree Doubt.JSON, leaf: A -> Doubt.JSON) -> Doubt.JSON { + public func JSON(annotation annotation: Annotation -> Doubt.JSON, leaf: Leaf -> Doubt.JSON) -> Doubt.JSON { return [ "extract": annotation(extract), "unwrap": unwrap.JSON(ifLeaf: leaf, ifRecur: { $0.JSON(annotation: annotation, leaf: leaf) }) @@ -96,7 +96,7 @@ extension Cofree { } } -extension Cofree where A: CustomJSONConvertible, Annotation: CustomJSONConvertible { +extension Cofree where Leaf: CustomJSONConvertible, Annotation: CustomJSONConvertible { public var JSON: Doubt.JSON { return JSON(annotation: { $0.JSON }, leaf: { $0.JSON }) } From 581ea694f930ee799a7b2b9f2de68271e001de85 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:26:36 -0400 Subject: [PATCH 09/55] =?UTF-8?q?Interpreter=E2=80=99s=20parameter=20must?= =?UTF-8?q?=20be=20a=20CofreeType.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/Doubt/Interpreter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 071634370..6f1a74a12 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -1,5 +1,5 @@ /// An interpreter of `Algorithm`s. -public struct Interpreter { +public struct Interpreter { /// The type of diffs constructed by `Interpreter`s. public typealias Diff = Free> From 695b69b086edf3242fa03f4cc832bf0cca0f162c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:26:45 -0400 Subject: [PATCH 10/55] =?UTF-8?q?Algorithm=E2=80=99s=20parameter=20must=20?= =?UTF-8?q?be=20a=20CofreeType.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/Doubt/Algorithm.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Algorithm.swift b/prototype/Doubt/Algorithm.swift index 555f71f0b..0e9669fe7 100644 --- a/prototype/Doubt/Algorithm.swift +++ b/prototype/Doubt/Algorithm.swift @@ -3,7 +3,7 @@ /// As with `Free`, this is “free” in the sense of “unconstrained,” i.e. “the monad induced by `Operation` without extra assumptions.” /// /// Where `Operation` models a single diffing strategy, `Algorithm` models the recursive selection of diffing strategies at each node. Thus, a value in `Algorithm` models an algorithm for constructing a value in the type `Result` from the resulting diffs. By this means, diffing can be adapted not just to the specific grammar, but to specific trees produced by that grammar, and even the values of type `A` encapsulated at each node. -public enum Algorithm { +public enum Algorithm { /// The type of `Patch`es produced by `Algorithm`s. public typealias Patch = Doubt.Patch From c5e01c41dbbbc91a487763bc9016ae181df91f98 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:28:22 -0400 Subject: [PATCH 11/55] =?UTF-8?q?Rename=20SES=E2=80=99=20A=20type=20parame?= =?UTF-8?q?ter=20to=20Leaf.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/Doubt/SES.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prototype/Doubt/SES.swift b/prototype/Doubt/SES.swift index e9d3ec285..a5dc352a0 100644 --- a/prototype/Doubt/SES.swift +++ b/prototype/Doubt/SES.swift @@ -1,8 +1,8 @@ -/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free>`) for two arrays of `Term`s which would suffice to transform `a` into `b`. +/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free>`) for two arrays of `Term`s which would suffice to transform `a` into `b`. /// /// This is computed w.r.t. an `equals` function, which computes the equality of leaf nodes within terms, and a `recur` function, which produces diffs representing matched-up terms. -public func SES(a: [Term], _ b: [Term], cost: Free> -> Int, recur: (Term, Term) -> Free>?) -> [Free>] { - typealias Diff = Free> +public func SES(a: [Term], _ b: [Term], cost: Free> -> Int, recur: (Term, Term) -> Free>?) -> [Free>] { + typealias Diff = Free> if a.isEmpty { return b.map { .Insert($0) } } if b.isEmpty { return a.map { .Delete($0) } } From c9b33f826c3f99bd60583228031ceabcadc03afe Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:30:38 -0400 Subject: [PATCH 12/55] Add an annotation parameter to Free. --- prototype/Doubt/Algorithm.swift | 2 +- prototype/Doubt/Free.swift | 10 +++++----- prototype/Doubt/Interpreter.swift | 2 +- prototype/Doubt/JSONLeaf.swift | 2 +- prototype/Doubt/SES.swift | 4 ++-- prototype/DoubtTests/DiffTests.swift | 2 +- prototype/DoubtTests/InterpreterTests.swift | 2 +- prototype/DoubtTests/RangedDiff.swift | 2 +- prototype/DoubtTests/SESTests.swift | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/prototype/Doubt/Algorithm.swift b/prototype/Doubt/Algorithm.swift index 0e9669fe7..97431d0d4 100644 --- a/prototype/Doubt/Algorithm.swift +++ b/prototype/Doubt/Algorithm.swift @@ -8,7 +8,7 @@ public enum Algorithm { public typealias Patch = Doubt.Patch /// The type of `Diff`s which `Algorithm`s produce. - public typealias Diff = Free + public typealias Diff = Free /// The injection of a value of type `Result` into an `Operation`. /// diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 538101e39..03a3f7ff2 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -5,7 +5,7 @@ /// `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 `Value` 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: CustomDebugStringConvertible, SyntaxConvertible { +public enum Free: CustomDebugStringConvertible, SyntaxConvertible { /// The injection of a value of type `Value` into the `Syntax` tree. case Pure(Value) @@ -83,14 +83,14 @@ public enum Free: CustomDebugStringConvertible, SyntaxConvertible { // MARK: Functor - public func map(@noescape transform: Value -> C) -> Free { + public func map(@noescape transform: Value -> C) -> Free { return analysis(ifPure: { .Pure(transform($0)) }, ifRoll: { .Roll($0.map { $0.map(transform) }) }) } // MARK: Monad - public func flatMap(@noescape transform: Value -> Free) -> Free { + public func flatMap(@noescape transform: Value -> Free) -> Free { return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) }) } @@ -184,11 +184,11 @@ extension Free { } } -public func == (left: Free, right: Free) -> Bool { +public func == (left: Free, right: Free) -> Bool { return Free.equals(ifPure: ==, ifRoll: ==)(left, right) } -public func == (left: Free>, right: Free>) -> Bool { +public func == (left: Free>, right: Free>) -> Bool { return Free.equals(ifPure: Patch.equals(Term.equals(==)), ifRoll: ==)(left, right) } diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 6f1a74a12..63dd1d646 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -1,7 +1,7 @@ /// An interpreter of `Algorithm`s. public struct Interpreter { /// The type of diffs constructed by `Interpreter`s. - public typealias Diff = Free> + public typealias Diff = Free> /// Constructs an `Interpreter` parameterized by the `equal` and `comparable` tests on `Term`s, and the `cost` function for `Diff`s. /// diff --git a/prototype/Doubt/JSONLeaf.swift b/prototype/Doubt/JSONLeaf.swift index a50a8aacd..aa89e6961 100644 --- a/prototype/Doubt/JSONLeaf.swift +++ b/prototype/Doubt/JSONLeaf.swift @@ -10,7 +10,7 @@ import Foundation import Doubt typealias Term = Cofree> -typealias Diff = Free> +typealias Diff = Free> enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable { case Number(Double) diff --git a/prototype/Doubt/SES.swift b/prototype/Doubt/SES.swift index a5dc352a0..5c434cc3e 100644 --- a/prototype/Doubt/SES.swift +++ b/prototype/Doubt/SES.swift @@ -1,8 +1,8 @@ /// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free>`) for two arrays of `Term`s which would suffice to transform `a` into `b`. /// /// This is computed w.r.t. an `equals` function, which computes the equality of leaf nodes within terms, and a `recur` function, which produces diffs representing matched-up terms. -public func SES(a: [Term], _ b: [Term], cost: Free> -> Int, recur: (Term, Term) -> Free>?) -> [Free>] { - typealias Diff = Free> +public func SES(a: [Term], _ b: [Term], cost: Free> -> Int, recur: (Term, Term) -> Free>?) -> [Free>] { + typealias Diff = Free> if a.isEmpty { return b.map { .Insert($0) } } if b.isEmpty { return a.map { .Delete($0) } } diff --git a/prototype/DoubtTests/DiffTests.swift b/prototype/DoubtTests/DiffTests.swift index 985de4ea8..fc532d467 100644 --- a/prototype/DoubtTests/DiffTests.swift +++ b/prototype/DoubtTests/DiffTests.swift @@ -4,7 +4,7 @@ final class DiffTests: XCTestCase { } typealias Term = RangedTerm.Term - typealias Diff = Free> + typealias Diff = Free> let interpreter = Interpreter(equal: ==, comparable: const(true), cost: Diff.sum(const(1))) diff --git a/prototype/DoubtTests/InterpreterTests.swift b/prototype/DoubtTests/InterpreterTests.swift index 4ca9aa52c..7507dc70e 100644 --- a/prototype/DoubtTests/InterpreterTests.swift +++ b/prototype/DoubtTests/InterpreterTests.swift @@ -21,7 +21,7 @@ final class InterpreterTests: XCTestCase { private typealias Term = Cofree -private typealias Diff = Free> +private typealias Diff = Free> private let a = Term(0, [ Term(1, .Leaf("a")), Term(2, .Leaf("b")), Term(3, .Leaf("c")) ]) private let b = Term(0, [ Term(1, .Leaf("c")), Term(2, .Leaf("b")), Term(3, .Leaf("a")) ]) diff --git a/prototype/DoubtTests/RangedDiff.swift b/prototype/DoubtTests/RangedDiff.swift index 37831b50a..bbb2d3a44 100644 --- a/prototype/DoubtTests/RangedDiff.swift +++ b/prototype/DoubtTests/RangedDiff.swift @@ -1,5 +1,5 @@ struct RangedDiff { - typealias Diff = Free> + typealias Diff = Free> let a: RangedTerm let b: RangedTerm diff --git a/prototype/DoubtTests/SESTests.swift b/prototype/DoubtTests/SESTests.swift index 1c15f404e..316810c4f 100644 --- a/prototype/DoubtTests/SESTests.swift +++ b/prototype/DoubtTests/SESTests.swift @@ -41,7 +41,7 @@ final class SESTests: XCTestCase { } private typealias Term = Cofree -private typealias Diff = Free> +private typealias Diff = Free> private let a = Term((), .Leaf("a")) private let b = Term((), .Leaf("b")) From ad05308ab699d79df8811f1c16c9e1319fc82e89 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:33:25 -0400 Subject: [PATCH 13/55] =?UTF-8?q?Define=20the=20recursive=20TermType=20?= =?UTF-8?q?=E2=86=92=20Free=20constructor=20over=20CofreeType=20instead.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 03a3f7ff2..6eeefb510 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -14,7 +14,7 @@ public enum Free: CustomDebugStringConvertible, SyntaxC /// Recursively copies a `Term: TermType where Term.Leaf == Leaf` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. - public init(_ term: Term) { + public init(_ term: Term) { self = .Roll(term.unwrap.map(Free.init)) } From 750def8107afaee6639750d12498eae75bfef78e Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:37:00 -0400 Subject: [PATCH 14/55] Fix up some comments. --- prototype/Doubt/Free.swift | 6 +++--- prototype/Doubt/Patch.swift | 2 +- prototype/Doubt/SES.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 6eeefb510..fe7dbe0a1 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -13,8 +13,8 @@ public enum Free: CustomDebugStringConvertible, SyntaxC indirect case Roll(Syntax) - /// Recursively copies a `Term: TermType where Term.Leaf == Leaf` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. - public init(_ term: Term) { + /// Recursively copies a `Term: TermType where Term.Leaf == Leaf, Term.Annotation == Annotation` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. + public init(_ term: Term) { self = .Roll(term.unwrap.map(Free.init)) } @@ -38,7 +38,7 @@ public enum Free: CustomDebugStringConvertible, SyntaxC /// /// The linearity of `iterate` in the size of the receiver makes it trivial to compute said size, by counting leaves as 1 and summing branches’ children: /// - /// func size(free: Free) -> Int { + /// func size(free: Free) -> Int { /// return free.iterate { flattenedSyntax in /// switch flattenedSyntax { /// case .Leaf: diff --git a/prototype/Doubt/Patch.swift b/prototype/Doubt/Patch.swift index 094abb6a7..7bfc13014 100644 --- a/prototype/Doubt/Patch.swift +++ b/prototype/Doubt/Patch.swift @@ -130,7 +130,7 @@ extension Patch where A: CustomJSONConvertible { // MARK: - PatchType -/// A hack to enable constrained extensions on `Free`. +/// A hack to enable constrained extensions on `Free`. public protocol PatchType { typealias Element diff --git a/prototype/Doubt/SES.swift b/prototype/Doubt/SES.swift index 5c434cc3e..755e656e9 100644 --- a/prototype/Doubt/SES.swift +++ b/prototype/Doubt/SES.swift @@ -1,4 +1,4 @@ -/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free>`) for two arrays of `Term`s which would suffice to transform `a` into `b`. +/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free>`) for two arrays of `Term`s which would suffice to transform `a` into `b`. /// /// This is computed w.r.t. an `equals` function, which computes the equality of leaf nodes within terms, and a `recur` function, which produces diffs representing matched-up terms. public func SES(a: [Term], _ b: [Term], cost: Free> -> Int, recur: (Term, Term) -> Free>?) -> [Free>] { From 5cfe2b33b9743e0c3b0a2df61417d42e0b92975d Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:39:33 -0400 Subject: [PATCH 15/55] Free is no longer SyntaxConvertible. --- prototype/Doubt/Free.swift | 2 +- prototype/Doubt/Interpreter.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index fe7dbe0a1..27fd4ef07 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -5,7 +5,7 @@ /// `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 `Value` 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: CustomDebugStringConvertible, SyntaxConvertible { +public enum Free: CustomDebugStringConvertible { /// The injection of a value of type `Value` into the `Syntax` tree. case Pure(Value) diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 63dd1d646..d32948481 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -66,10 +66,10 @@ public struct Interpreter { // Recur structurally into both terms, patching differing sub-terms. This is akin to unification, except that it computes a patched tree instead of a substitution. It’s also a little like a structural zip on pairs of terms. switch (a.unwrap, b.unwrap) { case let (.Indexed(a), .Indexed(b)) where a.count == b.count: - return recur(f(.Indexed(zip(a, b).map(run)))) + return recur(f(.Roll(.Indexed(zip(a, b).map(run))))) case let (.Keyed(a), .Keyed(b)) where Array(a.keys) == Array(b.keys): - return recur(f(.Keyed(Dictionary(elements: b.keys.map { ($0, self.run(a[$0]!, b[$0]!)) })))) + return recur(f(.Roll(.Keyed(Dictionary(elements: b.keys.map { ($0, self.run(a[$0]!, b[$0]!)) }))))) default: // This must not call `recur` directly with `a` and `b`, as that would infinite loop if actually recursive. From 0a28a390b760e31089c0b96f7c4be3d513fb94a5 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:40:08 -0400 Subject: [PATCH 16/55] Remove SyntaxConvertible. --- prototype/Doubt/Free.swift | 7 ------- prototype/Doubt/Syntax.swift | 25 ------------------------- 2 files changed, 32 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 27fd4ef07..2238d75bb 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -105,13 +105,6 @@ public enum Free: CustomDebugStringConvertible { return ".Roll(\(String(reflecting: s)))" } } - - - // MARK: SyntaxConvertible - - public init(syntax: Syntax) { - self = .Roll(syntax) - } } diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index 288afc0ce..f89db37c9 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -85,28 +85,3 @@ extension Syntax { } } } - - -// MARK: - Construction - -/// SyntaxConvertible types can be constructed with the same constructors available on Syntax itself, as a convenience. -public protocol SyntaxConvertible { - typealias Recur - typealias Leaf - - init(syntax: Syntax) -} - -extension SyntaxConvertible { - public static func Leaf(value: Leaf) -> Self { - return Self(syntax: .Leaf(value)) - } - - public static func Indexed(children: [Recur]) -> Self { - return Self(syntax: .Indexed(children)) - } - - public static func Keyed(children: [String:Recur]) -> Self { - return Self(syntax: .Keyed(children)) - } -} From d678946fda910084f2b3732d4ef436debbacf811 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:41:42 -0400 Subject: [PATCH 17/55] Name the piecewise equality functions after the type parameters. --- prototype/Doubt/Free.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 2238d75bb..e72a3611a 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -165,12 +165,12 @@ extension Free where Value: PatchType { // MARK: - Equality extension Free { - public static func equals(ifPure ifPure: (Value, Value) -> Bool, ifRoll: (Leaf, Leaf) -> Bool)(_ left: Free, _ right: Free) -> Bool { + public static func equals(pure pure: (Value, Value) -> Bool, leaf: (Leaf, Leaf) -> Bool)(_ left: Free, _ right: Free) -> Bool { switch (left, right) { case let (.Pure(a), .Pure(b)): - return ifPure(a, b) + return pure(a, b) case let (.Roll(a), .Roll(b)): - return Syntax.equals(ifLeaf: ifRoll, ifRecur: equals(ifPure: ifPure, ifRoll: ifRoll))(a, b) + return Syntax.equals(ifLeaf: leaf, ifRecur: equals(pure: pure, leaf: leaf))(a, b) default: return false } @@ -178,11 +178,11 @@ extension Free { } public func == (left: Free, right: Free) -> Bool { - return Free.equals(ifPure: ==, ifRoll: ==)(left, right) + return Free.equals(pure: ==, leaf: ==)(left, right) } public func == (left: Free>, right: Free>) -> Bool { - return Free.equals(ifPure: Patch.equals(Term.equals(==)), ifRoll: ==)(left, right) + return Free.equals(pure: Patch.equals(Term.equals(==)), leaf: ==)(left, right) } From b7a341b8b19583344a893cba4584298ee0899730 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:43:20 -0400 Subject: [PATCH 18/55] Correct omissions in the tests. --- prototype/DoubtTests/DiffTests.swift | 2 +- prototype/DoubtTests/SESTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prototype/DoubtTests/DiffTests.swift b/prototype/DoubtTests/DiffTests.swift index fc532d467..1a49f89b1 100644 --- a/prototype/DoubtTests/DiffTests.swift +++ b/prototype/DoubtTests/DiffTests.swift @@ -50,7 +50,7 @@ final class DiffTests: XCTestCase { private func equal(a: DiffTests.Diff, _ b: DiffTests.Diff) -> Bool { - return Free.equals(ifPure: Patch.equals(Cofree.equals(annotation: ==, leaf: ==)), ifRoll: ==)(a, b) + return Free.equals(pure: Patch.equals(Cofree.equals(annotation: ==, leaf: ==)), leaf: ==)(a, b) } diff --git a/prototype/DoubtTests/SESTests.swift b/prototype/DoubtTests/SESTests.swift index 316810c4f..4885a6871 100644 --- a/prototype/DoubtTests/SESTests.swift +++ b/prototype/DoubtTests/SESTests.swift @@ -53,7 +53,7 @@ private func SES(a: [Term], _ b: [Term]) -> [Diff] { } private func == (a: [Diff], b: [Diff]) -> Bool { - return a.count == b.count && zip(a, b).lazy.map(Diff.equals(ifPure: Patch.equals(Cofree.equals(annotation: const(true), leaf: ==)), ifRoll: ==)).reduce(true) { $0 && $1 } + return a.count == b.count && zip(a, b).lazy.map(Diff.equals(pure: Patch.equals(Cofree.equals(annotation: const(true), leaf: ==)), leaf: ==)).reduce(true) { $0 && $1 } } From 831f8dcbaefc403ddcd8187d07af3e43f013f9c6 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:43:42 -0400 Subject: [PATCH 19/55] =?UTF-8?q?Name=20Free=E2=80=99s=20piecewise=20JSON?= =?UTF-8?q?=20functions=20after=20the=20type=20parameters.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/Doubt/Free.swift | 10 +++++----- prototype/doubt-json/main.swift | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index e72a3611a..9dd2be29f 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -189,18 +189,18 @@ public func == (left: Free Doubt.JSON, ifLeaf: Leaf -> Doubt.JSON) -> Doubt.JSON { + public func JSON(pure pure: Value -> Doubt.JSON, leaf: Leaf -> Doubt.JSON) -> Doubt.JSON { return analysis( - ifPure: ifPure, + ifPure: pure, ifRoll: { - $0.JSON(ifLeaf: ifLeaf, ifRecur: { $0.JSON(ifPure: ifPure, ifLeaf: ifLeaf) }) + $0.JSON(ifLeaf: leaf, ifRecur: { $0.JSON(pure: pure, leaf: leaf) }) }) } } extension Free where Leaf: CustomJSONConvertible { - public func JSON(ifPure: Value -> Doubt.JSON) -> Doubt.JSON { - return JSON(ifPure: ifPure, ifLeaf: { $0.JSON }) + public func JSON(pure: Value -> Doubt.JSON) -> Doubt.JSON { + return JSON(pure: pure, leaf: { $0.JSON }) } } diff --git a/prototype/doubt-json/main.swift b/prototype/doubt-json/main.swift index 4d3f07075..4b18a08a5 100644 --- a/prototype/doubt-json/main.swift +++ b/prototype/doubt-json/main.swift @@ -55,7 +55,7 @@ func diffAndSerialize(a aString: String, b bString: String, to: String) throws { [ "a": .String(aString), "b": .String(bString), - "diff": diff.JSON(ifPure: { $0.JSON { $0.JSON(annotation: range, leaf: { $0.JSON }) } }, ifLeaf: { $0.JSON }), + "diff": diff.JSON(pure: { $0.JSON { $0.JSON(annotation: range, leaf: { $0.JSON }) } }, leaf: { $0.JSON }), ] } From 40fe88b4a45d17dbe0c41f85288177a9f2f38e89 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:46:51 -0400 Subject: [PATCH 20/55] Rename the Syntax piecewise equality functions after the type parameters (mostly). --- prototype/Doubt/Cofree.swift | 2 +- prototype/Doubt/Free.swift | 2 +- prototype/Doubt/Syntax.swift | 10 +++++----- prototype/Doubt/TermType.swift | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 1577e3915..437a4f8dc 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -76,7 +76,7 @@ extension Cofree { extension Cofree { public static func equals(annotation annotation: (Annotation, Annotation) -> Bool, leaf: (Leaf, Leaf) -> Bool)(_ left: Cofree, _ right: Cofree) -> Bool { return annotation(left.extract, right.extract) - && Syntax.equals(ifLeaf: leaf, ifRecur: Cofree.equals(annotation: annotation, leaf: leaf))(left.unwrap, right.unwrap) + && Syntax.equals(leaf: leaf, recur: Cofree.equals(annotation: annotation, leaf: leaf))(left.unwrap, right.unwrap) } } diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 9dd2be29f..aa08848d7 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -170,7 +170,7 @@ extension Free { case let (.Pure(a), .Pure(b)): return pure(a, b) case let (.Roll(a), .Roll(b)): - return Syntax.equals(ifLeaf: leaf, ifRecur: equals(pure: pure, leaf: leaf))(a, b) + return Syntax.equals(leaf: leaf, recur: equals(pure: pure, leaf: leaf))(a, b) default: return false } diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index f89db37c9..e2d9e4f7b 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -52,14 +52,14 @@ extension Syntax: DictionaryLiteralConvertible { // MARK: - Equality extension Syntax { - public static func equals(ifLeaf ifLeaf: (A, A) -> Bool, ifRecur: (Recur, Recur) -> Bool)(_ left: Syntax, _ right: Syntax) -> Bool { + public static func equals(leaf leaf: (A, A) -> Bool, recur: (Recur, Recur) -> Bool)(_ left: Syntax, _ right: Syntax) -> Bool { switch (left, right) { case let (.Leaf(l1), .Leaf(l2)): - return ifLeaf(l1, l2) + return leaf(l1, l2) case let (.Indexed(v1), .Indexed(v2)): - return v1.count == v2.count && zip(v1, v2).lazy.map(ifRecur).reduce(true) { $0 && $1 } + return v1.count == v2.count && zip(v1, v2).lazy.map(recur).reduce(true) { $0 && $1 } case let (.Keyed(d1), .Keyed(d2)): - return Set(d1.keys) == Set(d2.keys) && d1.keys.map { ifRecur(d1[$0]!, d2[$0]!) }.reduce(true) { $0 && $1 } + return Set(d1.keys) == Set(d2.keys) && d1.keys.map { recur(d1[$0]!, d2[$0]!) }.reduce(true) { $0 && $1 } default: return false } @@ -67,7 +67,7 @@ extension Syntax { } public func == (left: Syntax, right: Syntax) -> Bool { - return Syntax.equals(ifLeaf: ==, ifRecur: ==)(left, right) + return Syntax.equals(leaf: ==, recur: ==)(left, right) } diff --git a/prototype/Doubt/TermType.swift b/prototype/Doubt/TermType.swift index 4ecb857a5..ff651ba8d 100644 --- a/prototype/Doubt/TermType.swift +++ b/prototype/Doubt/TermType.swift @@ -44,7 +44,7 @@ extension TermType { extension TermType { public static func equals(leaf: (Leaf, Leaf) -> Bool)(_ a: Self, _ b: Self) -> Bool { - return Syntax.equals(ifLeaf: leaf, ifRecur: equals(leaf))(a.unwrap, b.unwrap) + return Syntax.equals(leaf: leaf, recur: equals(leaf))(a.unwrap, b.unwrap) } } From 8cbb69fa97bc176a624066a17c5167bd02774cca Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:47:48 -0400 Subject: [PATCH 21/55] Rename the Syntax piecewise JSON functions after the type parameters (mostly). --- prototype/Doubt/Cofree.swift | 6 +++--- prototype/Doubt/Free.swift | 2 +- prototype/Doubt/Syntax.swift | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 437a4f8dc..ff2f96d9a 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -91,7 +91,7 @@ extension Cofree { public func JSON(annotation annotation: Annotation -> Doubt.JSON, leaf: Leaf -> Doubt.JSON) -> Doubt.JSON { return [ "extract": annotation(extract), - "unwrap": unwrap.JSON(ifLeaf: leaf, ifRecur: { $0.JSON(annotation: annotation, leaf: leaf) }) + "unwrap": unwrap.JSON(leaf: leaf, recur: { $0.JSON(annotation: annotation, leaf: leaf) }) ] } } @@ -125,8 +125,8 @@ extension Cofree: CofreeType {} extension CofreeType where Self.Annotation == Range { public func JSON(source: String) -> Doubt.JSON { return unwrap.JSON( - ifLeaf: { _ in .String(source[extract]) }, - ifRecur: { + leaf: { _ in .String(source[extract]) }, + recur: { [ "range": [ "offset": .Number(Double(source.startIndex.distanceTo($0.extract.startIndex))), diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index aa08848d7..ec22d4061 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -193,7 +193,7 @@ extension Free { return analysis( ifPure: pure, ifRoll: { - $0.JSON(ifLeaf: leaf, ifRecur: { $0.JSON(pure: pure, leaf: leaf) }) + $0.JSON(leaf: leaf, recur: { $0.JSON(pure: pure, leaf: leaf) }) }) } } diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index e2d9e4f7b..b2971939e 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -74,14 +74,14 @@ public func == (left: Syntax, right: Syntax Doubt.JSON, @noescape ifRecur: Recur -> Doubt.JSON) -> Doubt.JSON { + public func JSON(@noescape leaf leaf: A -> Doubt.JSON, @noescape recur: Recur -> Doubt.JSON) -> Doubt.JSON { switch self { case let .Leaf(a): - return ifLeaf(a) + return leaf(a) case let .Indexed(a): - return .Array(a.map(ifRecur)) + return .Array(a.map(recur)) case let .Keyed(d): - return .Dictionary(Dictionary(elements: d.map { ($0, ifRecur($1)) })) + return .Dictionary(Dictionary(elements: d.map { ($0, recur($1)) })) } } } From 76b7df9d68f5b1e8a08270364e5a2c49ac14fd90 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:54:49 -0400 Subject: [PATCH 22/55] Constrain Free equality over CofreeType to terms whose annotations are equatable. --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index ec22d4061..b813cfc70 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -181,7 +181,7 @@ public func == (left: return Free.equals(pure: ==, leaf: ==)(left, right) } -public func == (left: Free>, right: Free>) -> Bool { +public func == (left: Free>, right: Free>) -> Bool { return Free.equals(pure: Patch.equals(Term.equals(==)), leaf: ==)(left, right) } From 6d665ff391650b915dee6ce8e1ebe3fa089f8d5e Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 10:57:31 -0400 Subject: [PATCH 23/55] Remove an unused conversion of Free to JSON. --- prototype/Doubt/Free.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index b813cfc70..ee5f52ad6 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -198,11 +198,5 @@ extension Free { } } -extension Free where Leaf: CustomJSONConvertible { - public func JSON(pure: Value -> Doubt.JSON) -> Doubt.JSON { - return JSON(pure: pure, leaf: { $0.JSON }) - } -} - import Prelude From 453fd8031888283665c585fd7a19aaca08fc5dea Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 11:35:25 -0400 Subject: [PATCH 24/55] Section header for CustomDebugStringConvertible. --- prototype/Doubt/Syntax.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index b2971939e..781b5f002 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -18,6 +18,9 @@ public enum Syntax: CustomDebugStringConvertible { } } + + // MARK: CustomDebugStringConvertible + public var debugDescription: String { switch self { case let .Leaf(n): From 651a3c2f910a6c2ff01801587a1e3f7f54b22629 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 15:50:16 -0400 Subject: [PATCH 25/55] =?UTF-8?q?Don=E2=80=99t=20constrain=20recursive=20F?= =?UTF-8?q?ree=20construction=20from=20TermType=20to=20the=20same=20annota?= =?UTF-8?q?tion=20type.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index ff205d32a..d58b3d358 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -14,7 +14,7 @@ public enum Free: CustomDebugStringConvertible { /// Recursively copies a `Term: TermType where Term.Leaf == Leaf, Term.Annotation == Annotation` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. - public init(_ term: Term) { + public init(_ term: Term) { self = .Roll(term.unwrap.map(Free.init)) } From 5e0fbd8d8c6311ee4e5eed9d708ecc97cbe404bf Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 15:51:52 -0400 Subject: [PATCH 26/55] Revert "Constrain Free equality over CofreeType to terms whose annotations are equatable." This reverts commit a79f354992cbca36fa3e55de648e6bc6c55f956b. --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index d58b3d358..ad2682164 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -193,7 +193,7 @@ public func == (left: return Free.equals(pure: ==, leaf: ==)(left, right) } -public func == (left: Free>, right: Free>) -> Bool { +public func == (left: Free>, right: Free>) -> Bool { return Free.equals(pure: Patch.equals(Term.equals(==)), leaf: ==)(left, right) } From 39ae63d42618439bb785e2693def5dc6148e2740 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 15:52:08 -0400 Subject: [PATCH 27/55] =?UTF-8?q?It=20doesn=E2=80=99t=20matter=20if=20anno?= =?UTF-8?q?tations=20are=20equatable.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index ad2682164..70a04f598 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -189,7 +189,7 @@ extension Free { } } -public func == (left: Free, right: Free) -> Bool { +public func == (left: Free, right: Free) -> Bool { return Free.equals(pure: ==, leaf: ==)(left, right) } From 49fb14f6a8bcba4872e0feb97c57d47b404880f9 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 16:01:48 -0400 Subject: [PATCH 28/55] Define Free equality over Patch without reference to annotation equality. --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 70a04f598..2e8a41d90 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -193,7 +193,7 @@ public func == (left: Free (left: Free>, right: Free>) -> Bool { +public func == (left: Free>, right: Free>) -> Bool { return Free.equals(pure: Patch.equals(Term.equals(==)), leaf: ==)(left, right) } From d6a9be0794406eed57feeeac301b5249ddb84b75 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 16:02:03 -0400 Subject: [PATCH 29/55] Double down on annotations. --- prototype/Doubt/Algorithm.swift | 2 +- prototype/Doubt/Interpreter.swift | 2 +- prototype/DoubtTests/DiffTests.swift | 2 +- prototype/DoubtTests/InterpreterTests.swift | 2 +- prototype/DoubtTests/RangedDiff.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/prototype/Doubt/Algorithm.swift b/prototype/Doubt/Algorithm.swift index 97431d0d4..a3009cbce 100644 --- a/prototype/Doubt/Algorithm.swift +++ b/prototype/Doubt/Algorithm.swift @@ -8,7 +8,7 @@ public enum Algorithm { public typealias Patch = Doubt.Patch /// The type of `Diff`s which `Algorithm`s produce. - public typealias Diff = Free + public typealias Diff = Free /// The injection of a value of type `Result` into an `Operation`. /// diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 159c0605c..804de08fe 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -1,7 +1,7 @@ /// An interpreter of `Algorithm`s. public struct Interpreter { /// The type of diffs constructed by `Interpreter`s. - public typealias Diff = Free> + public typealias Diff = Free> /// Constructs an `Interpreter` parameterized by the `equal` and `comparable` tests on `Term`s, and the `cost` function for `Diff`s. /// diff --git a/prototype/DoubtTests/DiffTests.swift b/prototype/DoubtTests/DiffTests.swift index 1a49f89b1..c53778f68 100644 --- a/prototype/DoubtTests/DiffTests.swift +++ b/prototype/DoubtTests/DiffTests.swift @@ -4,7 +4,7 @@ final class DiffTests: XCTestCase { } typealias Term = RangedTerm.Term - typealias Diff = Free> + typealias Diff = Free> let interpreter = Interpreter(equal: ==, comparable: const(true), cost: Diff.sum(const(1))) diff --git a/prototype/DoubtTests/InterpreterTests.swift b/prototype/DoubtTests/InterpreterTests.swift index 7507dc70e..3cc40ad3b 100644 --- a/prototype/DoubtTests/InterpreterTests.swift +++ b/prototype/DoubtTests/InterpreterTests.swift @@ -21,7 +21,7 @@ final class InterpreterTests: XCTestCase { private typealias Term = Cofree -private typealias Diff = Free> +private typealias Diff = Free> private let a = Term(0, [ Term(1, .Leaf("a")), Term(2, .Leaf("b")), Term(3, .Leaf("c")) ]) private let b = Term(0, [ Term(1, .Leaf("c")), Term(2, .Leaf("b")), Term(3, .Leaf("a")) ]) diff --git a/prototype/DoubtTests/RangedDiff.swift b/prototype/DoubtTests/RangedDiff.swift index bbb2d3a44..554b29e1b 100644 --- a/prototype/DoubtTests/RangedDiff.swift +++ b/prototype/DoubtTests/RangedDiff.swift @@ -1,5 +1,5 @@ struct RangedDiff { - typealias Diff = Free> + typealias Diff = Free> let a: RangedTerm let b: RangedTerm From f0379f421be80d3806d09b84c1243d6d89d908cb Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 17:08:17 -0400 Subject: [PATCH 30/55] Define `ana` using the backwards application operator to clarify dataflow. --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 2e8a41d90..a2bb266c9 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -115,7 +115,7 @@ extension Free { /// /// Unfolds a tree bottom-up by recursively applying `transform` to a series of values starting with `seed`. Since `Syntax.Leaf` does not recur, this will halt when it has produced leaves for every branch. public static func ana(transform: Seed -> Syntax)(_ seed: Seed) -> Free { - return (Roll <<< { $0.map(ana(transform)) } <<< transform)(seed) + return (Roll <<< { $0.map(ana(transform)) } <<< transform) <| seed } } From 090406aafeea1f6351a5dffbccf70605d4619468 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 17:09:00 -0400 Subject: [PATCH 31/55] Define `coiterate` so as to reflect its symmetry with `ana`. --- prototype/Doubt/Cofree.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 5b38591a7..9360ddf84 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -25,7 +25,7 @@ public enum Cofree { /// /// As this is the dual of `Free.iterate`, it’s unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree. public static func coiterate(annotate: Annotation -> Syntax)(_ seed: Annotation) -> Cofree { - return .Unroll(seed, annotate(seed).map(coiterate(annotate))) + return (curry(Unroll)(seed) <<< { $0.map(coiterate(annotate)) } <<< annotate) <| seed } } From f4966684a5f1c1b7bece43522db13c2b52465d7e Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 17:09:40 -0400 Subject: [PATCH 32/55] Rename the functions to `unfold`. --- prototype/Doubt/Cofree.swift | 4 ++-- prototype/Doubt/Free.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 9360ddf84..a61bcbd27 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -24,8 +24,8 @@ public enum Cofree { /// This is an _anamorphism_ (from the Greek “ana,” “upwards”; compare “anabolism”), a generalization of unfolds over regular trees (and datatypes isomorphic to them). The initial seed is used as the annotation of the returned value. The continuation of the structure is unpacked by applying `annotate` to the seed and mapping the resulting syntax’s values recursively. In this manner, the structure is unfolded bottom-up, starting with `seed` and ending at the leaves. /// /// As this is the dual of `Free.iterate`, it’s unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree. - public static func coiterate(annotate: Annotation -> Syntax)(_ seed: Annotation) -> Cofree { - return (curry(Unroll)(seed) <<< { $0.map(coiterate(annotate)) } <<< annotate) <| seed + public static func coiterate(unfold: Annotation -> Syntax)(_ seed: Annotation) -> Cofree { + return (curry(Unroll)(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed } } diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index a2bb266c9..244f2ecfb 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -114,8 +114,8 @@ extension Free { /// Anamorphism over `Free`. /// /// Unfolds a tree bottom-up by recursively applying `transform` to a series of values starting with `seed`. Since `Syntax.Leaf` does not recur, this will halt when it has produced leaves for every branch. - public static func ana(transform: Seed -> Syntax)(_ seed: Seed) -> Free { - return (Roll <<< { $0.map(ana(transform)) } <<< transform) <| seed + public static func ana(unfold: Seed -> Syntax)(_ seed: Seed) -> Free { + return (Roll <<< { $0.map(ana(unfold)) } <<< unfold) <| seed } } From 521e2532404dc23275487f9757ad49b6e1aeb201 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 17:11:35 -0400 Subject: [PATCH 33/55] Define hylomorphism through Syntax. --- prototype/Doubt/Free.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 244f2ecfb..beac82e83 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -120,6 +120,13 @@ extension Free { } +// MARK: - Hylomorphism + +public func hylo(down: Syntax -> B, _ up: A -> Syntax) -> A -> B { + return up >>> { $0.map(hylo(down, up)) } >>> down +} + + extension Free where Value: PatchType, Value.Element == Cofree { public typealias Term = Value.Element From d0aeedeb523b269cd4944cf4fd590fe05b8e7061 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 17:15:49 -0400 Subject: [PATCH 34/55] Define `iterate` so as to clarify its relationship with `cata`. --- prototype/Doubt/Free.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index beac82e83..6a5d0c4aa 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -57,7 +57,7 @@ public enum Free: CustomDebugStringConvertible { public func iterate(transform: Syntax -> Value) -> Value { return analysis( ifPure: id, - ifRoll: { transform($0.map { $0.iterate(transform) }) }) + ifRoll: { $0.map { $0.iterate(transform) } } >>> transform) } From 4195edc469723ab4afad70c621298438846f172e Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 17:25:58 -0400 Subject: [PATCH 35/55] Define a `reiterate` function. --- prototype/Doubt/Free.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 6a5d0c4aa..e9bc00462 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -126,6 +126,11 @@ public func hylo(down: Syntax -> B, _ up: A -> Syntax>> { $0.map(hylo(down, up)) } >>> down } +public func reiterate(down: (Annotation, Syntax) -> B, _ up: A -> (Annotation, Syntax)) -> A -> B { + return up >>> { ($0, $1.map(reiterate(down, up))) } >>> down +} + + extension Free where Value: PatchType, Value.Element == Cofree { public typealias Term = Value.Element From c6962c798dcf43486a97e2014f277fa7bac151cb Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:02:44 -0400 Subject: [PATCH 36/55] CofreeType requires a constructor. --- prototype/Doubt/Cofree.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index a61bcbd27..bf97c83e1 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -117,6 +117,7 @@ extension Cofree where Annotation: Categorizable { public protocol CofreeType: TermType { typealias Annotation + init(_ annotation: Annotation, _ syntax: Syntax) var extract: Annotation { get } } From 9ff1840e4a9e3d6393f894fe591609f4e844e364 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:04:19 -0400 Subject: [PATCH 37/55] CofreeTypes have a static curried Wrap constructor function. --- prototype/Doubt/Cofree.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index bf97c83e1..71ea55a8a 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -121,6 +121,12 @@ public protocol CofreeType: TermType { var extract: Annotation { get } } +extension CofreeType { + public static func Wrap(annotation: Annotation)(syntax: Syntax) -> Self { + return Self(annotation, syntax) + } +} + extension Cofree: CofreeType {} extension CofreeType where Self.Annotation == Range { From 6bc8fe428553539dfc352ddc0926e009779f33a2 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:04:51 -0400 Subject: [PATCH 38/55] Define `coiterate` over `CofreeType`. --- prototype/Doubt/Cofree.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 71ea55a8a..98044a58a 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -17,16 +17,6 @@ public enum Cofree { public init(_ annotation: Annotation, _ syntax: Syntax) { self = .Unroll(annotation, syntax) } - - - /// Constructs a cofree by coiteration. - /// - /// This is an _anamorphism_ (from the Greek “ana,” “upwards”; compare “anabolism”), a generalization of unfolds over regular trees (and datatypes isomorphic to them). The initial seed is used as the annotation of the returned value. The continuation of the structure is unpacked by applying `annotate` to the seed and mapping the resulting syntax’s values recursively. In this manner, the structure is unfolded bottom-up, starting with `seed` and ending at the leaves. - /// - /// As this is the dual of `Free.iterate`, it’s unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree. - public static func coiterate(unfold: Annotation -> Syntax)(_ seed: Annotation) -> Cofree { - return (curry(Unroll)(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed - } } @@ -125,6 +115,15 @@ extension CofreeType { public static func Wrap(annotation: Annotation)(syntax: Syntax) -> Self { return Self(annotation, syntax) } + + /// Constructs a cofree by coiteration. + /// + /// This is an _anamorphism_ (from the Greek “ana,” “upwards”; compare “anabolism”), a generalization of unfolds over regular trees (and datatypes isomorphic to them). The initial seed is used as the annotation of the returned value. The continuation of the structure is unpacked by applying `annotate` to the seed and mapping the resulting syntax’s values recursively. In this manner, the structure is unfolded bottom-up, starting with `seed` and ending at the leaves. + /// + /// As this is the dual of `Free.iterate`, it’s unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree. + public static func coiterate(unfold: Annotation -> Syntax)(_ seed: Annotation) -> Self { + return (Wrap(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed + } } extension Cofree: CofreeType {} From 7b57a9ce173fa6270cb1861277f2154733849641 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:06:35 -0400 Subject: [PATCH 39/55] CofreeType values can be zipped. --- prototype/Doubt/Cofree.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 98044a58a..d74f73cb6 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -124,6 +124,20 @@ extension CofreeType { public static func coiterate(unfold: Annotation -> Syntax)(_ seed: Annotation) -> Self { return (Wrap(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed } + + public static func zip(a: Self, _ b: Self) -> Cofree? { + let annotations = (a.extract, b.extract) + switch (a.unwrap, b.unwrap) { + case let (.Leaf, .Leaf(b)): + return Cofree(annotations, .Leaf(b)) + case let (.Indexed(a), .Indexed(b)): + return Cofree(annotations, .Indexed(Swift.zip(a, b).flatMap(zip))) + case let (.Keyed(a), .Keyed(b)): + return Cofree(annotations, .Keyed(Dictionary(elements: b.keys.flatMap { key in zip(a[key]!, b[key]!).map { (key, $0) } }))) + default: + return nil + } + } } extension Cofree: CofreeType {} From c84578402c8ada6e25f87ff2abdad00b524e4494 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:09:18 -0400 Subject: [PATCH 40/55] Document `CofreeType.zip`. --- prototype/Doubt/Cofree.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index d74f73cb6..b79c5ee80 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -125,6 +125,9 @@ extension CofreeType { return (Wrap(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed } + /// `Zip` two `CofreeType` values into a single `Cofree`, pairing their annotations. + /// + /// This is partial, returning `nil` for any pair of values which are not of the same “shape,” i.e. where they wrap `Syntax` values of different constructors. The values of leaves are always taken from the second parameter. public static func zip(a: Self, _ b: Self) -> Cofree? { let annotations = (a.extract, b.extract) switch (a.unwrap, b.unwrap) { From b425d69c364410fb548b5605dea15978f137b32e Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:18:05 -0400 Subject: [PATCH 41/55] Define `hylo` and `reiterate` through `Syntax`. --- prototype/Doubt/Free.swift | 12 ------------ prototype/Doubt/Syntax.swift | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index e9bc00462..f7c7ef4a3 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -120,18 +120,6 @@ extension Free { } -// MARK: - Hylomorphism - -public func hylo(down: Syntax -> B, _ up: A -> Syntax) -> A -> B { - return up >>> { $0.map(hylo(down, up)) } >>> down -} - -public func reiterate(down: (Annotation, Syntax) -> B, _ up: A -> (Annotation, Syntax)) -> A -> B { - return up >>> { ($0, $1.map(reiterate(down, up))) } >>> down -} - - - extension Free where Value: PatchType, Value.Element == Cofree { public typealias Term = Value.Element diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index 781b5f002..63e0d4005 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -34,6 +34,17 @@ public enum Syntax: CustomDebugStringConvertible { } +// MARK: - Hylomorphism + +public func hylo(down: Syntax -> B, _ up: A -> Syntax) -> A -> B { + return up >>> { $0.map(hylo(down, up)) } >>> down +} + +public func reiterate(down: (Annotation, Syntax) -> B, _ up: A -> (Annotation, Syntax)) -> A -> B { + return up >>> { ($0, $1.map(reiterate(down, up))) } >>> down +} + + // MARK: - ArrayLiteralConvertible extension Syntax: ArrayLiteralConvertible { @@ -88,3 +99,6 @@ extension Syntax { } } } + + +import Prelude From 397af0d02d534fd87662cce55ef3561c31f3f553 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:31:06 -0400 Subject: [PATCH 42/55] Documentation for `hylo`. --- prototype/Doubt/Syntax.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index 63e0d4005..2cec8f707 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -36,6 +36,13 @@ public enum Syntax: CustomDebugStringConvertible { // MARK: - Hylomorphism +/// Hylomorphism through `Syntax`. +/// +/// A hylomorphism (from the Aristotelian philosophy that form and matter are one) is a function of type `A → B` whose call-tree is linear in the size of the nodes produced by `up`. Conceptually, it’s the composition of a catamorphism (see also `TermType.cata`, `Free.iterate`) and an anamorphism (see also `Free.ana`, `CofreeType.coiterate`), but is implemented by [Stream fusion](http://lambda-the-ultimate.org/node/2192) and as such enjoys O(n) time complexity, O(1) size complexity, and small constant factors for both (modulo inadvisable implementations of `up` and `down`). +/// +/// Hylomorphisms are used to construct diffs corresponding to equal terms; see also `CofreeType.zip`. +/// +/// `hylo` can be used with arbitrary functors which can eliminate to and introduce with `Syntax` values. public func hylo(down: Syntax -> B, _ up: A -> Syntax) -> A -> B { return up >>> { $0.map(hylo(down, up)) } >>> down } From 95adbdf042d881c85a5836f76af8f8c475e33473 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:31:11 -0400 Subject: [PATCH 43/55] Documentation for `reiterate`. --- prototype/Doubt/Syntax.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index 2cec8f707..bd9e917f1 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -47,6 +47,13 @@ public func hylo(down: Syntax -> B, _ up: A -> Syntax>> { $0.map(hylo(down, up)) } >>> down } +/// Reiteration through `Syntax`. +/// +/// This is a form of hylomorphism (from the Aristotelian philosophy that form and matter are one). As such, it returns a function of type `A → B` whose call-tree is linear in the size of the nodes produced by `up`. Conceptually, it’s the composition of a catamorphism (see also `TermType.cata`, `Free.iterate`) and an anamorphism (see also `Free.ana`, `CofreeType.coiterate`), but is implemented by [Stream fusion](http://lambda-the-ultimate.org/node/2192) and as such enjoys O(n) time complexity, O(1) size complexity, and small constant factors for both (modulo inadvisable implementations of `up` and `down`). +/// +/// Hylomorphisms are used to construct diffs corresponding to equal terms; see also `CofreeType.zip`. +/// +/// `reiterate` can be used with arbitrary functors which can eliminate to and introduce with `Annotation` & `Syntax` pairs. public func reiterate(down: (Annotation, Syntax) -> B, _ up: A -> (Annotation, Syntax)) -> A -> B { return up >>> { ($0, $1.map(reiterate(down, up))) } >>> down } From b210cf858639c6862274ed92a8a8ca6bd5596199 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:32:41 -0400 Subject: [PATCH 44/55] Rephrase copying terms into diffs as a hylomorphism. --- prototype/Doubt/Interpreter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 804de08fe..0d65b5b2a 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -42,7 +42,7 @@ public struct Interpreter { /// Diff `a` against `b`, if comparable. private func recur(a: Term, _ b: Term) -> Diff? { - if equal(a, b) { return Diff.ana(Term.unwrap)(b) } + if equal(a, b) { return hylo(Diff.Roll, Term.unwrap)(b) } guard comparable(a, b) else { return nil } let algorithm: Algorithm From f8739e90b006b97d64f7b9c6b8499358ede50f74 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:42:04 -0400 Subject: [PATCH 45/55] zip the terms. --- prototype/Doubt/Interpreter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 0d65b5b2a..36ab86184 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -42,7 +42,7 @@ public struct Interpreter { /// Diff `a` against `b`, if comparable. private func recur(a: Term, _ b: Term) -> Diff? { - if equal(a, b) { return hylo(Diff.Roll, Term.unwrap)(b) } + if equal(a, b) { return hylo(Diff.Roll, { $0.unwrap })(Term.zip(a, b)!) } guard comparable(a, b) else { return nil } let algorithm: Algorithm From 6423d5817f8de85daeb37df588580318ca4689fd Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:43:08 -0400 Subject: [PATCH 46/55] Rename the introduction form for Cofree. --- prototype/Doubt/Cofree.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index b79c5ee80..437e33e70 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -112,7 +112,7 @@ public protocol CofreeType: TermType { } extension CofreeType { - public static func Wrap(annotation: Annotation)(syntax: Syntax) -> Self { + public static func Introduce(annotation: Annotation)(syntax: Syntax) -> Self { return Self(annotation, syntax) } @@ -122,7 +122,7 @@ extension CofreeType { /// /// As this is the dual of `Free.iterate`, it’s unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree. public static func coiterate(unfold: Annotation -> Syntax)(_ seed: Annotation) -> Self { - return (Wrap(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed + return (Introduce(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed } /// `Zip` two `CofreeType` values into a single `Cofree`, pairing their annotations. From 3a1ced9786dcb3585f0213019f22afc097c4b4f1 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:43:17 -0400 Subject: [PATCH 47/55] Add an elimination form for Cofree. --- prototype/Doubt/Cofree.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index 437e33e70..926c4503b 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -116,6 +116,10 @@ extension CofreeType { return Self(annotation, syntax) } + public static func eliminate(term: Self) -> (Annotation, Syntax) { + return (term.extract, term.unwrap) + } + /// Constructs a cofree by coiteration. /// /// This is an _anamorphism_ (from the Greek “ana,” “upwards”; compare “anabolism”), a generalization of unfolds over regular trees (and datatypes isomorphic to them). The initial seed is used as the annotation of the returned value. The continuation of the structure is unpacked by applying `annotate` to the seed and mapping the resulting syntax’s values recursively. In this manner, the structure is unfolded bottom-up, starting with `seed` and ending at the leaves. From 1ee9663e6ef493a3ef88e9b18f5c294adcb7fe92 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:43:49 -0400 Subject: [PATCH 48/55] Unwrap with the static eliminator. --- prototype/Doubt/Interpreter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 36ab86184..51375fd21 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -42,7 +42,7 @@ public struct Interpreter { /// Diff `a` against `b`, if comparable. private func recur(a: Term, _ b: Term) -> Diff? { - if equal(a, b) { return hylo(Diff.Roll, { $0.unwrap })(Term.zip(a, b)!) } + if equal(a, b) { return hylo(Diff.Roll, Cofree.unwrap)(Term.zip(a, b)!) } guard comparable(a, b) else { return nil } let algorithm: Algorithm From 6cca28582dae7c6a2c1392ac5dee275d3c3845c7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:51:14 -0400 Subject: [PATCH 49/55] Add a convenience to copy terms into diffs. --- prototype/DoubtTests/SESTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prototype/DoubtTests/SESTests.swift b/prototype/DoubtTests/SESTests.swift index 4885a6871..313644e61 100644 --- a/prototype/DoubtTests/SESTests.swift +++ b/prototype/DoubtTests/SESTests.swift @@ -43,6 +43,10 @@ final class SESTests: XCTestCase { private typealias Term = Cofree private typealias Diff = Free> +private func copy(term: Term) -> Diff { + return hylo(Diff.Introduce(()), Term.unwrap)(term) +} + private let a = Term((), .Leaf("a")) private let b = Term((), .Leaf("b")) private let c = Term((), .Leaf("c")) From 3682aca8a0fa768b2b51e31066dfd2052477b7cb Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:54:10 -0400 Subject: [PATCH 50/55] Rename the convenience to avoid the method of the same name. --- prototype/DoubtTests/SESTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/DoubtTests/SESTests.swift b/prototype/DoubtTests/SESTests.swift index 313644e61..db4780999 100644 --- a/prototype/DoubtTests/SESTests.swift +++ b/prototype/DoubtTests/SESTests.swift @@ -43,7 +43,7 @@ final class SESTests: XCTestCase { private typealias Term = Cofree private typealias Diff = Free> -private func copy(term: Term) -> Diff { +private func Copy(term: Term) -> Diff { return hylo(Diff.Introduce(()), Term.unwrap)(term) } From b194dede9aafdf05387da7fad081c5263230e9a7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:55:28 -0400 Subject: [PATCH 51/55] Add annotations to Free :sob: This is heartbreaking, but it was always going to end this way. --- prototype/Doubt/Free.swift | 47 ++++++++++++--------- prototype/Doubt/Interpreter.swift | 14 +++--- prototype/DoubtTests/DiffTests.swift | 2 +- prototype/DoubtTests/InterpreterTests.swift | 12 +++--- prototype/DoubtTests/SESTests.swift | 18 ++++---- 5 files changed, 52 insertions(+), 41 deletions(-) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index f7c7ef4a3..232de5aa2 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -10,16 +10,10 @@ public enum Free: CustomDebugStringConvertible { case Pure(Value) /// A recursive instantiation of `Syntax`, unrolling another iteration of the recursive type. - indirect case Roll(Syntax) + indirect case Roll(Annotation, Syntax) - /// Recursively copies a `Term: TermType where Term.Leaf == Leaf, Term.Annotation == Annotation` into a `Free`, essentially mapping `Term.unwrap` onto `Free.Roll`. - public init(_ term: Term) { - self = .Roll(term.unwrap.map(Free.init)) - } - - - public func analysis(@noescape ifPure ifPure: Value -> C, @noescape ifRoll: Syntax -> C) -> C { + public func analysis(@noescape ifPure ifPure: Value -> C, @noescape ifRoll: (Annotation, Syntax) -> C) -> C { switch self { case let .Pure(b): return ifPure(b) @@ -57,7 +51,7 @@ public enum Free: CustomDebugStringConvertible { public func iterate(transform: Syntax -> Value) -> Value { return analysis( ifPure: id, - ifRoll: { $0.map { $0.iterate(transform) } } >>> transform) + ifRoll: { $1.map { $0.iterate(transform) } } >>> transform) } @@ -84,14 +78,14 @@ public enum Free: CustomDebugStringConvertible { // MARK: Functor public func map(@noescape transform: Value -> C) -> Free { - return analysis(ifPure: { .Pure(transform($0)) }, ifRoll: { .Roll($0.map { $0.map(transform) }) }) + return analysis(ifPure: { .Pure(transform($0)) }, ifRoll: { .Roll($0, $1.map { $0.map(transform) }) }) } // MARK: Monad public func flatMap(@noescape transform: Value -> Free) -> Free { - return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) }) + return analysis(ifPure: transform, ifRoll: { .Roll($0, $1.map { $0.flatMap(transform) }) }) } @@ -111,11 +105,15 @@ public enum Free: CustomDebugStringConvertible { // MARK: - Anamorphism extension Free { + public static func Introduce(annotation: Annotation)(syntax: Syntax) -> Free { + return Roll(annotation, syntax) + } + /// Anamorphism over `Free`. /// /// Unfolds a tree bottom-up by recursively applying `transform` to a series of values starting with `seed`. Since `Syntax.Leaf` does not recur, this will halt when it has produced leaves for every branch. - public static func ana(unfold: Seed -> Syntax)(_ seed: Seed) -> Free { - return (Roll <<< { $0.map(ana(unfold)) } <<< unfold) <| seed + public static func coiterate(unfold: Annotation -> Syntax)(_ seed: Annotation) -> Free { + return (Introduce(seed) <<< { $0.map(coiterate(unfold)) } <<< unfold) <| seed } } @@ -177,35 +175,42 @@ extension Free where Value: PatchType { // MARK: - Equality extension Free { - public static func equals(pure pure: (Value, Value) -> Bool, leaf: (Leaf, Leaf) -> Bool)(_ left: Free, _ right: Free) -> Bool { + public static func equals(pure pure: (Value, Value) -> Bool, leaf: (Leaf, Leaf) -> Bool, annotation: (Annotation, Annotation) -> Bool)(_ left: Free, _ right: Free) -> Bool { switch (left, right) { case let (.Pure(a), .Pure(b)): return pure(a, b) - case let (.Roll(a), .Roll(b)): - return Syntax.equals(leaf: leaf, recur: equals(pure: pure, leaf: leaf))(a, b) + case let (.Roll(annotation1, syntax1), .Roll(annotation2, syntax2)): + return annotation(annotation1, annotation2) && Syntax.equals(leaf: leaf, recur: equals(pure: pure, leaf: leaf, annotation: annotation))(syntax1, syntax2) default: return false } } } -public func == (left: Free, right: Free) -> Bool { - return Free.equals(pure: ==, leaf: ==)(left, right) +public func == (left: Free, right: Free) -> Bool { + return Free.equals(pure: ==, leaf: ==, annotation: ==)(left, right) +} + +public func == (left: Free>, right: Free>) -> Bool { + return Free.equals(pure: Patch.equals(Term.equals(==)), leaf: ==, annotation: ==)(left, right) } public func == (left: Free>, right: Free>) -> Bool { - return Free.equals(pure: Patch.equals(Term.equals(==)), leaf: ==)(left, right) + return Free.equals(pure: Patch.equals(Term.equals(==)), leaf: ==, annotation: const(true))(left, right) } // MARK: - JSON extension Free { - public func JSON(pure pure: Value -> Doubt.JSON, leaf: Leaf -> Doubt.JSON) -> Doubt.JSON { + public func JSON(pure pure: Value -> Doubt.JSON, leaf: Leaf -> Doubt.JSON, annotation: Annotation -> Doubt.JSON) -> Doubt.JSON { return analysis( ifPure: pure, ifRoll: { - $0.JSON(leaf: leaf, recur: { $0.JSON(pure: pure, leaf: leaf) }) + [ + "extract": annotation($0), + "unwrap": $1.JSON(leaf: leaf, recur: { $0.JSON(pure: pure, leaf: leaf, annotation: annotation) }) + ] }) } } diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index 51375fd21..e4627df63 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -42,15 +42,18 @@ public struct Interpreter { /// Diff `a` against `b`, if comparable. private func recur(a: Term, _ b: Term) -> Diff? { - if equal(a, b) { return hylo(Diff.Roll, Cofree.unwrap)(Term.zip(a, b)!) } + if equal(a, b) { return reiterate(Diff.Roll, Cofree.eliminate)(Term.zip(a, b)!) } guard comparable(a, b) else { return nil } let algorithm: Algorithm + let annotations = (a.extract, b.extract) switch (a.unwrap, b.unwrap) { + case let (.Leaf, .Leaf(leaf)) where equal(a, b): + return .Roll(annotations, .Leaf(leaf)) case let (.Keyed(a), .Keyed(b)): - algorithm = .Roll(.ByKey(a, b, Syntax.Keyed >>> Diff.Roll >>> Algorithm.Pure)) + algorithm = .Roll(.ByKey(a, b, Syntax.Keyed >>> curry(Diff.Roll)(annotations) >>> Algorithm.Pure)) case let (.Indexed(a), .Indexed(b)): - algorithm = .Roll(.ByIndex(a, b, Syntax.Indexed >>> Diff.Roll >>> Algorithm.Pure)) + algorithm = .Roll(.ByIndex(a, b, Syntax.Indexed >>> curry(Diff.Roll)(annotations) >>> Algorithm.Pure)) default: algorithm = .Roll(.Recursive(a, b, Algorithm.Pure)) } @@ -64,12 +67,13 @@ public struct Interpreter { case let .Roll(.Recursive(a, b, f)): // Recur structurally into both terms, patching differing sub-terms. This is akin to unification, except that it computes a patched tree instead of a substitution. It’s also a little like a structural zip on pairs of terms. + let annotations = (a.extract, b.extract) switch (a.unwrap, b.unwrap) { case let (.Indexed(a), .Indexed(b)) where a.count == b.count: - return recur(f(.Roll(.Indexed(zip(a, b).map(run))))) + return recur(f(.Roll(annotations, .Indexed(zip(a, b).map(run))))) case let (.Keyed(a), .Keyed(b)) where Array(a.keys) == Array(b.keys): - return recur(f(.Roll(.Keyed(Dictionary(elements: b.keys.map { ($0, self.run(a[$0]!, b[$0]!)) }))))) + return recur(f(.Roll(annotations, .Keyed(Dictionary(elements: b.keys.map { ($0, self.run(a[$0]!, b[$0]!)) }))))) default: // This must not call `recur` directly with `a` and `b`, as that would infinite loop if actually recursive. diff --git a/prototype/DoubtTests/DiffTests.swift b/prototype/DoubtTests/DiffTests.swift index c53778f68..ffef01989 100644 --- a/prototype/DoubtTests/DiffTests.swift +++ b/prototype/DoubtTests/DiffTests.swift @@ -50,7 +50,7 @@ final class DiffTests: XCTestCase { private func equal(a: DiffTests.Diff, _ b: DiffTests.Diff) -> Bool { - return Free.equals(pure: Patch.equals(Cofree.equals(annotation: ==, leaf: ==)), leaf: ==)(a, b) + return Free.equals(pure: Patch.equals(Cofree.equals(annotation: ==, leaf: ==)), leaf: ==, annotation: const(true))(a, b) } diff --git a/prototype/DoubtTests/InterpreterTests.swift b/prototype/DoubtTests/InterpreterTests.swift index 3cc40ad3b..4daaa9084 100644 --- a/prototype/DoubtTests/InterpreterTests.swift +++ b/prototype/DoubtTests/InterpreterTests.swift @@ -1,7 +1,9 @@ final class InterpreterTests: XCTestCase { func testRestrictsComparisons() { let comparable: (Term, Term) -> Bool = { $0.extract == 0 && $1.extract == 0 } - assert(Interpreter(equal: ==, comparable: comparable, cost: const(1)).run(a, b), ==, restricted) + let i = Interpreter(equal: ==, comparable: comparable, cost: const(1)) + let d = i.run(a, b) + assert(d, ==, restricted) } func testComparisonsOfDisjointlyCategorizedTermsAreRestricted() { @@ -26,17 +28,17 @@ private typealias Diff = Free> private let a = Term(0, [ Term(1, .Leaf("a")), Term(2, .Leaf("b")), Term(3, .Leaf("c")) ]) private let b = Term(0, [ Term(1, .Leaf("c")), Term(2, .Leaf("b")), Term(3, .Leaf("a")) ]) -private let restricted = Diff.Roll([ +private let restricted = Diff.Roll((0, 0), [ .Pure(.Insert(Term(1, .Leaf("c")))), .Pure(.Delete(Term(1, .Leaf("a")))), - Diff(Term(2, .Leaf("b"))), + .Roll((2, 2), .Leaf("b")), .Pure(.Insert(Term(3, .Leaf("a")))), .Pure(.Delete(Term(3, .Leaf("c")))), ]) -private let unrestricted = Diff.Roll([ +private let unrestricted = Diff.Roll((0, 0), [ .Pure(.Replace(Term(1, .Leaf("a")), Term(1, .Leaf("c")))), - Diff(Term(2, .Leaf("b"))), + .Roll((2, 2), .Leaf("b")), .Pure(.Replace(Term(3, .Leaf("c")), Term(3, .Leaf("a")))), ]) diff --git a/prototype/DoubtTests/SESTests.swift b/prototype/DoubtTests/SESTests.swift index db4780999..0a77efb42 100644 --- a/prototype/DoubtTests/SESTests.swift +++ b/prototype/DoubtTests/SESTests.swift @@ -12,31 +12,31 @@ final class SESTests: XCTestCase { } func testSESCanInsertAtHead() { - assert(SES([ a, b, c ], [ d, a, b, c ]), ==, [ .Insert(d), Diff(a), Diff(b), Diff(c) ]) + assert(SES([ a, b, c ], [ d, a, b, c ]), ==, [ .Insert(d), Copy(a), Copy(b), Copy(c) ]) } func testSESCanDeleteAtHead() { - assert(SES([ d, a, b, c ], [ a, b, c ]), ==, [ .Delete(d), Diff(a), Diff(b), Diff(c) ]) + assert(SES([ d, a, b, c ], [ a, b, c ]), ==, [ .Delete(d), Copy(a), Copy(b), Copy(c) ]) } func testSESCanInsertInMiddle() { - assert(SES([ a, b, c ], [ a, d, b, c ]), ==, [ Diff(a), .Insert(d), Diff(b), Diff(c) ]) + assert(SES([ a, b, c ], [ a, d, b, c ]), ==, [ Copy(a), .Insert(d), Copy(b), Copy(c) ]) } func testSESCanDeleteInMiddle() { - assert(SES([ a, d, b, c ], [ a, b, c ]), ==, [ Diff(a), .Delete(d), Diff(b), Diff(c) ]) + assert(SES([ a, d, b, c ], [ a, b, c ]), ==, [ Copy(a), .Delete(d), Copy(b), Copy(c) ]) } func testInsertsAtEnd() { - assert(SES([ a, b, c ], [ a, b, c, d ]), ==, [ Diff(a), Diff(b), Diff(c), .Insert(d) ]) + assert(SES([ a, b, c ], [ a, b, c, d ]), ==, [ Copy(a), Copy(b), Copy(c), .Insert(d) ]) } func testDeletesAtEnd() { - assert(SES([ a, b, c, d ], [ a, b, c ]), ==, [ Diff(a), Diff(b), Diff(c), .Delete(d) ]) + assert(SES([ a, b, c, d ], [ a, b, c ]), ==, [ Copy(a), Copy(b), Copy(c), .Delete(d) ]) } func testSESOfLongerSequences() { - assert(SES([ a, b, c, a, b, b, a ], [ c, b, a, b, a, c ]), ==, [ .Insert(c), .Delete(a), Diff(b), .Delete(c), Diff(a), .Delete(b), Diff(b), Diff(a), .Insert(c) ]) + assert(SES([ a, b, c, a, b, b, a ], [ c, b, a, b, a, c ]), ==, [ .Insert(c), .Delete(a), Copy(b), .Delete(c), Copy(a), .Delete(b), Copy(b), Copy(a), .Insert(c) ]) } } @@ -53,11 +53,11 @@ private let c = Term((), .Leaf("c")) private let d = Term((), .Leaf("d")) private func SES(a: [Term], _ b: [Term]) -> [Diff] { - return SES(a, b, cost: const(1)) { Cofree.equals(annotation: const(true), leaf: ==)($0, $1) ? Diff($1) : nil } + return SES(a, b, cost: const(1)) { Cofree.equals(annotation: const(true), leaf: ==)($0, $1) ? Copy($1) : nil } } private func == (a: [Diff], b: [Diff]) -> Bool { - return a.count == b.count && zip(a, b).lazy.map(Diff.equals(pure: Patch.equals(Cofree.equals(annotation: const(true), leaf: ==)), leaf: ==)).reduce(true) { $0 && $1 } + return a.count == b.count && zip(a, b).lazy.map(Diff.equals(pure: Patch.equals(Cofree.equals(annotation: const(true), leaf: ==)), leaf: ==, annotation: const(true))).reduce(true) { $0 && $1 } } From 5f5a4739bf9ef612ff25d2d903576b0a10511a02 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:56:03 -0400 Subject: [PATCH 52/55] Use the Introduce shorthand. --- prototype/Doubt/Interpreter.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prototype/Doubt/Interpreter.swift b/prototype/Doubt/Interpreter.swift index e4627df63..07e4f9285 100644 --- a/prototype/Doubt/Interpreter.swift +++ b/prototype/Doubt/Interpreter.swift @@ -51,9 +51,9 @@ public struct Interpreter { case let (.Leaf, .Leaf(leaf)) where equal(a, b): return .Roll(annotations, .Leaf(leaf)) case let (.Keyed(a), .Keyed(b)): - algorithm = .Roll(.ByKey(a, b, Syntax.Keyed >>> curry(Diff.Roll)(annotations) >>> Algorithm.Pure)) + algorithm = .Roll(.ByKey(a, b, Syntax.Keyed >>> Diff.Introduce(annotations) >>> Algorithm.Pure)) case let (.Indexed(a), .Indexed(b)): - algorithm = .Roll(.ByIndex(a, b, Syntax.Indexed >>> curry(Diff.Roll)(annotations) >>> Algorithm.Pure)) + algorithm = .Roll(.ByIndex(a, b, Syntax.Indexed >>> Diff.Introduce(annotations) >>> Algorithm.Pure)) default: algorithm = .Roll(.Recursive(a, b, Algorithm.Pure)) } From bfb9095d21ac6766e52ec607d16b814ac5f1799d Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 18:57:07 -0400 Subject: [PATCH 53/55] =?UTF-8?q?Don=E2=80=99t=20output=20Diff.Roll=20anno?= =?UTF-8?q?tations.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/doubt-json/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prototype/doubt-json/main.swift b/prototype/doubt-json/main.swift index 4b18a08a5..4f37b51d5 100644 --- a/prototype/doubt-json/main.swift +++ b/prototype/doubt-json/main.swift @@ -55,7 +55,7 @@ func diffAndSerialize(a aString: String, b bString: String, to: String) throws { [ "a": .String(aString), "b": .String(bString), - "diff": diff.JSON(pure: { $0.JSON { $0.JSON(annotation: range, leaf: { $0.JSON }) } }, leaf: { $0.JSON }), + "diff": diff.JSON(pure: { $0.JSON { $0.JSON(annotation: range, leaf: { $0.JSON }) } }, leaf: { $0.JSON }, annotation: const(nil)), ] } From 6c99f37e7af92a278f27272576f99fee9614998c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 19:04:21 -0400 Subject: [PATCH 54/55] Encode ranges in Diff.Roll. --- prototype/doubt-json/main.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/prototype/doubt-json/main.swift b/prototype/doubt-json/main.swift index 4f37b51d5..b62aac33e 100644 --- a/prototype/doubt-json/main.swift +++ b/prototype/doubt-json/main.swift @@ -55,7 +55,12 @@ func diffAndSerialize(a aString: String, b bString: String, to: String) throws { [ "a": .String(aString), "b": .String(bString), - "diff": diff.JSON(pure: { $0.JSON { $0.JSON(annotation: range, leaf: { $0.JSON }) } }, leaf: { $0.JSON }, annotation: const(nil)), + "diff": diff.JSON(pure: { $0.JSON { $0.JSON(annotation: range, leaf: { $0.JSON }) } }, leaf: { $0.JSON }, annotation: { + [ + "a": range($0), + "b": range($1), + ] + }), ] } From 2a50752f241b79668f6d53aa68264916354ca31c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 22 Oct 2015 19:04:38 -0400 Subject: [PATCH 55/55] =?UTF-8?q?Don=E2=80=99t=20decode=20back=20into=20an?= =?UTF-8?q?=20unused=20string,=20that=E2=80=99s=20dumb.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prototype/doubt-json/main.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/prototype/doubt-json/main.swift b/prototype/doubt-json/main.swift index b62aac33e..b943b3dc8 100644 --- a/prototype/doubt-json/main.swift +++ b/prototype/doubt-json/main.swift @@ -69,10 +69,6 @@ func diffAndSerialize(a aString: String, b bString: String, to: String) throws { } try data.writeToFile(to, options: .DataWritingAtomic) - - return benchmark("decoding data into string") { - NSString(data: data, encoding: NSUTF8StringEncoding) as String? - } } let readFile = { (path: String) -> String? in