1
1
mirror of https://github.com/github/semantic.git synced 2024-11-25 11:04:00 +03:00
semantic/prototype/Doubt/Cofree.swift

145 lines
4.2 KiB
Swift
Raw Normal View History

2015-10-14 17:19:02 +03:00
/// The cofree comonad over `Syntax`.
///
/// This is free in the sense of unconstrained rather than zero-cost; its 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 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.
2015-10-14 16:43:51 +03:00
public enum Cofree<A, B> {
2015-10-16 23:40:47 +03:00
indirect case Unroll(B, Syntax<Cofree, A>)
2015-10-14 16:59:04 +03:00
public var unwrap: Syntax<Cofree, A> {
switch self {
case let .Unroll(_, rest):
2015-10-16 23:40:47 +03:00
return rest
2015-10-14 16:59:04 +03:00
}
}
2015-10-14 17:39:01 +03:00
2015-10-16 23:40:47 +03:00
public init(_ annotation: B, _ syntax: Syntax<Cofree, A>) {
self = .Unroll(annotation, syntax)
}
2015-10-14 17:44:53 +03:00
/// Constructs a cofree by coiteration.
///
/// 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 syntaxs 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`, its unsurprising that we have a similar guarantee: coiteration is linear in the size of the constructed tree.
2015-10-14 17:39:01 +03:00
public static func coiterate(annotate: B -> Syntax<B, A>)(_ seed: B) -> Cofree {
2015-10-16 23:40:47 +03:00
return .Unroll(seed, annotate(seed).map(coiterate(annotate)))
2015-10-14 17:39:01 +03:00
}
2015-10-14 16:34:18 +03:00
}
2015-10-14 16:43:23 +03:00
// MARK: - CustomDebugStringConvertible
extension Cofree: CustomDebugStringConvertible {
public var debugDescription: String {
return "(\(String(reflecting: extract)), \(String(reflecting: unwrap)))"
}
}
2015-10-14 17:09:38 +03:00
// MARK: - Functor
extension Cofree {
2015-10-14 17:48:42 +03:00
public func map<Other>(transform: B -> Other) -> Cofree<A, Other> {
2015-10-16 23:40:47 +03:00
return .Unroll(transform(extract), unwrap.map { $0.map(transform) })
2015-10-14 17:09:38 +03:00
}
}
2015-10-14 16:43:23 +03:00
// MARK: - Comonad
extension Cofree {
2015-10-14 17:19:38 +03:00
/// Returns the value annotating the syntax tree at this node.
2015-10-14 17:01:19 +03:00
public var extract: B {
2015-10-14 16:43:23 +03:00
switch self {
2015-10-14 16:43:51 +03:00
case let .Unroll(b, _):
2015-10-14 16:43:23 +03:00
return b
}
}
2015-10-14 17:00:16 +03:00
2015-10-14 17:22:04 +03:00
/// Returns a new `Cofree` by recursively applying `transform` to each node, producing the annotations for the copy.
2015-10-14 17:48:42 +03:00
public func extend<Other>(transform: Cofree -> Other) -> Cofree<A, Other> {
2015-10-16 23:40:47 +03:00
return .Unroll(transform(self), unwrap.map { $0.extend(transform) })
2015-10-14 17:00:16 +03:00
}
2015-10-14 17:03:42 +03:00
2015-10-14 17:23:43 +03:00
/// Returns a new `Cofree` constructed by recursively annotating each subtree with itself.
2015-10-14 17:03:42 +03:00
public var duplicate: Cofree<A, Cofree<A, B>> {
2015-10-14 17:24:16 +03:00
return extend(id)
2015-10-14 17:03:42 +03:00
}
2015-10-14 16:43:23 +03:00
}
2015-10-14 17:24:16 +03:00
2015-10-14 17:52:27 +03:00
// MARK: - Equality
extension Cofree {
public static func equals(annotation annotation: (B, B) -> 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 == <A: Equatable, B: Equatable> (left: Cofree<A, B>, right: Cofree<A, B>) -> Bool {
return Cofree.equals(annotation: ==, leaf: ==)(left, right)
}
2015-10-14 17:52:27 +03:00
// MARK: - JSON
extension Cofree {
public func JSON(annotation annotation: B -> Doubt.JSON, leaf: A -> Doubt.JSON) -> Doubt.JSON {
return [
"extract": annotation(extract),
"unwrap": unwrap.JSON(ifLeaf: leaf, ifRecur: { $0.JSON(annotation: annotation, leaf: leaf) })
]
}
}
extension Cofree where A: CustomJSONConvertible, B: CustomJSONConvertible {
public var JSON: Doubt.JSON {
return JSON(annotation: { $0.JSON }, leaf: { $0.JSON })
}
}
// MARK: - Categorizable
extension Cofree where B: Categorizable {
var categories: Set<B.Category> {
return extract.categories
}
}
2015-10-16 16:20:47 +03:00
// MARK: - CofreeType
2015-10-15 22:31:51 +03:00
public protocol CofreeType {
2015-10-15 22:31:51 +03:00
typealias Annotation
typealias Leaf
2015-10-15 22:31:51 +03:00
var extract: Annotation { get }
var unwrap: Syntax<Self, Leaf> { get }
2015-10-15 22:31:51 +03:00
}
2015-10-15 22:42:39 +03:00
extension Cofree: CofreeType {}
extension CofreeType where Self.Annotation == Range<String.Index> {
public func JSON(source: String) -> Doubt.JSON {
return unwrap.JSON(
ifLeaf: { _ in .String(source[extract]) },
ifRecur: {
[
"range": [
"offset": .Number(Double(source.startIndex.distanceTo($0.extract.startIndex))),
"length": .Number(Double($0.extract.count)),
],
"unwrap": $0.JSON(source)
]
})
}
}
2015-10-15 22:31:51 +03:00
2015-10-14 17:24:16 +03:00
import Prelude