diff --git a/stdlib/Makefile b/stdlib/Makefile index 7f51ebb7cfb..3cbf4962e62 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -13,6 +13,7 @@ NO_COLOR="\x1b[0m" MODULES=\ List \ ListTest \ + AssocList \ Trie \ Set \ SetDb \ @@ -54,31 +55,37 @@ $(OUTDIR)/ListTest.out: $(OUTDIR) list.as listTest.as $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/Trie.out: $(OUTDIR) list.as trie.as +$(OUTDIR)/AssocList.out: $(OUTDIR) list.as assocList.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/Set.out: $(OUTDIR) list.as trie.as set.as +$(OUTDIR)/Trie.out: $(OUTDIR) list.as assocList.as trie.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/SetDb.out: $(OUTDIR) list.as trie.as set.as setDb.as +$(OUTDIR)/Set.out: $(OUTDIR) list.as assocList.as trie.as set.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/SetDbTest.out: $(OUTDIR) list.as trie.as set.as setDb.as setDbTest.as +$(OUTDIR)/SetDb.out: $(OUTDIR) list.as assocList.as trie.as set.as setDb.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/ProduceExchange.out: $(OUTDIR) list.as trie.as examples/produceExchange.as +$(OUTDIR)/SetDbTest.out: $(OUTDIR) list.as assocList.as trie.as set.as setDb.as setDbTest.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) + @echo $(BEGIN) + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ + @echo $(DONE) + +$(OUTDIR)/ProduceExchange.out: $(OUTDIR) list.as assocList.as trie.as examples/produceExchange.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ diff --git a/stdlib/assocList.as b/stdlib/assocList.as new file mode 100644 index 00000000000..67d2b40bf81 --- /dev/null +++ b/stdlib/assocList.as @@ -0,0 +1,168 @@ +/* + * Association Lists, a la functional programming, in ActorScript. + */ + + +// polymorphic association linked lists between keys and values +type AssocList = List<(K,V)>; + +let AssocList = new { + + // find the value associated with a given key, or null if absent. + func find(al : AssocList, + k:K, + k_eq:(K,K)->Bool) + : ?V + { + func rec(al:AssocList) : ?V { + switch (al) { + case (null) null; + case (?((hd_k, hd_v), tl)) { + if (k_eq(k, hd_k)) { + ?hd_v + } else { + rec(tl) + } + }; + }}; + rec(al) + }; + + // replace the value associated with a given key, or add it, if missing. + // returns old value, or null, if no prior value existed. + func replace(al : AssocList, + k:K, + k_eq:(K,K)->Bool, + ov: ?V) + : (AssocList, ?V) + { + func rec(al:AssocList) : (AssocList, ?V) { + switch (al) { + case (null) { + switch ov { + case (null) (null, null); + case (?v) (?((k, v), null), null); + } + }; + case (?((hd_k, hd_v), tl)) { + if (k_eq(k, hd_k)) { + // if value is null, remove the key; otherwise, replace key's old value + // return old value + switch ov { + case (null) (tl, ?hd_v); + case (?v) (?((hd_k, v), tl), ?hd_v); + } + } else { + let (tl2, old_v) = rec(tl); + (?((hd_k, hd_v), tl2), old_v) + } + }; + }}; + rec(al) + }; + + // The key-value pairs of the final list consist of those pairs of + // the left list whose keys are not present in the right list; the + // values of the right list are irrelevant. + func diff(al1: AssocList, + al2: AssocList, + keq: (K,K)->Bool) + : AssocList + { + func rec(al1:AssocList) : AssocList = { + switch al1 { + case (null) null; + case (?((k, v1), tl)) { + switch (find(al2, k, keq)) { + case (null) { rec(tl)}; + case (?v2) { ?((k, v1), rec(tl)) }; + } + }; + } + }; + rec(al1) + }; + + // This operation generalizes the notion of "set union" to finite maps. + // Produces a "disjunctive image" of the two lists, where the values of + // matching keys are combined with the given binary operator. + // + // For unmatched key-value pairs, the operator is still applied to + // create the value in the image. To accomodate these various + // situations, the operator accepts optional values, but is never + // applied to (null, null). + // + func disj(al1:AssocList, + al2:AssocList, + keq:(K,K)->Bool, + vbin:(?V,?W)->X) + : AssocList + { + func rec1(al1:AssocList) : AssocList = { + switch al1 { + case (null) { + func rec2(al2:AssocList) : AssocList = { + switch al2 { + case (null) null; + case (?((k, v2), tl)) { + switch (find(al1, k, keq)) { + case (null) { ?((k, vbin(null, ?v2)), rec2(tl)) }; + case (?v1) { ?((k, vbin(?v1, ?v2)), rec2(tl)) }; + } + }; + } + }; + rec2(al2) + }; + case (?((k, v1), tl)) { + switch (find(al2, k, keq)) { + case (null) { ?((k, vbin(?v1, null)), rec1(tl)) }; + case (?v2) { /* handled above */ rec1(tl) }; + } + }; + } + }; + rec1(al1) + }; + + // This operation generalizes the notion of "set intersection" to + // finite maps. Produces a "conjuctive image" of the two lists, where + // the values of matching keys are combined with the given binary + // operator, and unmatched key-value pairs are not present in the output. + // + func conj(al1 : AssocList, + al2:AssocList, + keq:(K,K)->Bool, + vbin:(V,W)->X) + : AssocList + { + func rec(al1:AssocList) : AssocList = { + switch al1 { + case (null) { null }; + case (?((k, v1), tl)) { + switch (find(al2, k, keq)) { + case (null) { rec(tl) }; + case (?v2) { ?((k, vbin(v1, v2)), rec(tl)) }; + } + }; + } + }; + rec(al1) + }; + + + func fold(al:AssocList, + nil:X, + cons:(K,V,X)->X) + : X + { + func rec(al:List<(K,V)>) : X = { + switch al { + case null nil; + case (?((k,v),t)) { cons(k, v, rec(t)) }; + } + }; + rec(al) + }; + +}; diff --git a/stdlib/examples/produceExchange.as b/stdlib/examples/produceExchange.as index dfc40a2ca7b..123610735a6 100644 --- a/stdlib/examples/produceExchange.as +++ b/stdlib/examples/produceExchange.as @@ -58,7 +58,7 @@ type Price = Nat; type Unit = Nat; // xxx replace with a variant type type Grade = Nat; // xxx replace with a variant type -type TruckType = Nat; // ??? replace with a variant type +type TruckKind = Nat; // ??? replace with a variant type type TruckCapacity = Weight; @@ -81,7 +81,7 @@ type RegionId = Nat; // xxx variant type? type ProduceId = Nat; type ProducerId = Nat; type RetailerId = Nat; -type TruckTypeId = Nat; +type TruckKindId = Nat; type InventoryId = Nat; type TransporterId = Nat; type RouteId = Nat; @@ -98,7 +98,7 @@ type OrderInfo = shared { quant: Quantity; ppu: PricePerUnit; transporter: TransporterId; - truck_type: TruckTypeId; + truck_kind: TruckKindId; weight: Weight; region_begin:RegionId; region_end: RegionId; @@ -115,7 +115,7 @@ type QueryAllResult = shared { quant: Quantity; ppu: PricePerUnit; transporter: TransporterId; - truck_type: TruckTypeId; + truck_kind: TruckKindId; weight: Weight; region_begin:RegionId; region_end: RegionId; @@ -165,7 +165,7 @@ actor ProduceExchange { start: Date, end: Date, cost: Price, - tt: TruckTypeId + tt: TruckKindId ) : async ?RouteId { // xxx null diff --git a/stdlib/list.as b/stdlib/list.as index d16b2f78902..d9fe19613b8 100644 --- a/stdlib/list.as +++ b/stdlib/list.as @@ -46,9 +46,9 @@ let List = new { // last element, optionally; tail recursive func last(l : List) : ?T = { switch l { - case null { null }; - case (?(x,null)) { ?x }; - case (?(_,t)) { last(t) }; + case null { null }; + case (?(x,null)) { ?x }; + case (?(_,t)) { last(t) }; } }; @@ -64,8 +64,8 @@ let List = new { func len(l : List) : Nat = { func rec(l : List, n : Nat) : Nat { switch l { - case null { n }; - case (?(_,t)) { rec(t,n+1) }; + case null { n }; + case (?(_,t)) { rec(t,n+1) }; } }; rec(l,0) @@ -84,8 +84,8 @@ let List = new { func rev(l : List) : List = { func rec(l : List, r : List) : List { switch l { - case null { r }; - case (?(h,t)) { rec(t,?(h,r)) }; + case null { r }; + case (?(h,t)) { rec(t,?(h,r)) }; } }; rec(l, null) @@ -95,8 +95,8 @@ let List = new { func iter(l : List, f:T -> ()) : () = { func rec(l : List) : () { switch l { - case null { () }; - case (?(h,t)) { f(h) ; rec(t) }; + case null { () }; + case (?(h,t)) { f(h) ; rec(t) }; } }; rec(l) @@ -107,8 +107,8 @@ let List = new { func map(l : List, f:T -> S) : List = { func rec(l : List) : List { switch l { - case null { null }; - case (?(h,t)) { ?(f(h),rec(t)) }; + case null { null }; + case (?(h,t)) { ?(f(h),rec(t)) }; } }; rec(l) @@ -119,8 +119,8 @@ let List = new { func filter(l : List, f:T -> Bool) : List = { func rec(l : List) : List { switch l { - case null { null }; - case (?(h,t)) { if (f(h)){ ?(h,rec(t)) } else { rec(t) } }; + case null { null }; + case (?(h,t)) { if (f(h)){ ?(h,rec(t)) } else { rec(t) } }; } }; rec(l) @@ -131,13 +131,13 @@ let List = new { func mapFilter(l : List, f:T -> ?S) : List = { func rec(l : List) : List { switch l { - case null { null }; - case (?(h,t)) { - switch (f(h)) { - case null { rec(t) }; - case (?h_){ ?(h_,rec(t)) }; - } - }; + case null { null }; + case (?(h,t)) { + switch (f(h)) { + case null { rec(t) }; + case (?h_){ ?(h_,rec(t)) }; + } + }; } }; rec(l) @@ -160,7 +160,7 @@ let List = new { // 1/2: fold from left to right, reverse-appending the sublists... let r = { let f = func(a:List, b:List) : List { revAppend(a,b) }; - foldLeft, List>(l, null, f) + foldLeft, List>(l, null, f) }; // 2/2: ...re-reverse the elements, to their original order: rev(r) @@ -169,8 +169,8 @@ let List = new { // (See SML Basis library); tail recursive func revAppend(l1 : List, l2 : List) : List = { switch l1 { - case null { l2 }; - case (?(h,t)) { revAppend(t, ?(h,l2)) }; + case null { l2 }; + case (?(h,t)) { revAppend(t, ?(h,l2)) }; } }; @@ -219,8 +219,8 @@ let List = new { func find(l: List, f:T -> Bool) : ?T = { func rec(l:List) : ?T { switch l { - case null { null }; - case (?(h,t)) { if (f(h)) { ?h } else { rec(t) } }; + case null { null }; + case (?(h,t)) { if (f(h)) { ?h } else { rec(t) } }; } }; rec(l) @@ -230,10 +230,10 @@ let List = new { func exists(l: List, f:T -> Bool) : Bool = { func rec(l:List) : Bool { switch l { - case null { false }; - // XXX/minor --- Missing parens on condition leads to unhelpful error: - //case (?(h,t)) { if f(h) { true } else { rec(t) } }; - case (?(h,t)) { if (f(h)) { true } else { rec(t) } }; + case null { false }; + // XXX/minor --- Missing parens on condition leads to unhelpful error: + //case (?(h,t)) { if f(h) { true } else { rec(t) } }; + case (?(h,t)) { if (f(h)) { true } else { rec(t) } }; } }; rec(l) @@ -243,8 +243,8 @@ let List = new { func all(l: List, f:T -> Bool) : Bool = { func rec(l:List) : Bool { switch l { - case null { true }; - case (?(h,t)) { if (f(h)) { false } else { rec(t) } }; + case null { true }; + case (?(h,t)) { if (f(h)) { false } else { rec(t) } }; } }; rec(l) @@ -254,15 +254,15 @@ let List = new { func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { func rec(l1: List, l2: List) : List { switch (l1, l2) { - case (null, _) { l2 }; - case (_, null) { l1 }; - case (?(h1,t1), ?(h2,t2)) { - if (lte(h1,h2)) { - ?(h1, rec(t1, ?(h2,t2))) - } else { - ?(h2, rec(?(h1,t1), t2)) - } - }; + case (null, _) { l2 }; + case (_, null) { l1 }; + case (?(h1,t1), ?(h2,t2)) { + if (lte(h1,h2)) { + ?(h1, rec(t1, ?(h2,t2))) + } else { + ?(h2, rec(?(h1,t1), t2)) + } + }; } }; rec(l1, l2) @@ -274,15 +274,9 @@ let List = new { func lessThanEq(l1: List, l2: List, lte:(T,T) -> Bool) : Bool { func rec(l1: List, l2: List) : Bool { switch (l1, l2) { - case (null, _) { true }; - case (_, null) { false }; - case (?(h1,t1), ?(h2,t2)) { - if (lte(h1,h2)) { - rec(t1, t2) - } else { - false - } - }; + case (null, _) { true }; + case (_, null) { false }; + case (?(h1,t1), ?(h2,t2)) { lte(h1,h2) and rec(t1, t2) }; } }; rec(l1, l2) @@ -293,16 +287,10 @@ let List = new { func isEq(l1: List, l2: List, eq:(T,T) -> Bool) : Bool { func rec(l1: List, l2: List) : Bool { switch (l1, l2) { - case (null, null) { true }; - case (null, _) { false }; - case (_, null) { false }; - case (?(h1,t1), ?(h2,t2)) { - if (eq(h1,h2)) { - rec(t1, t2) - } else { - false - } - }; + case (null, null) { true }; + case (null, _) { false }; + case (_, null) { false }; + case (?(h1,t1), ?(h2,t2)) { eq(h1,h2) and rec(t1, t2) }; } }; rec(l1, l2) @@ -313,15 +301,15 @@ let List = new { func partition(l: List, f:T -> Bool) : (List, List) { func rec(l: List) : (List, List) { switch l { - case null { (null, null) }; - case (?(h,t)) { - let (pl,pr) = rec(t); - if (f(h)) { - (?(h, pl), pr) - } else { - (pl, ?(h, pr)) - } - }; + case null { (null, null) }; + case (?(h,t)) { + let (pl,pr) = rec(t); + if (f(h)) { + (?(h, pl), pr) + } else { + (pl, ?(h, pr)) + } + }; } }; rec(l) diff --git a/stdlib/set.as b/stdlib/set.as index 00ebf8e664c..4961b890aab 100644 --- a/stdlib/set.as +++ b/stdlib/set.as @@ -5,7 +5,7 @@ /* Sets are partial maps from element type to unit type, i.e., the partial map represents the set with its domain. -*/ + */ // TODO-Matthew: // @@ -24,13 +24,13 @@ let Set = new { func empty():Set = Trie.empty(); - func insert(s:Set, x:T, xh:Hash):Set = { - let (s2, _) = Trie.insert(s, x, xh, ()); + func insert(s:Set, x:T, xh:Hash, eq:(T,T)->Bool) : Set = { + let (s2, _) = Trie.insert(s, new {key=x; hash=xh}, eq, ()); s2 }; - func remove(s:Set, x:T, xh:Hash):Set = { - let (s2, _) = Trie.remove(s, x, xh); + func remove(s:Set, x:T, xh:Hash, eq:(T,T)->Bool) : Set = { + let (s2, _) = Trie.remove(s, new {key=x; hash=xh}, eq); s2 }; @@ -48,14 +48,14 @@ let Set = new { }; func mem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { - switch (Trie.find(s, x, xh, eq)) { + switch (Trie.find(s, new {key=x; hash=xh}, eq)) { case null { false }; case (?_) { true }; } }; - func union(s1:Set, s2:Set):Set { - let s3 = Trie.merge(s1, s2); + func union(s1:Set, s2:Set, eq:(T,T)->Bool):Set { + let s3 = Trie.merge(s1, s2, eq); s3 }; @@ -72,4 +72,4 @@ let Set = new { func unitEq (_:(),_:()):Bool{ true }; -}; \ No newline at end of file +}; diff --git a/stdlib/setDb.as b/stdlib/setDb.as index 96a7d9a99cc..84351439ff9 100644 --- a/stdlib/setDb.as +++ b/stdlib/setDb.as @@ -3,47 +3,66 @@ //////////////////////////////////////////////////////////////////// let SetDb = new { - private func setDbPrint(s:Set) { + private func setDbPrint(s:Set) { func rec(s:Set, ind:Nat, bits:Hash) { func indPrint(i:Nat) { - if (i == 0) { } else { print "| "; indPrint(i-1) } + if (i == 0) { } else { print "| "; indPrint(i-1) } }; func bitsPrintRev(bits:Bits) { - switch bits { - case null { print "" }; - case (?(bit,bits_)) { - bitsPrintRev(bits_); - if bit { print "1R." } - else { print "0L." } - } - } + switch bits { + case null { print "" }; + case (?(bit,bits_)) { + bitsPrintRev(bits_); + if bit { print "1R." } + else { print "0L." } + } + } + }; + func hashPrintRev(bits:Bits) { + switch bits { + case null { print "" }; + case (?(bit,bits_)) { + hashPrintRev(bits_); + if bit { print "1" } + else { print "0" } + } + } }; switch s { case null { - //indPrint(ind); - //bitsPrintRev(bits); - //print "(null)\n"; - }; + //indPrint(ind); + //bitsPrintRev(bits); + //print "(null)\n"; + }; case (?n) { - switch (n.key) { - case null { - //indPrint(ind); - //bitsPrintRev(bits); - //print "bin \n"; - rec(n.right, ind+1, ?(true, bits)); - rec(n.left, ind+1, ?(false,bits)); - //bitsPrintRev(bits); - //print ")\n" - }; - case (?k) { - //indPrint(ind); - bitsPrintRev(bits); - print "(leaf "; - printInt k; - print ")\n"; - }; - } - }; + switch (n.keyvals) { + case null { + //indPrint(ind); + //bitsPrintRev(bits); + //print "bin \n"; + rec(n.right, ind+1, ?(true, bits)); + rec(n.left, ind+1, ?(false,bits)); + //bitsPrintRev(bits); + //print ")\n" + }; + case (?keyvals) { + //indPrint(ind); + print "(leaf ["; + List.iter<(Key,())>( + ?keyvals, + func ((k:Key, ())) : () = { + print("hash("); + printInt(k.key); + print(")="); + hashPrintRev(k.hash); + print("; "); + () + } + ); + print "])\n"; + }; + } + }; } }; rec(s, 0, null); @@ -57,7 +76,7 @@ let SetDb = new { print " setInsert("; printInt x; print ")"; - let r = Set.insert(s,x,xh); + let r = Set.insert(s,x,xh,natEq); print ";\n"; setDbPrint(r); r @@ -82,7 +101,7 @@ let SetDb = new { print s2name; print ")"; // also: test that merge agrees with disj: - let r1 = Set.union(s1, s2); + let r1 = Set.union(s1, s2, natEq); let r2 = Trie.disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); assert(Trie.equalStructure(r1, r2, natEq, Set.unitEq)); print ";\n"; @@ -104,4 +123,4 @@ let SetDb = new { r }; -}; \ No newline at end of file +}; diff --git a/stdlib/trie.as b/stdlib/trie.as index aaec442f09c..2791c47bdf6 100644 --- a/stdlib/trie.as +++ b/stdlib/trie.as @@ -5,10 +5,10 @@ Functional maps (and sets) whose representation is "canonical", and history independent. - See this POPL 1989 paper (Section 6): - - "Incremental computation via function caching", Pugh & Teitelbaum. - - https://dl.acm.org/citation.cfm?id=75305 - - Public copy here: http://matthewhammer.org/courses/csci7000-s17/readings/Pugh89.pdf + See this POPL 1989 paper (Section 6): + - "Incremental computation via function caching", Pugh & Teitelbaum. + - https://dl.acm.org/citation.cfm?id=75305 + - Public copy here: http://matthewhammer.org/courses/csci7000-s17/readings/Pugh89.pdf By contrast, other usual functional representations of maps (AVL Trees, Red-Black Trees) do not enjoy history independence, and are @@ -28,14 +28,12 @@ // - basic tests (and primitive debugging) for set operations // - write trie operations that operate over pairs of tries: // for set union, difference and intersection. +// - handle hash collisions gracefully using association list module // TODO-Matthew: // // - (more) regression tests for everything that is below // -// - handle hash collisions gracefully; -// ==> Blocked on AS module support, for using List module. -// // - adapt the path length of each subtree to its cardinality; avoid // needlessly long paths, or paths that are too short for their // subtree's size. @@ -70,39 +68,58 @@ type Hash = Bits; // let HASH_BITS = 4; +type Key = { + // hash field: permits fast inequality checks, permits collisions; + // (eventually: permits incremental growth of deeper trie paths) + hash: Hash; + // key field: for conservative equality checks, after equal hashes. + key: K; +}; + +// Binary branch nodes +type Branch = { + left:Trie; + right:Trie; +}; +// Leaf nodes are association lists of `Key`s +// Every key shares a common hash prefix, its trie path. +type Leaf = { + keyvals:List<(Key,V)>; +}; + // XXX: See AST-42 type Node = { left:Trie; right:Trie; - key:?K; - val:?V + keyvals:List<(Key,V)>; }; + type Trie = ?Node; /* See AST-42 (sum types); we want this type definition instead: -// Use a sum type (AST-42) -type Trie = { #leaf : LeafNode; #bin : BinNode; #empty }; -type BinNode = { left:Trie; right:Trie }; -type LeafNode = { key:K; val:V }; + // Use a sum type (AST-42) + type Trie = { #leaf : LeafNode; #bin : BinNode; #empty }; + type BinNode = { left:Trie; right:Trie }; + type LeafNode = { key:K; val:V }; -*/ + */ let Trie = new { // XXX: until AST-42: func isNull(x : ?X) : Bool { switch x { - case null { true }; - case (?_) { false }; + case null { true }; + case (?_) { false }; }; }; // XXX: until AST-42: func assertIsNull(x : ?X) { switch x { - case null { assert(true) }; - case (?_) { assert(false) }; + case null { assert true }; + case (?_) { assert false }; }; }; @@ -126,14 +143,14 @@ let Trie = new { // XXX: until AST-42: func assertIsEmpty(t : Trie) { switch t { - case null { assert(true) }; - case (?_) { assert(false) }; + case null { assert true }; + case (?_) { assert false }; }; }; // XXX: until AST-42: func makeBin(l:Trie, r:Trie) : Trie { - ?(new {left=l; right=r; key=null; val=null }) + ?(new {left=l; right=r; keyvals=null; }) }; // XXX: until AST-42: @@ -141,29 +158,29 @@ let Trie = new { switch t { case null { false }; case (?t_) { - switch (t_.key) { - case null { true }; - case _ { false }; - }; - }; + switch (t_.keyvals) { + case null { true }; + case _ { false }; + }; + }; } }; // XXX: until AST-42: - func makeLeaf(k:K, v:V) : Trie { - ?(new {left=null; right=null; key=?k; val=?v }) + func makeLeaf(kvs:AssocList,V>) : Trie { + ?(new {left=null; right=null; keyvals=kvs }) }; // XXX: until AST-42: - func matchLeaf(t:Trie) : ?(K,V) { + func matchLeaf(t:Trie) : ?List<(Key,V)> { switch t { case null { null }; case (?t_) { - switch (t_.key, t_.val) { - case (?k,?v) ?(k,v); - case (_) null; - } - }; + switch (t_.keyvals) { + case (?keyvals) ?(?(keyvals)); + case (_) null; + } + }; } }; @@ -172,39 +189,41 @@ let Trie = new { switch t { case null { false }; case (?t_) { - switch (t_.key) { - case null { false }; - case _ { true }; - } - }; + switch (t_.keyvals) { + case null { false }; + case _ { true }; + } + }; } }; // XXX: until AST-42: func assertIsBin(t : Trie) { switch t { - case null { assert(false) }; - case (?n) { - assertIsNull(n.key); - assertIsNull(n.val); - }; + case null { assert false }; + case (?n) { + assertIsNull<((Key,V),AssocList,V>)>(n.keyvals); + }; } }; // XXX: until AST-42: - func getLeafKey(t : Node) : K { + func getLeafKey(t : Node) : Key { assertIsNull>(t.left); assertIsNull>(t.right); - switch (t.key) { - case (?k) { k }; - case null { getLeafKey(t) }; + switch (t.keyvals) { + case (?((k,v),_)) { k }; + case (null) { /* ERROR */ getLeafKey(t) }; } }; // XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: - func getLeafVal(t : Node) : ?V { + func getLeafVal(t : Node) : V { assertIsNull>(t.left); assertIsNull>(t.right); - t.val + switch (t.keyvals) { + case (?((k,v),_)) { v }; + case null { /* ERROR */ getLeafVal(t) }; + } }; // TODO: Replace with bitwise operations on Words, once we have each of those in AS. @@ -212,115 +231,138 @@ let Trie = new { func getHashBit(h:Hash, pos:Nat) : Bool { switch h { case null { - // XXX: Should be an error case; it shouldn't happen in our tests if we set them up right. - false - }; + // XXX: Should be an error case; it shouldn't happen in our tests if we set them up right. + false + }; case (?(b, h_)) { - if (pos == 0) { b } - else { getHashBit(h_, pos-1) } - }; + if (pos == 0) { b } + else { getHashBit(h_, pos-1) } + }; } }; + // Test if two lists of bits are equal. + func hashEq(ha:Hash, hb:Hash) : Bool { + switch (ha, hb) { + case (null, null) true; + case (null, _) false; + case (_, null) false; + case (?(bita, ha2), ?(bitb, hb2)) { + if (bita == bitb) { hashEq(ha2, hb2) } + else { false } + }; + } + }; + + // Equality function for two `Key`s, in terms of equaltiy of `K`'s. + func keyEq(keq:(K,K) -> Bool) : ((Key,Key) -> Bool) = { + func (key1:Key, key2:Key) : Bool = + (hashEq(key1.hash, key2.hash) and keq(key1.key, key2.key)) + }; + // part of "public interface": func empty() : Trie = makeEmpty(); // helper function for constructing new paths of uniform length - func buildNewPath(bitpos:Nat, k:K, k_hash:Hash, ov:?V) : Trie { + func buildNewPath(bitpos:Nat, k:Key, ov:?V) : Trie { func rec(bitpos:Nat) : Trie { if ( bitpos < HASH_BITS ) { - // create new bin node for this bit of the hash - let path = rec(bitpos+1); - let bit = getHashBit(k_hash, bitpos); - if (not bit) { - ?(new {left=path; right=null; key=null; val=null}) - } - else { - ?(new {left=null; right=path; key=null; val=null}) - } + // create new bin node for this bit of the hash + let path = rec(bitpos+1); + let bit = getHashBit(k.hash, bitpos); + if bit { + ?(new {left=null; right=path; keyvals=null}) + } + else { + ?(new {left=path; right=null; keyvals=null}) + } } else { - // create new leaf for (k,v) pair - ?(new {left=null; right=null; key=?k; val=ov }) + // create new leaf for (k,v) pair, if the value is non-null: + switch ov { + case null { ?(new {left=null; right=null; keyvals=null }) }; + case (?v) { ?(new {left=null; right=null; keyvals=?((k,v),null) }) }; + } } }; rec(bitpos) }; // replace the given key's value option with the given one, returning the previous one - func replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { + func replace(t : Trie, k:Key, k_eq:(K,K)->Bool, v:?V) : (Trie, ?V) { + let key_eq = keyEq(k_eq); // For `bitpos` in 0..HASH_BITS, walk the given trie and locate the given value `x`, if it exists. func rec(t : Trie, bitpos:Nat) : (Trie, ?V) { if ( bitpos < HASH_BITS ) { - switch t { - case null { (buildNewPath(bitpos, k, k_hash, v), null) }; - case (?n) { - assertIsBin(t); - let bit = getHashBit(k_hash, bitpos); - // rebuild either the left or right path with the inserted (k,v) pair - if (not bit) { - let (l, v_) = rec(n.left, bitpos+1); - (?(new {left=l; right=n.right; key=null; val=null }), v_) - } - else { - let (r, v_) = rec(n.right, bitpos+1); - (?(new {left=n.left; right=r; key=null; val=null }), v_) - } - }; - } + switch t { + case null { (buildNewPath(bitpos, k, v), null) }; + case (?n) { + assertIsBin(t); + let bit = getHashBit(k.hash, bitpos); + // rebuild either the left or right path with the inserted (k,v) pair + if (not bit) { + let (l, v_) = rec(n.left, bitpos+1); + (?(new {left=l; right=n.right; keyvals=null; }), v_) + } + else { + let (r, v_) = rec(n.right, bitpos+1); + (?(new {left=n.left; right=r; keyvals=null; }), v_) + } + }; + } } else { - // No more walking; we should be at a leaf now, by construction invariants. - switch t { - case null { (buildNewPath(bitpos, k, k_hash, v), null) }; - case (?l) { - // TODO: Permit hash collisions by walking a list/array of KV pairs in each leaf: - (?(new{left=null;right=null;key=?k;val=v}), l.val) - }; - } + // No more walking; we should be at a leaf now, by construction invariants. + switch t { + case null { (buildNewPath(bitpos, k, v), null) }; + case (?l) { + // Permit hash collisions by walking + // a list/array of KV pairs in each leaf: + let (kvs2, old_val) = + AssocList.replace,V>(l.keyvals, k, key_eq, v); + (?(new{left=null; right=null; keyvals=kvs2}), old_val) + }; + } } }; rec(t, 0) }; // insert the given key's value in the trie; return the new trie - func insert(t : Trie, k:K, k_hash:Hash, v:V) : (Trie, ?V) { - replace(t, k, k_hash, ?v) + func insert(t : Trie, k:Key, k_eq:(K,K)->Bool, v:V) : (Trie, ?V) { + replace(t, k, k_eq, ?v) }; // remove the given key's value in the trie; return the new trie - func remove(t : Trie, k:K, k_hash:Hash) : (Trie, ?V) { - replace(t, k, k_hash, null) + func remove(t : Trie, k:Key, k_eq:(K,K)->Bool) : (Trie, ?V) { + replace(t, k, k_eq, null) }; // find the given key's value in the trie, or return null if nonexistent - func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { + func find(t : Trie, k:Key, k_eq:(K,K) -> Bool) : ?V { + let key_eq = keyEq(k_eq); // For `bitpos` in 0..HASH_BITS, walk the given trie and locate the given value `x`, if it exists. func rec(t : Trie, bitpos:Nat) : ?V { if ( bitpos < HASH_BITS ) { - switch t { - case null { - // the trie may be "sparse" along paths leading to no keys, and may end early. - null - }; - case (?n) { - assertIsBin(t); - let bit = getHashBit(k_hash, bitpos); - if (not bit) { rec(n.left, bitpos+1) } - else { rec(n.right, bitpos+1) } - }; - } + switch t { + case null { + // the trie may be "sparse" along paths leading to no keys, and may end early. + null + }; + case (?n) { + assertIsBin(t); + let bit = getHashBit(k.hash, bitpos); + if (not bit) { rec(n.left, bitpos+1) } + else { rec(n.right, bitpos+1) } + }; + } } else { - // No more walking; we should be at a leaf now, by construction invariants. - switch t { - case null { null }; - case (?l) { - // TODO: Permit hash collisions by walking a list/array of KV pairs in each leaf: - if (keq(getLeafKey(l), k)) { - getLeafVal(l) - } else { - null - } - }; - } + // No more walking; we should be at a leaf now, by construction invariants. + switch t { + case null { null }; + case (?l) { + // Permit hash collisions by walking a list/array of KV pairs in each leaf: + AssocList.find,V>(l.keyvals, k, key_eq) + }; + } } }; rec(t, 0) @@ -330,79 +372,88 @@ let Trie = new { // in common keys. note: the `disj` operation generalizes this `merge` // operation in various ways, and does not (in general) loose // information; this operation is a simpler, special case. - func merge(tl:Trie, tr:Trie) : Trie { - switch (tl, tr) { + func merge(tl:Trie, tr:Trie, k_eq:(K,K)->Bool): Trie { + let key_eq = keyEq(k_eq); + func rec(tl:Trie, tr:Trie) : Trie { + switch (tl, tr) { case (null, _) { return tr }; case (_, null) { return tl }; case (?nl,?nr) { - switch (isBin(tl), - isBin(tr)) { - case (true, true) { - let t0 = merge(nl.left, nr.left); - let t1 = merge(nl.right, nr.right); - makeBin(t0, t1) - }; - case (false, true) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - tr - }; - case (true, false) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - tr - }; - case (false, false) { - /// XXX: handle hash collisions here. - tr - }; - } - }; - } + switch (isBin(tl), + isBin(tr)) { + case (true, true) { + let t0 = rec(nl.left, nr.left); + let t1 = rec(nl.right, nr.right); + makeBin(t0, t1) + }; + case (false, true) { + assert false; + // XXX impossible, until we lift uniform depth assumption + tr + }; + case (true, false) { + assert false; + // XXX impossible, until we lift uniform depth assumption + tr + }; + case (false, false) { + /// handle hash collisions by using the association list: + makeLeaf( + AssocList.disj,V,V,V>( + nl.keyvals, nr.keyvals, + key_eq, + func (x:?V, y:?V):V = { + switch (x, y) { + case (null, null) {/* IMPOSSIBLE case: diverge. */ func x():V=x(); x()}; + case (null, ?v) v; + case (?v, _) v; + }} + )) + }; + } + }; + } + }; + rec(tl, tr) }; // The key-value pairs of the final trie consists of those pairs of // the left trie whose keys are not present in the right trie; the // values of the right trie are irrelevant. - func diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { + func diff(tl:Trie, tr:Trie, k_eq:(K,K)->Bool) : Trie { + let key_eq = keyEq(k_eq); func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { case (null, _) { return makeEmpty() }; case (_, null) { return tl }; case (?nl,?nr) { - switch (isBin(tl), - isBin(tr)) { - case (true, true) { - let t0 = rec(nl.left, nr.left); - let t1 = rec(nl.right, nr.right); - makeBin(t0, t1) - }; - case (false, true) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - tl - }; - case (true, false) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - tl - }; - case (false, false) { - /// XXX: handle hash collisions here. - switch (nl.key, nr.key) { - case (?kl, ?kr) { - if (keq(kl, kr)) { - makeEmpty(); - } else { - tl - }}; - // XXX impossible, and unnecessary with AST-42. - case _ { tl } - } - }; - } - }; - }}; + switch (isBin(tl), + isBin(tr)) { + case (true, true) { + let t0 = rec(nl.left, nr.left); + let t1 = rec(nl.right, nr.right); + makeBin(t0, t1) + }; + case (false, true) { + assert false; + // XXX impossible, until we lift uniform depth assumption + tl + }; + case (true, false) { + assert false; + // XXX impossible, until we lift uniform depth assumption + tl + }; + case (false, false) { + assert(isLeaf(tl)); + assert(isLeaf(tr)); + makeLeaf( + AssocList.diff,V,W>(nl.keyvals, nr.keyvals, key_eq) + ) + }; + } + }; + }}; rec(tl, tr) }; @@ -416,73 +467,64 @@ let Trie = new { // applied to (null, null). // func disj(tl:Trie, tr:Trie, - keq:(K,K)->Bool, vbin:(?V,?W)->X) + k_eq:(K,K)->Bool, vbin:(?V,?W)->X) : Trie { + let key_eq = keyEq(k_eq); func recL(t:Trie) : Trie { switch t { - case (null) null; - case (? n) { - switch (matchLeaf(t)) { - case (?(k,v)) { makeLeaf(k, vbin(?v, null)) }; - case _ { makeBin(recL(n.left), recL(n.right)) } - } - }; - }}; + case (null) null; + case (? n) { + switch (matchLeaf(t)) { + case (?_) { makeLeaf(AssocList.disj,V,W,X>(n.keyvals, null, key_eq, vbin)) }; + case _ { makeBin(recL(n.left), recL(n.right)) } + } + }; + }}; func recR(t:Trie) : Trie { switch t { - case (null) null; - case (? n) { - switch (matchLeaf(t)) { - case (?(k,w)) { makeLeaf(k, vbin(null, ?w)) }; - case _ { makeBin(recR(n.left), recR(n.right)) } - } - }; - }}; + case (null) null; + case (? n) { + switch (matchLeaf(t)) { + case (?_) { makeLeaf(AssocList.disj,V,W,X>(null, n.keyvals, key_eq, vbin)) }; + case _ { makeBin(recR(n.left), recR(n.right)) } + } + }; + }}; func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { - // empty-empty terminates early, all other cases do not. + // empty-empty terminates early, all other cases do not. case (null, null) { makeEmpty() }; case (null, _ ) { recR(tr) }; case (_, null) { recL(tl) }; case (? nl, ? nr) { - switch (isBin(tl), - isBin(tr)) { - case (true, true) { - let t0 = rec(nl.left, nr.left); - let t1 = rec(nl.right, nr.right); - makeBin(t0, t1) - }; - case (false, true) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - makeEmpty() - }; - case (true, false) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - makeEmpty() - }; - case (false, false) { - assert(isLeaf(tl)); - assert(isLeaf(tr)); - switch (nl.key, nl.val, nr.key, nr.val) { - // leaf-leaf case - case (?kl, ?vl, ?kr, ?vr) { - if (keq(kl, kr)) { - makeLeaf(kl, vbin(?vl, ?vr)); - } else { - // XXX: handle hash collisions here. - makeEmpty() - } - }; - // XXX impossible, and unnecessary with AST-42. - case _ { makeEmpty() }; - } - }; - } - }; - }}; + switch (isBin(tl), + isBin(tr)) { + case (true, true) { + let t0 = rec(nl.left, nr.left); + let t1 = rec(nl.right, nr.right); + makeBin(t0, t1) + }; + case (false, true) { + assert false; + // XXX impossible, until we lift uniform depth assumption + makeEmpty() + }; + case (true, false) { + assert false; + // XXX impossible, until we lift uniform depth assumption + makeEmpty() + }; + case (false, false) { + assert(isLeaf(tl)); + assert(isLeaf(tr)); + makeLeaf( + AssocList.disj,V,W,X>(nl.keyvals, nr.keyvals, key_eq, vbin) + ) + }; + } + }; + }}; rec(tl, tr) }; @@ -491,51 +533,42 @@ let Trie = new { // the values of matching keys are combined with the given binary // operator, and unmatched key-value pairs are not present in the output. func conj(tl:Trie, tr:Trie, - keq:(K,K)->Bool, vbin:(V,W)->X) + k_eq:(K,K)->Bool, vbin:(V,W)->X) : Trie { + let key_eq = keyEq(k_eq); func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { - case (null, null) { return makeEmpty() }; - case (null, ? nr) { return makeEmpty() }; - case (? nl, null) { return makeEmpty() }; - case (? nl, ? nr) { - switch (isBin(tl), - isBin(tr)) { - case (true, true) { - let t0 = rec(nl.left, nr.left); - let t1 = rec(nl.right, nr.right); - makeBin(t0, t1) - }; - case (false, true) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - makeEmpty() - }; - case (true, false) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - makeEmpty() - }; - case (false, false) { - assert(isLeaf(tl)); - assert(isLeaf(tr)); - switch (nl.key, nl.val, nr.key, nr.val) { - // leaf-leaf case - case (?kl, ?vl, ?kr, ?vr) { - if (keq(kl, kr)) { - makeLeaf(kl, vbin(vl, vr)); - } else { - // XXX: handle hash collisions here. - makeEmpty() - } - }; - // XXX impossible, and unnecessary with AST-42. - case _ { makeEmpty() }; - } - }; - } - } + case (null, null) { return makeEmpty() }; + case (null, ? nr) { return makeEmpty() }; + case (? nl, null) { return makeEmpty() }; + case (? nl, ? nr) { + switch (isBin(tl), + isBin(tr)) { + case (true, true) { + let t0 = rec(nl.left, nr.left); + let t1 = rec(nl.right, nr.right); + makeBin(t0, t1) + }; + case (false, true) { + assert false; + // XXX impossible, until we lift uniform depth assumption + makeEmpty() + }; + case (true, false) { + assert false; + // XXX impossible, until we lift uniform depth assumption + makeEmpty() + }; + case (false, false) { + assert(isLeaf(tl)); + assert(isLeaf(tr)); + makeLeaf( + AssocList.conj,V,W,X>(nl.keyvals, nr.keyvals, key_eq, vbin) + ) + }; + } + } }}; rec(tl, tr) }; @@ -549,11 +582,17 @@ let Trie = new { switch t { case (null) { empty }; case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { leaf(k,v) }; - case null { bin(rec(n.left), rec(n.right)) }; - } - }; + switch (matchLeaf(t)) { + case (?kvs) { + AssocList.fold,V,X>( + kvs, empty, + func (k:Key, v:V, x:X):X = + bin(leaf(k.key,v),x) + ) + }; + case null { bin(rec(n.left), rec(n.right)) }; + } + }; }}; rec(t) }; @@ -565,11 +604,16 @@ let Trie = new { switch t { case (null) x; case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { f(k,v,x) }; - case null { rec(n.left,rec(n.right,x)) }; - } - }; + switch (matchLeaf(t)) { + case (?kvs) { + AssocList.fold,V,X>( + kvs, x, + func (k:Key, v:V, x:X):X = f(k.key,v,x) + ) + }; + case null { rec(n.left,rec(n.right,x)) }; + } + }; }}; rec(t, x) }; @@ -580,26 +624,35 @@ let Trie = new { switch t { case (null) { false }; case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { f(k,v) }; - case null { rec(n.left) or rec(n.right) }; - } - }; + switch (matchLeaf(t)) { + case (?kvs) { + List.exists<(Key,V)>( + kvs, func ((k:Key,v:V)):Bool=f(k.key,v) + ) + }; + case null { rec(n.left) or rec(n.right) }; + } + }; }}; rec(t) }; + // specialized foldUp operation. func forAll(t:Trie, f:(K,V)->Bool) : Bool { func rec(t:Trie) : Bool { switch t { case (null) { true }; case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { f(k,v) }; - case null { rec(n.left) and rec(n.right) }; - } - }; + switch (matchLeaf(t)) { + case (?kvs) { + List.all<(Key,V)>( + kvs, func ((k:Key,v:V)):Bool=f(k.key,v) + ) + }; + case null { rec(n.left) and rec(n.right) }; + } + }; }}; rec(t) }; @@ -613,11 +666,11 @@ let Trie = new { switch t { case (null) { true }; case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { false }; - case null { rec(n.left) and rec(n.right) }; - } - }; + switch (matchLeaf(t)) { + case (?kvs) { List.isNil<(Key,V)>(kvs) }; + case null { rec(n.left) and rec(n.right) }; + } + }; } }; rec(t) @@ -628,60 +681,61 @@ let Trie = new { switch t { case (null) { null }; case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { - // XXX-Typechecker: - // This version of the next line gives _really_ - // strange type errors, and no parse errors. - // if f(k,v) { - if (f(k,v)) { - makeLeaf(k,v) - } else { - null - } - }; - case null { - let l = rec(n.left); - let r = rec(n.right); - switch (isEmpty(l), - isEmpty(r)) { - case (true, true) null; - case (false, true) r; - case (true, false) l; - case (false, false) makeBin(l, r); - } - }; - } - }; + switch (matchLeaf(t)) { + case (?kvs) { + makeLeaf( + List.filter<(Key,V)>(kvs, func ((k:Key,v:V)):Bool = f(k.key,v)) + ) + }; + case null { + let l = rec(n.left); + let r = rec(n.right); + switch (isEmpty(l), + isEmpty(r)) { + case (true, true) null; + case (false, true) r; + case (true, false) l; + case (false, false) makeBin(l, r); + } + }; + } + }; } }; rec(t) }; - func mapFilter(t:Trie, f:(K,V)->?(K,W)) : Trie { + func mapFilter(t:Trie, f:(K,V)->?W) : Trie { func rec(t:Trie) : Trie { switch t { case (null) { null }; case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { - switch (f(k,v)) { - case (null) null; - case (?(k,w)) { makeLeaf(k,w) }; - }}; - case null { - let l = rec(n.left); - let r = rec(n.right); - switch (isEmpty(l), - isEmpty(r)) { - case (true, true) null; - case (false, true) r; - case (true, false) l; - case (false, false) makeBin(l, r); - } - }; - } - }; + switch (matchLeaf(t)) { + case (?kvs) { + makeLeaf( + List.mapFilter<(Key,V),(Key,W)> + (kvs, + // retain key and hash, but update key's value using f: + func ((k:Key,v:V)):?(Key,W) = { + switch (f(k.key,v)) { + case (null) null; + case (?w) (?(new {key=k.key; hash=k.hash}, w)); + }} + )) + }; + case null { + let l = rec(n.left); + let r = rec(n.right); + switch (isEmpty(l), + isEmpty(r)) { + case (true, true) null; + case (false, true) r; + case (true, false) l; + case (false, false) makeBin(l, r); + } + }; + } + }; } }; rec(t) @@ -706,14 +760,23 @@ let Trie = new { case (_, null) { false }; case (null, _) { false }; case (?nl, ?nr) { - switch (matchLeaf(tl), - matchLeaf(tr)) { - case (?(kl,vl), ?(kr,vr)) { keq(kl,kr) and veq(vl,vr) }; - case (null, null) { rec(nl.left, nr.left) - and rec(nl.right, nr.right) }; - case _ { false } - } - }; + switch (matchLeaf(tl), + matchLeaf(tr)) { + case (null, null) { + rec(nl.left, nr.left) + and rec(nl.right, nr.right) + }; + case (null, _) { false }; + case (_, null) { false }; + case (?kvs1, ?kvs2) { + List.isEq<(Key,V)> + (kvs1, kvs2, + func ((k1:Key, v1:V), (k2:Key, v2:V)) : Bool = + keq(k1.key, k2.key) and veq(v1,v2) + ) + }; + } + }; }}; rec(tl, tr) };