diff --git a/prototype/Doubt.xcodeproj/project.pbxproj b/prototype/Doubt.xcodeproj/project.pbxproj index 745a44b25..ec0ce834b 100644 --- a/prototype/Doubt.xcodeproj/project.pbxproj +++ b/prototype/Doubt.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ D42F09791BCCC5DC00B95610 /* Prelude.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09691BCCC41600B95610 /* Prelude.framework */; }; D42F097A1BCCC5DC00B95610 /* Stream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F096A1BCCC41600B95610 /* Stream.framework */; }; D42F097C1BCE914A00B95610 /* Cofree.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42F097B1BCE914A00B95610 /* Cofree.swift */; settings = {ASSET_TAGS = (); }; }; + D42F097E1BCEAEDA00B95610 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42F097D1BCEAEDA00B95610 /* Operation.swift */; settings = {ASSET_TAGS = (); }; }; + D42F09801BCECB7900B95610 /* TermType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42F097F1BCECB7900B95610 /* TermType.swift */; settings = {ASSET_TAGS = (); }; }; D432D4711BA9AC0B00F3FABC /* SESTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D432D4701BA9AC0B00F3FABC /* SESTests.swift */; }; D4413FEF1BB06D4C00E3C3C1 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4413FEE1BB06D4C00E3C3C1 /* Dictionary.swift */; }; D4413FF11BB08FDC00E3C3C1 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4413FF01BB08FDC00E3C3C1 /* JSON.swift */; }; @@ -64,6 +66,8 @@ D42F09691BCCC41600B95610 /* Prelude.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Prelude.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D42F096A1BCCC41600B95610 /* Stream.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Stream.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D42F097B1BCE914A00B95610 /* Cofree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cofree.swift; sourceTree = ""; }; + D42F097D1BCEAEDA00B95610 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = ""; }; + D42F097F1BCECB7900B95610 /* TermType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermType.swift; sourceTree = ""; }; D432D4701BA9AC0B00F3FABC /* SESTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SESTests.swift; sourceTree = ""; }; D435B7521BB31BBC000902F6 /* BoundsCheckedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoundsCheckedArray.swift; sourceTree = ""; }; D4413FEE1BB06D4C00E3C3C1 /* Dictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; @@ -172,8 +176,10 @@ D42F097B1BCE914A00B95610 /* Cofree.swift */, D49FCBC51BBF214300C5E9C3 /* Patch.swift */, D49FCBC71BBF2C4300C5E9C3 /* Algorithm.swift */, + D42F097D1BCEAEDA00B95610 /* Operation.swift */, D4DF96EC1BC46B630040F41F /* SES.swift */, D435B7521BB31BBC000902F6 /* BoundsCheckedArray.swift */, + D42F097F1BCECB7900B95610 /* TermType.swift */, D4AAE5001B5AE22E004E581F /* Supporting Files */, ); path = Doubt; @@ -350,8 +356,10 @@ D4AAE5471B5AE2D0004E581F /* Optional.swift in Sources */, D4413FEF1BB06D4C00E3C3C1 /* Dictionary.swift in Sources */, D4D7F3171BBB22E500AAB0C0 /* Hash.swift in Sources */, + D42F097E1BCEAEDA00B95610 /* Operation.swift in Sources */, D45A36C91BBC667D00BE3DDE /* Categorizable.swift in Sources */, D4DF96ED1BC46B630040F41F /* SES.swift in Sources */, + D42F09801BCECB7900B95610 /* TermType.swift in Sources */, D4AAE5401B5AE2D0004E581F /* RangeReplaceableCollectionType.swift in Sources */, D49FCBC21BBEF2C600C5E9C3 /* Fix.swift in Sources */, D49FCBC41BBEF98E00C5E9C3 /* Free.swift in Sources */, diff --git a/prototype/Doubt/Algorithm.swift b/prototype/Doubt/Algorithm.swift index ea53d1dcb..6df0ebf0c 100644 --- a/prototype/Doubt/Algorithm.swift +++ b/prototype/Doubt/Algorithm.swift @@ -1,49 +1,14 @@ -/// An operation of diffing over terms or collections of terms. -public enum Operation { - /// The type of `Term`s over which `Operation`s operate. - public typealias Term = Fix - - /// The type of `Diff`s which `Operation`s produce. - public typealias Diff = Free> - - /// Indicates that diffing should compare the enclosed `Term`s. - /// - /// When run, the enclosed function will be applied to the resulting `Diff`. - case Recursive(Term, Term, Diff -> Recur) - - /// Represents a diff to be performed on a collection of terms identified by keys. - case ByKey([String:Term], [String:Term], [String:Diff] -> Recur) - - /// Represents a diff to be performed over an array of terms by index. - case ByIndex([Term], [Term], [Diff] -> Recur) - - - // MARK: Functor - - public func map(transform: Recur -> Other) -> Operation { - switch self { - case let .Recursive(a, b, f): - return .Recursive(a, b, f >>> transform) - case let .ByKey(a, b, f): - return .ByKey(a, b, f >>> transform) - case let .ByIndex(a, b, f): - return .ByIndex(a, b, f >>> transform) - } - } -} - - /// The free monad over `Operation`, implementing the language of diffing. /// /// 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 `B` 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 { - /// The type of `Term`s over which `Algorithm`s operate. - public typealias Term = Operation.Term +public enum Algorithm { + /// The type of `Patch`es produced by `Algorithm`s. + public typealias Patch = Doubt.Patch /// The type of `Diff`s which `Algorithm`s produce. - public typealias Diff = Operation.Diff + public typealias Diff = Free /// The injection of a value of type `B` into an `Operation`. /// @@ -51,9 +16,9 @@ public enum Algorithm { case Pure(B) /// A recursive instantiation of `Operation`, unrolling another iteration of the recursive type. - case Roll(Operation) + indirect case Roll(Operation) - public func analysis(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Operation -> C) -> C { + public func analysis(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Operation -> C) -> C { switch self { case let .Pure(b): return ifPure(b) @@ -65,22 +30,22 @@ public enum Algorithm { // MARK: Functor - public func map(transform: B -> Other) -> Algorithm { - return analysis(ifPure: transform >>> Algorithm.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) }) + public func map(transform: B -> Other) -> Algorithm { + return analysis(ifPure: transform >>> Algorithm.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) }) } // MARK: Monad - public func flatMap(transform: B -> Algorithm) -> Algorithm { + public func flatMap(transform: B -> Algorithm) -> Algorithm { return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) }) } /// Evaluates the encoded algorithm, returning its result. - public func evaluate(equals: (A, A) -> Bool, recur: (Term, Term) -> Diff?) -> B { + public func evaluate(equals: (Term, Term) -> Bool, recur: (Term, Term) -> Diff?) -> B { let recur = { - Term.equals(equals)($0, $1) + equals($0, $1) ? Diff($1) : recur($0, $1) } @@ -95,7 +60,7 @@ public enum Algorithm { // Recur structurally into both terms, if compatible, patching paired sub-terms. This is akin to the shape of unification, except that it computes a patched tree instead of a substitution. It’s also a little like a structural zip on the pair of terms. // // At the moment, there are no restrictions on whether terms are compatible. - if Term.equals(equals)(a, b) { return f(Diff(b)).evaluate(equals, recur: recur) } + if equals(a, b) { return f(Diff(b)).evaluate(equals, recur: recur) } switch (a.out, b.out) { case let (.Indexed(a), .Indexed(b)) where a.count == b.count: @@ -117,19 +82,19 @@ public enum Algorithm { return f(Dictionary(elements: deleted + inserted + patched)).evaluate(equals, recur: recur) case let .Roll(.ByIndex(a, b, f)): - return f(SES(a, b, equals: equals, recur: recur)).evaluate(equals, recur: recur) + return f(SES(a, b, recur: recur)).evaluate(equals, recur: recur) } } } -extension Algorithm where A: Equatable { +extension Algorithm where Term: Equatable { public func evaluate(recur: (Term, Term) -> Diff?) -> B { return evaluate(==, recur: recur) } } -extension Algorithm where B: FreeConvertible, B.RollType == A, B.PureType == Patch { - /// `Algorithm`s can be constructed from a pair of `Term`s using `ByKey` when `Keyed`, `ByIndex` when `Indexed`, and `Recursive` otherwise. +extension Algorithm where B: FreeConvertible, B.RollType == Term.LeafType, B.PureType == Algorithm.Patch { + /// `Algorithm`s can be constructed from a pair of `Term`s using `ByKey` when `Keyed`, `ByIndex` when `Indexed`, and `Recursive` otherwise. public init(_ a: Term, _ b: Term) { switch (a.out, b.out) { case let (.Keyed(a), .Keyed(b)): @@ -141,19 +106,19 @@ extension Algorithm where B: FreeConvertible, B.RollType == A, B.PureType == Pat } } - public func evaluate(equals: (A, A) -> Bool) -> B { + public func evaluate(equals: (Term, Term) -> Bool) -> B { return evaluate(equals, recur: { Algorithm($0, $1).evaluate(equals).free }) } } -extension Algorithm where A: Equatable, B: FreeConvertible, B.RollType == A, B.PureType == Patch { +extension Algorithm where Term: Equatable, B: FreeConvertible, B.RollType == Term.LeafType, B.PureType == Algorithm.Patch { public func evaluate() -> B { return evaluate(==) } } -extension Algorithm where A: Categorizable, B: FreeConvertible, B.RollType == A, B.PureType == Patch { - public func evaluate(equals: (A, A) -> Bool) -> B { +extension Algorithm where Term: Categorizable, B: FreeConvertible, B.RollType == Term.LeafType, B.PureType == Algorithm.Patch { + public func evaluate(equals: (Term, Term) -> Bool) -> B { return evaluate(equals, recur: { ($0.categories.isEmpty || $1.categories.isEmpty) && !$0.categories.intersect($1.categories).isEmpty ? Algorithm($0, $1).evaluate(equals).free @@ -163,7 +128,7 @@ extension Algorithm where A: Categorizable, B: FreeConvertible, B.RollType == A, } -extension Algorithm where A: Categorizable, A: Equatable, B: FreeConvertible, B.RollType == A, B.PureType == Patch { +extension Algorithm where Term.LeafType: Categorizable, Term: Equatable, B: FreeConvertible, B.RollType == Term.LeafType, B.PureType == Algorithm.Patch { public func evaluate() -> B { return evaluate(==) } diff --git a/prototype/Doubt/Cofree.swift b/prototype/Doubt/Cofree.swift index d40143f1d..4986a18a9 100644 --- a/prototype/Doubt/Cofree.swift +++ b/prototype/Doubt/Cofree.swift @@ -1,5 +1,3 @@ -// Copyright © 2015 GitHub. All rights reserved. - /// 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. @@ -16,6 +14,11 @@ public enum Cofree { } + public init(_ annotation: B, @autoclosure(escaping) _ syntax: () -> Syntax) { + self = .Unroll(annotation, syntax) + } + + /// Recursively copies a `Fix` into a `Cofree` with a function assigning `B` for every `Fix`. public init(_ fix: Fix, _ annotate: Fix -> B) { self = Cofree>.coiterate { $0.out } (fix).map(annotate) diff --git a/prototype/Doubt/Free.swift b/prototype/Doubt/Free.swift index 2f19d3e3a..d20f1e713 100644 --- a/prototype/Doubt/Free.swift +++ b/prototype/Doubt/Free.swift @@ -18,6 +18,10 @@ public enum Free: CustomDebugStringConvertible, CustomDocConvertible, Synt self = .Roll(fix.out.map(Free.init)) } + public init(_ term: Term) { + self = .Roll(term.out.map(Free.init)) + } + public func analysis(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Syntax -> C) -> C { switch self { @@ -107,7 +111,7 @@ public enum Free: CustomDebugStringConvertible, CustomDocConvertible, Synt } -extension Free where B: PatchConvertible, B.Info == A { +extension Free where B: PatchConvertible, B.Element == Fix { public typealias Term = Fix private func discardNullTerms(syntax: Syntax) -> Term? { @@ -190,9 +194,9 @@ extension Free where A: CustomJSONConvertible { } } -extension Free where A: CustomJSONConvertible, B: PatchConvertible, B.Info == A { +extension Free where A: CustomJSONConvertible, B: PatchConvertible, B.Element == Fix { public var JSON: Doubt.JSON { - return JSON { $0.patch.JSON } + return JSON { $0.patch.JSON { $0.JSON } } } } diff --git a/prototype/Doubt/JSON.swift b/prototype/Doubt/JSON.swift index 109b9cc14..53ff351d4 100644 --- a/prototype/Doubt/JSON.swift +++ b/prototype/Doubt/JSON.swift @@ -180,6 +180,12 @@ extension String: CustomJSONConvertible { } } +extension Int: CustomJSONConvertible { + public var JSON: Doubt.JSON { + return .Number(Double(self)) + } +} + /// A type which can be converted to and from JSON. public protocol JSONConvertible: CustomJSONConvertible { diff --git a/prototype/Doubt/Operation.swift b/prototype/Doubt/Operation.swift new file mode 100644 index 000000000..b29d5fbb3 --- /dev/null +++ b/prototype/Doubt/Operation.swift @@ -0,0 +1,32 @@ +/// An operation of diffing over terms or collections of terms. +public enum Operation { + /// Indicates that diffing should compare the enclosed `Term`s. + /// + /// When run, the enclosed function will be applied to the resulting `Diff`. + case Recursive(Term, Term, Diff -> Recur) + + /// Represents a diff to be performed on a collection of terms identified by keys. + case ByKey([String:Term], [String:Term], [String:Diff] -> Recur) + + /// Represents a diff to be performed over an array of terms by index. + case ByIndex([Term], [Term], [Diff] -> Recur) +} + + +// MARK: - Functor + +extension Operation { + public func map(transform: Recur -> Other) -> Operation { + switch self { + case let .Recursive(a, b, f): + return .Recursive(a, b, f >>> transform) + case let .ByKey(a, b, f): + return .ByKey(a, b, f >>> transform) + case let .ByIndex(a, b, f): + return .ByIndex(a, b, f >>> transform) + } + } +} + + +import Prelude diff --git a/prototype/Doubt/Patch.swift b/prototype/Doubt/Patch.swift index 7c87903f6..057694d1b 100644 --- a/prototype/Doubt/Patch.swift +++ b/prototype/Doubt/Patch.swift @@ -1,10 +1,10 @@ /// A patch to some part of a `Syntax` tree. public enum Patch: CustomDebugStringConvertible, CustomDocConvertible { - case Replace(Fix, Fix) - case Insert(Fix) - case Delete(Fix) + case Replace(A, A) + case Insert(A) + case Delete(A) - public var state: (before: Fix?, after: Fix?) { + public var state: (before: A?, after: A?) { switch self { case let .Replace(a, b): return (a, b) @@ -56,8 +56,8 @@ public enum Patch: CustomDebugStringConvertible, CustomDocConvertible { // MARK: CustomDocConvertible public var doc: Doc { - return (state.before?.doc.bracket("{-", "-}") ?? .Empty) - <> (state.after?.doc.bracket("{+", "+}") ?? .Empty) + return (state.before.map(Doc.init)?.bracket("{-", "-}") ?? .Empty) + <> (state.after.map(Doc.init)?.bracket("{+", "+}") ?? .Empty) } } @@ -66,8 +66,8 @@ public enum Patch: CustomDebugStringConvertible, CustomDocConvertible { extension Patch { public static func equals(param: (A, A) -> Bool)(_ left: Patch, _ right: Patch) -> Bool { - return Optional.equals(Fix.equals(param))(left.state.before, right.state.before) - && Optional.equals(Fix.equals(param))(left.state.after, right.state.after) + return Optional.equals(param)(left.state.before, right.state.before) + && Optional.equals(param)(left.state.after, right.state.after) } } @@ -77,8 +77,8 @@ extension Patch { extension Patch { public func hash(param: A -> Hash) -> Hash { return Hash.Ordered([ - state.before.map { $0.hash(param) } ?? Hash.Empty, - state.after.map { $0.hash(param) } ?? Hash.Empty + state.before.map(param) ?? Hash.Empty, + state.after.map(param) ?? Hash.Empty ]) } } @@ -92,17 +92,17 @@ extension Patch { case let .Replace(a, b): return [ "replace": [ - "before": a.JSON(ifLeaf), - "after": b.JSON(ifLeaf), + "before": ifLeaf(a), + "after": ifLeaf(b), ] ] case let .Insert(b): return [ - "insert": b.JSON(ifLeaf), + "insert": ifLeaf(b), ] case let .Delete(a): return [ - "delete": a.JSON(ifLeaf) + "delete": ifLeaf(a) ] } } @@ -111,17 +111,17 @@ extension Patch { extension Patch where A: CustomJSONConvertible { public var JSON: Doubt.JSON { - return self.JSON { $0.JSON } + return JSON { $0.JSON } } } -/// A hack to enable constrained extensions on `Free>`. +/// A hack to enable constrained extensions on `Free>`. public protocol PatchConvertible { - typealias Info + typealias Element - init(patch: Patch) - var patch: Patch { get } + init(patch: Patch) + var patch: Patch { get } } extension Patch: PatchConvertible { diff --git a/prototype/Doubt/SES.swift b/prototype/Doubt/SES.swift index 526fc4aa1..847b84434 100644 --- a/prototype/Doubt/SES.swift +++ b/prototype/Doubt/SES.swift @@ -1,9 +1,8 @@ -/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free>`) for two arrays of terms (`Fix`) 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: [Fix], _ b: [Fix], equals: (A, A) -> Bool, recur: (Fix, Fix) -> Free>?) -> [Free>] { - typealias Term = Fix - typealias Diff = Free> +public func SES(a: [Term], _ b: [Term], recur: (Term, Term) -> Free>?) -> [Free>] { + typealias Diff = Free> if a.isEmpty { return b.map { Diff.Pure(Patch.Insert($0)) } } if b.isEmpty { return a.map { Diff.Pure(Patch.Delete($0)) } } @@ -46,12 +45,6 @@ public func SES(a: [Fix], _ b: [Fix], equals: (A, A) -> Bool, recur: (F let down = matrix[i, j + 1] let diagonal = matrix[i + 1, j + 1] - let recur = { - Term.equals(equals)($0, $1) - ? Diff($1) - : recur($0, $1) - } - if let right = right, down = down, diagonal = diagonal { let right = (right, Diff.Pure(Patch.Delete(a[i])), costOfStream(right)) let down = (down, Diff.Pure(Patch.Insert(b[j])), costOfStream(down)) diff --git a/prototype/Doubt/TermType.swift b/prototype/Doubt/TermType.swift new file mode 100644 index 000000000..1b02855c8 --- /dev/null +++ b/prototype/Doubt/TermType.swift @@ -0,0 +1,15 @@ +/// The type of terms. +public protocol TermType { + typealias LeafType + + var out: Syntax { get } +} + + +extension Fix: TermType {} + +extension Cofree: TermType { + public var out: Syntax { + return unwrap + } +} diff --git a/prototype/DoubtTests/SESTests.swift b/prototype/DoubtTests/SESTests.swift index af14860af..641c366b5 100644 --- a/prototype/DoubtTests/SESTests.swift +++ b/prototype/DoubtTests/SESTests.swift @@ -48,16 +48,16 @@ private func delete(term: Term) -> Diff { return Diff.Pure(.Delete(term)) } -private typealias Term = Fix -private typealias Diff = Free> +private typealias Term = Fix +private typealias Diff = Free> -private let a = Term.Leaf(.Literal("a", [])) -private let b = Term.Leaf(.Literal("b", [])) -private let c = Term.Leaf(.Literal("c", [])) -private let d = Term.Leaf(.Literal("d", [])) +private let a = Term.Leaf("a") +private let b = Term.Leaf("b") +private let c = Term.Leaf("c") +private let d = Term.Leaf("d") private func SES(a: [Term], _ b: [Term]) -> [Diff] { - return SES(a, b, equals: ==, recur: const(nil)) + return SES(a, b) { $0 == $1 ? Diff($1) : nil } } private func == (a: [Diff], b: [Diff]) -> Bool { diff --git a/prototype/doubt-json/Fixtures/a.json b/prototype/doubt-json/Fixtures/a.json index 460b5331d..136ab3fec 100644 --- a/prototype/doubt-json/Fixtures/a.json +++ b/prototype/doubt-json/Fixtures/a.json @@ -1,3 +1,478 @@ -{ - "foo": "bar" -} +[ + [{ + "literal": { + "source": "Algorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "Recursive", + "categories": [ + + ] + } + }, + [ + + ] + ], + [{ + "literal": { + "source": "ByKey", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "insert": [{ + "literal": { + "source": "ByIndex", + "categories": [ + + ] + } + }, + [ + + ] + ] + }, { + "literal": { + "source": "mark", + "categories": [ + + ] + } + }, + [{ + "literal": { + "source": "map(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ] + ] + ], { + "insert": [{ + "literal": { + "source": "FreeAlgorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "Pure", + "categories": [ + + ] + } + }, + [ + + ] + ], + [{ + "literal": { + "source": "Roll", + "categories": [ + + ] + } + }, + [ + + ] + ], + [{ + "literal": { + "source": "analysis(ifPure:ifRoll:)", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "literal": { + "source": "mark", + "categories": [ + + ] + } + }, + [{ + "literal": { + "source": "map(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "literal": { + "source": "mark", + "categories": [ + + ] + } + }, + [{ + "literal": { + "source": "flatMap(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ], + [{ + "literal": { + "source": "evaluate(_:recur:)", + "categories": [ + + ] + } + }, + [ + + ] + ] + ] + ] + }, { + "replace": { + "before": { + "literal": { + "source": "mark", + "categories": [ + + ] + } + }, + "after": [{ + "literal": { + "source": "FreeAlgorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "evaluate(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ] + ] + ] + } + }, { + "delete": [{ + "literal": { + "source": "Algorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "evaluate(_:)", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "copy(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ] + ] + ] + ] + ] + }, { + "delete": [{ + "literal": { + "source": "FreeAlgorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "Pure", + "categories": [ + + ] + } + }, + [ + + ] + ], + [{ + "literal": { + "source": "Roll", + "categories": [ + + ] + } + }, + [ + + ] + ], + [{ + "literal": { + "source": "analysis(ifPure:ifRoll:)", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "literal": { + "source": "mark", + "categories": [ + + ] + } + }, + [{ + "literal": { + "source": "map(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "literal": { + "source": "mark", + "categories": [ + + ] + } + }, + [{ + "literal": { + "source": "flatMap(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ], + [{ + "literal": { + "source": "evaluate(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ] + ] + ] + }, { + "delete": [{ + "literal": { + "source": "FreeAlgorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "evaluate()", + "categories": [ + + ] + } + }, + [ + + ] + ] + ] + ] + }, + [{ + "literal": { + "source": "FreeConvertible", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "init(free:)", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "literal": { + "source": "free", + "categories": [ + + ] + } + } + ] + ], + [{ + "literal": { + "source": "Free", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "init(free:)", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "literal": { + "source": "free", + "categories": [ + + ] + } + } + ] + ], + [{ + "literal": { + "source": "FreeAlgorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "init(_:_:)", + "categories": [ + + ] + } + }, + [ + + ] + ], { + "insert": [{ + "literal": { + "source": "evaluate(_:)", + "categories": [ + + ] + } + }, + [ + + ] + ] + } + ] + ], { + "insert": [{ + "literal": { + "source": "FreeAlgorithm", + "categories": [ + + ] + } + }, + [ + [{ + "literal": { + "source": "evaluate()", + "categories": [ + + ] + } + }, + [ + + ] + ] + ] + ] + } +] diff --git a/prototype/doubt-json/Fixtures/b.json b/prototype/doubt-json/Fixtures/b.json index d036eb6be..d72eceb71 100644 --- a/prototype/doubt-json/Fixtures/b.json +++ b/prototype/doubt-json/Fixtures/b.json @@ -1 +1,392 @@ -{"foo": "baz"} +[ + [{ + "source": "Algorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "Recursive", + "categories": [ + + ] + }, + [ + + ] + ], + [{ + "source": "ByKey", + "categories": [ + + ] + }, + [ + + ] + ], { + "insert": [{ + "source": "ByIndex", + "categories": [ + + ] + }, + [ + + ] + ] + }, { + "source": "mark", + "categories": [ + + ] + }, + [{ + "source": "map(_:)", + "categories": [ + + ] + }, + [ + + ] + ] + ] + ], { + "insert": [{ + "source": "FreeAlgorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "Pure", + "categories": [ + + ] + }, + [ + + ] + ], + [{ + "source": "Roll", + "categories": [ + + ] + }, + [ + + ] + ], + [{ + "source": "analysis(ifPure:ifRoll:)", + "categories": [ + + ] + }, + [ + + ] + ], { + "source": "mark", + "categories": [ + + ] + }, + [{ + "source": "map(_:)", + "categories": [ + + ] + }, + [ + + ] + ], { + "source": "mark", + "categories": [ + + ] + }, + [{ + "source": "flatMap(_:)", + "categories": [ + + ] + }, + [ + + ] + ], + [{ + "source": "evaluate(_:recur:)", + "categories": [ + + ] + }, + [ + + ] + ] + ] + ] + }, { + "replace": { + "before": { + "source": "mark", + "categories": [ + + ] + }, + "after": [{ + "source": "FreeAlgorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "evaluate(_:)", + "categories": [ + + ] + }, + [ + + ] + ] + ] + ] + } + }, { + "delete": [{ + "source": "Algorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "evaluate(_:)", + "categories": [ + + ] + }, + [ + [{ + "source": "copy(_:)", + "categories": [ + + ] + }, + [ + + ] + ] + ] + ] + ] + ] + }, { + "delete": [{ + "source": "FreeAlgorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "Pure", + "categories": [ + + ] + }, + [ + + ] + ], + [{ + "source": "Roll", + "categories": [ + + ] + }, + [ + + ] + ], + [{ + "source": "analysis(ifPure:ifRoll:)", + "categories": [ + + ] + }, + [ + + ] + ], { + "source": "mark", + "categories": [ + + ] + }, + [{ + "source": "map(_:)", + "categories": [ + + ] + }, + [ + + ] + ], { + "source": "mark", + "categories": [ + + ] + }, + [{ + "source": "flatMap(_:)", + "categories": [ + + ] + }, + [ + + ] + ], + [{ + "source": "evaluate(_:)", + "categories": [ + + ] + }, + [ + + ] + ] + ] + ] + }, { + "delete": [{ + "source": "FreeAlgorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "evaluate()", + "categories": [ + + ] + }, + [ + + ] + ] + ] + ] + }, + [{ + "source": "FreeConvertible", + "categories": [ + + ] + }, + [ + [{ + "source": "init(free:)", + "categories": [ + + ] + }, + [ + + ] + ], { + "source": "free", + "categories": [ + + ] + } + ] + ], + [{ + "source": "Free", + "categories": [ + + ] + }, + [ + [{ + "source": "init(free:)", + "categories": [ + + ] + }, + [ + + ] + ], { + "source": "free", + "categories": [ + + ] + } + ] + ], + [{ + "source": "FreeAlgorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "init(_:_:)", + "categories": [ + + ] + }, + [ + + ] + ], { + "insert": [{ + "source": "evaluate(_:)", + "categories": [ + + ] + }, + [ + + ] + ] + } + ] + ], { + "insert": [{ + "source": "FreeAlgorithm", + "categories": [ + + ] + }, + [ + [{ + "source": "evaluate()", + "categories": [ + + ] + }, + [ + + ] + ] + ] + ] + } +] diff --git a/prototype/doubt-json/main.swift b/prototype/doubt-json/main.swift index 87c750eed..506de5ceb 100644 --- a/prototype/doubt-json/main.swift +++ b/prototype/doubt-json/main.swift @@ -1,9 +1,9 @@ -import Doubt import Cocoa +import Doubt +import Prelude -typealias Term = Fix -typealias Diff = Free> - +typealias Term = Cofree +typealias Diff = Free> enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable { case Number(Double) @@ -68,27 +68,41 @@ extension JSON { } var term: Term { + func annotate(json: Syntax) -> Term { + return Cofree(size(json), json) + } + func size(syntax: Syntax) -> Int { + switch syntax { + case .Leaf: + return 1 + case let .Indexed(i): + return 1 + i.map { size($0.unwrap) }.reduce(0, combine: +) + case let .Keyed(i): + return 1 + i.values.map { size($0.unwrap) }.reduce(0, combine: +) + } + } + switch self { case let .Array(a): - return .Indexed(a.map { $0.term }) + return annotate(.Indexed(a.map { $0.term })) case let .Dictionary(d): - return .Keyed(Swift.Dictionary(elements: d.map { ($0, $1.term) })) + return annotate(.Keyed(Swift.Dictionary(elements: d.map { ($0, $1.term) }))) case let .Number(n): - return .Leaf(.Number(n)) + return annotate(.Leaf(.Number(n))) case let .Boolean(b): - return .Leaf(.Boolean(b)) + return annotate(.Leaf(.Boolean(b))) case let .String(s): - return .Leaf(.String(s)) + return annotate(.Leaf(.String(s))) case .Null: - return .Leaf(.Null) + return annotate(.Leaf(.Null)) } } } let arguments = BoundsCheckedArray(array: Process.arguments) if let a = arguments[1].flatMap(JSON.init), b = arguments[2].flatMap(JSON.init) { - let diff = Algorithm(a.term, b.term).evaluate() - if let JSON = NSString(data: diff.JSON.serialize(), encoding: NSUTF8StringEncoding) { + let diff = Algorithm(a.term, b.term).evaluate(Cofree.equals(annotation: const(true), leaf: ==)) + if let JSON = NSString(data: diff.JSON(ifPure: { $0.JSON { $0.JSON } }, ifLeaf: { $0.JSON }).serialize(), encoding: NSUTF8StringEncoding) { print(JSON) } }