diff --git a/src/Rel.mo b/src/Rel.mo new file mode 100644 index 00000000..5482cb0f --- /dev/null +++ b/src/Rel.mo @@ -0,0 +1,176 @@ +import Trie "mo:base/Trie"; +import List "mo:base/List"; +import Iter "mo:base/Iter"; +import Hash "mo:base/Hash"; +import Text "mo:base/Text"; +import Prelude "mo:base/Prelude"; + +/// Binary relation representation. +/// +/// https://en.wikipedia.org/wiki/Binary_relation +/// +/// Properties of this implementation: +/// +/// - Uses (purely functional) tries from base library. +/// - Each operation is fast (sublinear, O(log n) time). +/// - Relations permit cheap O(1)-time copies; versioned history is possible. +/// +/// Use this representation to implement binary relations (e.g., +/// CanCan videos and CanCan users) that can be represented, merged +/// and analyzed separately from the data that they relate. +/// +/// The goal of this representation is to isolate common patterns for +/// relations, and reduce the boilerplate of the alternative (bespoke +/// system) design, where each kind of thing has internal collections +/// (arrays or lists or maps) of the things to which it is related. +/// That representation can be reconstituted as a view of this one. +/// +module { + public type HashPair = + ( X -> Hash.Hash, + Y -> Hash.Hash ); + + public type EqualPair = + ( (X, X) -> Bool, + (Y, Y) -> Bool) ; + + /// Relation between X's and Y's. + /// + /// Uses two (related) hash tries, for the edges in each direction. + /// Holds the hash and equal functions for the tries. + public type Rel = { + forw : Trie.Trie2D ; + back : Trie.Trie2D ; + hash : HashPair ; + equal : EqualPair ; + }; + + /// Relation between X's and Y's. + /// + /// Shared type (no hash or equal functions). + public type RelShared = { + forw : Trie.Trie2D ; + // + // No HO functions, and no backward direction: + // In a serialized message form, the backward direction is redundant + // and can be recomputed in linear time from the forw field. + // + // back : Trie.Trie2D ; + }; + + public func share( rel : Rel ) : RelShared { + { forw = rel.forw ; + // back = rel.back ; + } + }; + + public func fromShare( rel : RelShared, + hash_ : HashPair, + equal_ : EqualPair ) : Rel + { + { forw = rel.forw ; + back = invert(rel.forw); + hash = hash_ ; + equal = equal_ + } + }; + + public func keyOf0( rel : Rel, x : X) : Trie.Key { + { key = x ; hash = rel.hash.0(x) } + }; + + public func keyOf1( rel : Rel, y : Y) : Trie.Key { + { key = y ; hash = rel.hash.1(y) } + }; + + public func keyOf( rel : Rel, p : (X, Y)) + : (Trie.Key, Trie.Key) + { + (keyOf0(rel, p.0), + keyOf1(rel, p.1)) + }; + + public func empty( hash_ : HashPair, + equal_ : EqualPair) : Rel { + { + forw = Trie.empty(); + back = Trie.empty(); + hash = hash_ ; + equal = equal_ + } + }; + + public func getRelated0(rel : Rel, x : X) : Iter.Iter { + let t = Trie.find>(rel.forw, keyOf0(rel, x), rel.equal.0); + switch t { + // to do -- define as Iter.empty() + case null { object { public func next() : ?Y { null } } }; + case (?t) { iterAll(t) }; + } + }; + + public func getRelated1(rel : Rel, y : Y) : Iter.Iter { + let t = Trie.find(rel.back, keyOf1(rel, y), rel.equal.1); + switch t { + case null { object { public func next() : ?X { null } } }; + case (?t) { iterAll(t) }; + } + }; + + public func put( rel : Rel, p : (X, Y)) : Rel { + let k = keyOf(rel, p); + { + forw = Trie.put2D(rel.forw, k.0, rel.equal.0, k.1, rel.equal.1, ()) ; + back = Trie.put2D(rel.back, k.1, rel.equal.1, k.0, rel.equal.0, ()) ; + hash = rel.hash ; + equal = rel.equal ; + } + }; + + public func delete( rel : Rel, p : (X, Y)) : Rel { + let k = (keyOf0(rel, p.0), keyOf1(rel, p.1)); + { + forw = Trie.remove2D(rel.forw, k.0, rel.equal.0, k.1, rel.equal.1).0 ; + back = Trie.remove2D(rel.back, k.1, rel.equal.1, k.0, rel.equal.0).0 ; + hash = rel.hash ; + equal = rel.equal ; + } + }; + + func invert(rel : Trie.Trie2D) : Trie.Trie2D { + Prelude.nyi() // to do -- for testing / upgrades sub-story + }; + + // helper for getRelated{0,1} + func iterAll(t : Trie.Trie) : Iter.Iter = + object { + var stack = ?(t, null) : List.List>; + public func next() : ?K { + switch stack { + case null { null }; + case (?(trie, stack2)) { + switch trie { + case (#empty) { + stack := stack2; + next() + }; + case (#leaf({keyvals=null})) { + stack := stack2; + next() + }; + case (#leaf({size=c; keyvals=?((k2, _), kvs)})) { + stack := ?(#leaf({size=c-1; keyvals=kvs}), stack2); + ?k2.key + }; + case (#branch(br)) { + stack := ?(br.left, ?(br.right, stack2)); + next() + }; + } + } + } + } + }; + + +} diff --git a/src/RelObj.mo b/src/RelObj.mo new file mode 100644 index 00000000..e42a35c5 --- /dev/null +++ b/src/RelObj.mo @@ -0,0 +1,38 @@ +import Trie "mo:base/Trie"; +import List "mo:base/List"; +import Iter "mo:base/Iter"; +import Hash "mo:base/Hash"; +import Text "mo:base/Text"; +import Prelude "mo:base/Prelude"; + +import Rel "Rel"; + +/// OO-based binary relation representation. +/// +/// See also: Rel module. +module { + public class RelObj( + hash : Rel.HashPair, + equal : Rel.EqualPair) + { + var rel = Rel.empty(hash, equal); + public func put(x : X, y : Y) { + rel := Rel.put(rel, (x, y)) + }; + public func delete(x : X, y : Y) { + rel := Rel.delete(rel, (x, y)) + }; + public func get0(x : X) : [Y] { + Iter.toArray(Rel.getRelated0(rel, x)) + }; + public func get1(y : Y) : [X] { + Iter.toArray(Rel.getRelated1(rel, y)) + }; + public func getMap0(x : X, f : Y -> Z) : [Z] { + Iter.toArray(Iter.map(Rel.getRelated0(rel, x), f)) + }; + public func getMap1(y : Y, f : X -> Z) : [Z] { + Iter.toArray(Iter.map(Rel.getRelated1(rel, y), f)) + }; + }; +}