mirror of
https://github.com/github/semantic.git
synced 2024-11-29 02:44:36 +03:00
Merge branch 'diff-cofree-values' into restrict-comparisons-by-category
Conflicts: prototype/Doubt/SES.swift prototype/DoubtTests/SESTests.swift
This commit is contained in:
commit
80bda88082
@ -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 = "<group>"; };
|
||||
D42F097D1BCEAEDA00B95610 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
||||
D42F097F1BCECB7900B95610 /* TermType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermType.swift; sourceTree = "<group>"; };
|
||||
D432D4701BA9AC0B00F3FABC /* SESTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SESTests.swift; sourceTree = "<group>"; };
|
||||
D435B7521BB31BBC000902F6 /* BoundsCheckedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoundsCheckedArray.swift; sourceTree = "<group>"; };
|
||||
D4413FEE1BB06D4C00E3C3C1 /* Dictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = "<group>"; };
|
||||
@ -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 */,
|
||||
|
@ -1,49 +1,14 @@
|
||||
/// An operation of diffing over terms or collections of terms.
|
||||
public enum Operation<Recur, A> {
|
||||
/// The type of `Term`s over which `Operation`s operate.
|
||||
public typealias Term = Fix<A>
|
||||
|
||||
/// The type of `Diff`s which `Operation`s produce.
|
||||
public typealias Diff = Free<A, Patch<A>>
|
||||
|
||||
/// 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<Other>(transform: Recur -> Other) -> Operation<Other, A> {
|
||||
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<A, B> {
|
||||
/// The type of `Term`s over which `Algorithm`s operate.
|
||||
public typealias Term = Operation<Algorithm, A>.Term
|
||||
public enum Algorithm<Term: TermType, B> {
|
||||
/// The type of `Patch`es produced by `Algorithm`s.
|
||||
public typealias Patch = Doubt.Patch<Term>
|
||||
|
||||
/// The type of `Diff`s which `Algorithm`s produce.
|
||||
public typealias Diff = Operation<Algorithm, A>.Diff
|
||||
public typealias Diff = Free<Term.LeafType, Patch>
|
||||
|
||||
/// The injection of a value of type `B` into an `Operation`.
|
||||
///
|
||||
@ -51,9 +16,9 @@ public enum Algorithm<A, B> {
|
||||
case Pure(B)
|
||||
|
||||
/// A recursive instantiation of `Operation`, unrolling another iteration of the recursive type.
|
||||
case Roll(Operation<Algorithm, A>)
|
||||
indirect case Roll(Operation<Algorithm, Term, Diff>)
|
||||
|
||||
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Operation<Algorithm, A> -> C) -> C {
|
||||
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Operation<Algorithm, Term, Diff> -> C) -> C {
|
||||
switch self {
|
||||
case let .Pure(b):
|
||||
return ifPure(b)
|
||||
@ -65,22 +30,22 @@ public enum Algorithm<A, B> {
|
||||
|
||||
// MARK: Functor
|
||||
|
||||
public func map<Other>(transform: B -> Other) -> Algorithm<A, Other> {
|
||||
return analysis(ifPure: transform >>> Algorithm<A, Other>.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) })
|
||||
public func map<Other>(transform: B -> Other) -> Algorithm<Term, Other> {
|
||||
return analysis(ifPure: transform >>> Algorithm<Term, Other>.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) })
|
||||
}
|
||||
|
||||
|
||||
// MARK: Monad
|
||||
|
||||
public func flatMap<C>(transform: B -> Algorithm<A, C>) -> Algorithm<A, C> {
|
||||
public func flatMap<C>(transform: B -> Algorithm<Term, C>) -> Algorithm<Term, C> {
|
||||
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<A, B> {
|
||||
// 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<A, B> {
|
||||
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<A> {
|
||||
/// `Algorithm<A, Diff>`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<Term, B>.Patch {
|
||||
/// `Algorithm<Term, Diff>`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<A> {
|
||||
extension Algorithm where Term: Equatable, B: FreeConvertible, B.RollType == Term.LeafType, B.PureType == Algorithm<Term, B>.Patch {
|
||||
public func evaluate() -> B {
|
||||
return evaluate(==)
|
||||
}
|
||||
}
|
||||
|
||||
extension Algorithm where A: Categorizable, B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
|
||||
public func evaluate(equals: (A, A) -> Bool) -> B {
|
||||
extension Algorithm where Term: Categorizable, B: FreeConvertible, B.RollType == Term.LeafType, B.PureType == Algorithm<Term, B>.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<A> {
|
||||
extension Algorithm where Term.LeafType: Categorizable, Term: Equatable, B: FreeConvertible, B.RollType == Term.LeafType, B.PureType == Algorithm<Term, B>.Patch {
|
||||
public func evaluate() -> B {
|
||||
return evaluate(==)
|
||||
}
|
||||
|
@ -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<A, B> {
|
||||
}
|
||||
|
||||
|
||||
public init(_ annotation: B, @autoclosure(escaping) _ syntax: () -> Syntax<Cofree, A>) {
|
||||
self = .Unroll(annotation, syntax)
|
||||
}
|
||||
|
||||
|
||||
/// Recursively copies a `Fix<A>` into a `Cofree<A, B>` with a function assigning `B` for every `Fix<A>`.
|
||||
public init(_ fix: Fix<A>, _ annotate: Fix<A> -> B) {
|
||||
self = Cofree<A, Fix<A>>.coiterate { $0.out } (fix).map(annotate)
|
||||
|
@ -18,6 +18,10 @@ public enum Free<A, B>: CustomDebugStringConvertible, CustomDocConvertible, Synt
|
||||
self = .Roll(fix.out.map(Free.init))
|
||||
}
|
||||
|
||||
public init<Term: TermType where Term.LeafType == A>(_ term: Term) {
|
||||
self = .Roll(term.out.map(Free.init))
|
||||
}
|
||||
|
||||
|
||||
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Syntax<Free, A> -> C) -> C {
|
||||
switch self {
|
||||
@ -107,7 +111,7 @@ public enum Free<A, B>: CustomDebugStringConvertible, CustomDocConvertible, Synt
|
||||
}
|
||||
|
||||
|
||||
extension Free where B: PatchConvertible, B.Info == A {
|
||||
extension Free where B: PatchConvertible, B.Element == Fix<A> {
|
||||
public typealias Term = Fix<A>
|
||||
|
||||
private func discardNullTerms(syntax: Syntax<Term?, A>) -> 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<A> {
|
||||
public var JSON: Doubt.JSON {
|
||||
return JSON { $0.patch.JSON }
|
||||
return JSON { $0.patch.JSON { $0.JSON } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
32
prototype/Doubt/Operation.swift
Normal file
32
prototype/Doubt/Operation.swift
Normal file
@ -0,0 +1,32 @@
|
||||
/// An operation of diffing over terms or collections of terms.
|
||||
public enum Operation<Recur, Term, Diff> {
|
||||
/// 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<Other>(transform: Recur -> Other) -> Operation<Other, Term, Diff> {
|
||||
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
|
@ -1,10 +1,10 @@
|
||||
/// A patch to some part of a `Syntax` tree.
|
||||
public enum Patch<A>: CustomDebugStringConvertible, CustomDocConvertible {
|
||||
case Replace(Fix<A>, Fix<A>)
|
||||
case Insert(Fix<A>)
|
||||
case Delete(Fix<A>)
|
||||
case Replace(A, A)
|
||||
case Insert(A)
|
||||
case Delete(A)
|
||||
|
||||
public var state: (before: Fix<A>?, after: Fix<A>?) {
|
||||
public var state: (before: A?, after: A?) {
|
||||
switch self {
|
||||
case let .Replace(a, b):
|
||||
return (a, b)
|
||||
@ -56,8 +56,8 @@ public enum Patch<A>: 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<A>: 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, Patch<A>>`.
|
||||
/// A hack to enable constrained extensions on `Free<A, Patch<Fix<A>>`.
|
||||
public protocol PatchConvertible {
|
||||
typealias Info
|
||||
typealias Element
|
||||
|
||||
init(patch: Patch<Info>)
|
||||
var patch: Patch<Info> { get }
|
||||
init(patch: Patch<Element>)
|
||||
var patch: Patch<Element> { get }
|
||||
}
|
||||
|
||||
extension Patch: PatchConvertible {
|
||||
|
@ -1,9 +1,8 @@
|
||||
/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free<A, Patch<A>>`) for two arrays of terms (`Fix<A>`) which would suffice to transform `a` into `b`.
|
||||
/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free<A, Patch<Term>>`) 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>(a: [Fix<A>], _ b: [Fix<A>], equals: (A, A) -> Bool, recur: (Fix<A>, Fix<A>) -> Free<A, Patch<A>>?) -> [Free<A, Patch<A>>] {
|
||||
typealias Term = Fix<A>
|
||||
typealias Diff = Free<A, Patch<A>>
|
||||
public func SES<Term, A>(a: [Term], _ b: [Term], recur: (Term, Term) -> Free<A, Patch<Term>>?) -> [Free<A, Patch<Term>>] {
|
||||
typealias Diff = Free<A, Patch<Term>>
|
||||
|
||||
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>(a: [Fix<A>], _ b: [Fix<A>], 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))
|
||||
|
15
prototype/Doubt/TermType.swift
Normal file
15
prototype/Doubt/TermType.swift
Normal file
@ -0,0 +1,15 @@
|
||||
/// The type of terms.
|
||||
public protocol TermType {
|
||||
typealias LeafType
|
||||
|
||||
var out: Syntax<Self, LeafType> { get }
|
||||
}
|
||||
|
||||
|
||||
extension Fix: TermType {}
|
||||
|
||||
extension Cofree: TermType {
|
||||
public var out: Syntax<Cofree, A> {
|
||||
return unwrap
|
||||
}
|
||||
}
|
@ -48,16 +48,16 @@ private func delete(term: Term) -> Diff {
|
||||
return Diff.Pure(.Delete(term))
|
||||
}
|
||||
|
||||
private typealias Term = Fix<Info>
|
||||
private typealias Diff = Free<Info, Patch<Info>>
|
||||
private typealias Term = Fix<String>
|
||||
private typealias Diff = Free<String, Patch<Term>>
|
||||
|
||||
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 {
|
||||
|
479
prototype/doubt-json/Fixtures/a.json
vendored
479
prototype/doubt-json/Fixtures/a.json
vendored
@ -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": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
[
|
||||
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
393
prototype/doubt-json/Fixtures/b.json
vendored
393
prototype/doubt-json/Fixtures/b.json
vendored
@ -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": [
|
||||
|
||||
]
|
||||
},
|
||||
[
|
||||
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Doubt
|
||||
import Cocoa
|
||||
import Doubt
|
||||
import Prelude
|
||||
|
||||
typealias Term = Fix<JSONLeaf>
|
||||
typealias Diff = Free<JSONLeaf, Patch<JSONLeaf>>
|
||||
|
||||
typealias Term = Cofree<JSONLeaf, Int>
|
||||
typealias Diff = Free<JSONLeaf, Patch<Term>>
|
||||
|
||||
enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable {
|
||||
case Number(Double)
|
||||
@ -68,27 +68,41 @@ extension JSON {
|
||||
}
|
||||
|
||||
var term: Term {
|
||||
func annotate(json: Syntax<Term, JSONLeaf>) -> Term {
|
||||
return Cofree(size(json), json)
|
||||
}
|
||||
func size(syntax: Syntax<Term, JSONLeaf>) -> 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<JSONLeaf, Diff>(a.term, b.term).evaluate()
|
||||
if let JSON = NSString(data: diff.JSON.serialize(), encoding: NSUTF8StringEncoding) {
|
||||
let diff = Algorithm<Term, Diff>(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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user