From 947744f19e34d0d6ab8feb1592e17db17ec4e497 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 4 Feb 2019 13:15:36 -0700 Subject: [PATCH 01/52] list functions in AS, inspired by SML Basis library --- samples/collections/list.as | 387 ++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 samples/collections/list.as diff --git a/samples/collections/list.as b/samples/collections/list.as new file mode 100644 index 00000000000..fce8a79b06a --- /dev/null +++ b/samples/collections/list.as @@ -0,0 +1,387 @@ +/* + This file represents a kind of "warm up" for creating more involved + collections, such as hash tables (which use linked lists internally) + and persistant maps, which will follow similar functional prog + patterns. + */ + +// TODO-Matthew: Look at SML Basis Library; Look at OCaml List library. +// Write: +// - iterator objects, for use in 'for ... in ...' patterns +// - standard list recursors: foldl, foldr, iter +// - standard higher-order combinators: map, filter, etc. +// - lists+pairs: zip, split, etc + +// polymorphic linked lists +type List = ?(T, List); + +// empty list +func nil() : List = + null; + +// test for empty list +func isnil(l : List) : Bool { + switch l { + case null { true }; + case _ { false }; + } +}; + +// aka "list cons" +func push(x : T, l : List) : List = + ?(x, l); + +// get head of list +func hd(l : List) : ?T = { + switch l { + case null { null }; + case (?(h, _)) { ?h }; + } +}; + +// get tail of list, as a list +func tl(l : List) : List = { + switch l { + case null { null }; + case (?(_, t)) { t }; + } +}; + +// get tail of list, as an optional list +func tlo(l : List) : ?List = { + switch l { + case null { null }; + case (?(_, t)) { ?t }; + } +}; + +/* +// last element (SML Basis library); tail recursive +func last(l : List) : T = { + switch l { + // XXX + // Q: What's the type of 'assert false'? + // Shouldn't it be 'Any', and not '()'? + // + //case null { assert false }; + case (?(x,null)) { x }; + case (?(_,t)) { last(t) }; + } +}; +*/ + +// last element, optionally; tail recursive +func lasto(l : List) : ?T = { + switch l { + case null { null }; + case (?(x,null)) { ?x }; + case (?(_,t)) { lasto(t) }; + } +}; + +// treat the list as a stack; combines 'hd' and (non-failing) 'tl' into one operation +func pop(l : List) : (?T, List) = { + switch l { + case null { (null, null) }; + case (?(h, t)) { (?h, t) }; + } +}; + +// length; tail recursive +func len(l : List) : Nat = { + func rec(l : List, n : Nat) : Nat { + switch l { + case null { n }; + case (?(_,t)) { rec(t,n+1) }; + } + }; + rec(l,0) +}; + +// array-like list access, but in linear time; tail recursive +func nth(l : List, n : Nat) : ?T = { + switch (n, l) { + case (0, _) { hd(l) }; + case (_, null) { null }; + case (_, ?(_,t)) { nth(t, n - 1) }; + } +}; + +// array-like list access, but in linear time; tail recursive +func nth_(l : List, n : Nat) : ?T = { + switch (n, tlo(l)) { + case (0, _) { hd(l) }; + case (_, null) { null }; + case (_, ?t) { nth_(t, n - 1) }; + } +}; + +// reverse; tail recursive +func rev(l : List) : List = { + func rec(l : List, r : List) : List { + switch l { + case null { r }; + case (?(h,t)) { rec(t,?(h,r)) }; + } + }; + rec(l, null) +}; + +// Called "app" in SML Basis, and "iter" in OCaml; tail recursive +func iter(l : List, f:T -> ()) : () = { + func rec(l : List) : () { + switch l { + case null { () }; + case (?(h,t)) { f(h) ; rec(t) }; + } + }; + rec(l) +}; + +// map; non-tail recursive +// (Note: need mutable Cons tails for tail-recursive map) +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)) }; + } + }; + rec(l) +}; + +// filter; non-tail recursive +// (Note: need mutable Cons tails for tail-recursive version) +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) } }; + } + }; + rec(l) +}; + +// map-and-filter; non-tail recursive +// (Note: need mutable Cons tails for tail-recursive version) +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)) }; + } + }; + } + }; + rec(l) +}; + +// append; non-tail recursive +// (Note: need mutable Cons tails for tail-recursive version) +func append(l : List, m : List) : List = { + func rec(l : List) : List { + switch l { + case null { m }; + case (?(h,t)) {?(h,rec(l))}; + } + }; + rec(l) +}; + +// (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)) }; + } +}; + +// take; non-tail recursive +// (Note: need mutable Cons tails for tail-recursive version) +func take(l : List, n:Nat) : List = { + switch (l, n) { + case (_, 0) { null }; + case (null,_) { null }; + case (?(h, t), m) {?(h, take(t, m - 1))}; + } +}; + +// drop; tail recursive +func drop(l : List, n:Nat) : List = { + switch (l, n) { + case (l_, 0) { l_ }; + case (null, _) { null }; + case ((?(h,t)), m) { drop(t, m - 1) }; + } +}; + +// fold list left-to-right using f; tail recursive +func foldl(l : List, a:S, f:(T,S) -> S) : S = { + func rec(l:List, a:S) : S = { + switch l { + case null { a }; + case (?(h,t)) { rec(t, f(h,a)) }; + } + }; + rec(l,a) +}; + +// fold list right-to-left using f; tail recursive +func foldr(l : List, a:S, f:(T,S) -> S) : S = { + func rec(l:List) : S = { + switch l { + case null { a }; + case (?(h,t)) { f(h, rec(t)) }; + } + }; + rec(l) +}; + +// test if there exists list element for which given predicate is true +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) } }; + } + }; + rec(l) +}; + +// test if given predicate is true for all list elements +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) } }; + } + }; + rec(l) +}; + +// Called 'collate' in SML basis library +// Here, we e use a 'less-than-or-eq' relation, not a 3-valued 'order' type. +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)) + } + }; + } + }; + rec(l1, l2) +}; + +////////////////////////////////////////////////////////////////// + +// # Example usage + +type X = Nat; +func opnat_eq(a : ?Nat, b : ?Nat) : Bool { + switch (a, b) { + case (null, null) { true }; + case (?aaa, ?bbb) { aaa == bbb }; + case (_, _ ) { false }; + } +}; +func opnat_isnull(a : ?Nat) : Bool { + switch a { + case (null) { true }; + case (?aaa) { false }; + } +}; + +// ## Construction +let l1 = nil(); +let l2 = push(2, l1); +let l3 = push(3, l2); + +// ## Projection -- use nth_ +assert (opnat_eq(nth_(l3, 0), ?3)); +assert (opnat_eq(nth_(l3, 1), ?2)); +assert (opnat_eq(nth_(l3, 2), null)); +assert (opnat_eq (hd(l3), ?3)); +assert (opnat_eq (hd(l2), ?2)); +assert (opnat_isnull(hd(l1))); + +/* +// ## Projection -- use nth +assert (opnat_eq(nth(l3, 0), ?3)); +assert (opnat_eq(nth(l3, 1), ?2)); +assert (opnat_eq(nth(l3, 2), null)); +assert (opnat_eq (hd(l3), ?3)); +assert (opnat_eq (hd(l2), ?2)); +assert (opnat_isnull(hd(l1))); +*/ + +// ## Deconstruction +let (a1, t1) = pop(l3); +assert (opnat_eq(a1, ?3)); +let (a2, t2) = pop(l2); +assert (opnat_eq(a2, ?2)); +let (a3, t3) = pop(l1); +assert (opnat_eq(a3, null)); +assert (isnil(t3)); + +// ## List functions +assert (len(l1) == 0); +assert (len(l2) == 1); +assert (len(l3) == 2); + +// ## List functions +assert (len(l1) == 0); +assert (len(l2) == 1); +assert (len(l3) == 2); + + +//////////////////////////////////////////////////////////////// +// For comparison: +// +// SML Basis Library Interface +// http://sml-family.org/Basis/list.html +// +// datatype 'a list = nil | :: of 'a * 'a list +// exception Empty +// +// Done? +// ----------------------------------------------------------------- +// x val null : 'a list -> bool +// x val length : 'a list -> int +// x val @ : 'a list * 'a list -> 'a list +// x val hd : 'a list -> 'a +// x val tl : 'a list -> 'a list +// x val last : 'a list -> 'a +// ??? val getItem : 'a list -> ('a * 'a list) option +// x val nth : 'a list * int -> 'a +// x val take : 'a list * int -> 'a list +// x val drop : 'a list * int -> 'a list +// x val rev : 'a list -> 'a list +// val concat : 'a list list -> 'a list +// x val revAppend : 'a list * 'a list -> 'a list +// x val app : ('a -> unit) -> 'a list -> unit +// x val map : ('a -> 'b) -> 'a list -> 'b list +// x val mapPartial : ('a -> 'b option) -> 'a list -> 'b list +// val find : ('a -> bool) -> 'a list -> 'a option +// x val filter : ('a -> bool) -> 'a list -> 'a list +// val partition : ('a -> bool) +// -> 'a list -> 'a list * 'a list +// x val foldl : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b +// x val foldr : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b +// x val exists : ('a -> bool) -> 'a list -> bool +// x val all : ('a -> bool) -> 'a list -> bool +// val tabulate : int * (int -> 'a) -> 'a list +// x val collate : ('a * 'a -> order) +// -> 'a list * 'a list -> order +// +//////////////////////////////////////////////////////////// From b97eb67822a9b1b6e90082bff24de66e6e658654 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 4 Feb 2019 15:09:52 -0700 Subject: [PATCH 02/52] done with SML basis List module; starting streams (aka lazy lists) --- samples/collections/list.as | 97 +++++++++++++++++++++++++++++------ samples/collections/stream.as | 19 +++++++ samples/collections/thunk.as | 40 +++++++++++++++ 3 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 samples/collections/stream.as create mode 100644 samples/collections/thunk.as diff --git a/samples/collections/list.as b/samples/collections/list.as index fce8a79b06a..b68241d3960 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -1,16 +1,26 @@ -/* - This file represents a kind of "warm up" for creating more involved - collections, such as hash tables (which use linked lists internally) - and persistant maps, which will follow similar functional prog - patterns. +/* + * Lists, a la functional programming, in ActorScript. */ -// TODO-Matthew: Look at SML Basis Library; Look at OCaml List library. -// Write: -// - iterator objects, for use in 'for ... in ...' patterns +// Done: +// +// - standard list definition // - standard list recursors: foldl, foldr, iter // - standard higher-order combinators: map, filter, etc. +// - (Every function here: http://sml-family.org/Basis/list.html) + +// TODO-Matthew: File issues: +// +// - 'assert_unit' vs 'assert_any' (related note: 'any' vs 'none') +// - apply type args, but no actual args? (should be ok, and zero cost, right?) +// - unhelpful error message around conditional parens (search for XXX below) + +// TODO-Matthew: Write: +// +// - iterator objects, for use in 'for ... in ...' patterns // - lists+pairs: zip, split, etc +// - regression tests for everything that is below + // polymorphic linked lists type List = ?(T, List); @@ -191,6 +201,19 @@ func append(l : List, m : List) : List = { rec(l) }; +// concat (aka "list join"); tail recursive, but requires "two passes" +func concat(l : List>) : List = { + // 1/2: fold from left to right, reverse-appending the sublists... + // XXX -- I'd like to write this (shorter) version: + // let r = foldl, List>(l, null, revAppend); + let r = + { let f = func(a:List, b:List) : List { revAppend(a,b) }; + foldl, List>(l, null, f) + }; + // 2/2: ...re-reverse the elements, to their original order: + rev(r) +}; + // (See SML Basis library); tail recursive func revAppend(l1 : List, l2 : List) : List = { switch l1 { @@ -240,6 +263,17 @@ func foldr(l : List, a:S, f:(T,S) -> S) : S = { rec(l) }; +// test if there exists list element for which given predicate is true +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) } }; + } + }; + rec(l) +}; + // test if there exists list element for which given predicate is true func exists(l: List, f:T -> Bool) : Bool = { func rec(l:List) : Bool { @@ -283,6 +317,34 @@ func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { rec(l1, l2) }; +// using a predicate, create two lists from one: the "true" list, and the "false" list. +// (See SML basis library); non-tail recursive +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)) + } + }; + } + }; + rec(l) +}; + +// generate a list based on a length, and a function from list index to list element; +// (See SML basis library); non-tail recursive +func tabulate(n:Nat, f:Nat -> T) : List { + func rec(i:Nat) : List { + if (i == n) { null } else { ?(f(i), rec(i+1)) } + }; + rec(0) +}; + ////////////////////////////////////////////////////////////////// // # Example usage @@ -344,6 +406,9 @@ assert (len(l1) == 0); assert (len(l2) == 1); assert (len(l3) == 2); +// +// TODO: Write list equaliy test; write tests for each function +// //////////////////////////////////////////////////////////////// // For comparison: @@ -354,7 +419,7 @@ assert (len(l3) == 2); // datatype 'a list = nil | :: of 'a * 'a list // exception Empty // -// Done? +// Done in AS (marked "x"): // ----------------------------------------------------------------- // x val null : 'a list -> bool // x val length : 'a list -> int @@ -362,26 +427,24 @@ assert (len(l3) == 2); // x val hd : 'a list -> 'a // x val tl : 'a list -> 'a list // x val last : 'a list -> 'a -// ??? val getItem : 'a list -> ('a * 'a list) option +// ??? val getItem : 'a list -> ('a * 'a list) option --------- Q: What does this function "do"? Is it just witnessing a type isomorphism? // x val nth : 'a list * int -> 'a // x val take : 'a list * int -> 'a list // x val drop : 'a list * int -> 'a list // x val rev : 'a list -> 'a list -// val concat : 'a list list -> 'a list +// x val concat : 'a list list -> 'a list // x val revAppend : 'a list * 'a list -> 'a list // x val app : ('a -> unit) -> 'a list -> unit // x val map : ('a -> 'b) -> 'a list -> 'b list // x val mapPartial : ('a -> 'b option) -> 'a list -> 'b list -// val find : ('a -> bool) -> 'a list -> 'a option +// x val find : ('a -> bool) -> 'a list -> 'a option // x val filter : ('a -> bool) -> 'a list -> 'a list -// val partition : ('a -> bool) -// -> 'a list -> 'a list * 'a list +// x val partition : ('a -> bool) -> 'a list -> 'a list * 'a list // x val foldl : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b // x val foldr : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b // x val exists : ('a -> bool) -> 'a list -> bool // x val all : ('a -> bool) -> 'a list -> bool -// val tabulate : int * (int -> 'a) -> 'a list -// x val collate : ('a * 'a -> order) -// -> 'a list * 'a list -> order +// x val tabulate : int * (int -> 'a) -> 'a list +// x val collate : ('a * 'a -> order) -> 'a list * 'a list -> order // //////////////////////////////////////////////////////////// diff --git a/samples/collections/stream.as b/samples/collections/stream.as new file mode 100644 index 00000000000..7f2c3016f33 --- /dev/null +++ b/samples/collections/stream.as @@ -0,0 +1,19 @@ +/* + * Streams, a la functional programming, in ActorScript. + */ + +// Thunks are not primitive in AS, +// ..but we can encode them as objects with a force method: +type Thk = {force:() -> T}; + +// "Stream head optional" is the (optional) head of the stream; The +// "tail" of the stream head is a thunk, that when forced, produces +// the next stream head, or `null`. +type Sho = ?(T,Thk>); + +// A "stream" contains a value "today", and a thunk for the value +// "tomorrow". These streams have optional endings, represented by +// the `Sho` type; they may or may not end. +type Stream = (T,Thk>); + + diff --git a/samples/collections/thunk.as b/samples/collections/thunk.as new file mode 100644 index 00000000000..e3a39ce6e66 --- /dev/null +++ b/samples/collections/thunk.as @@ -0,0 +1,40 @@ +/* + * Thunks, a la functional programming, in ActorScript. + */ + +// Thunks are not primitive in AS, +// ..but we can encode them as objects with a force method: +type Thk = {force:() -> T}; + +// lift a value into a "value-producing thunk" +func lift(a:T) : Thk = + new { force() : T { a } }; + +// apply a function to a thunk's value +func app(f:T->S, x:Thk) : Thk { + new { force() : S { f(x.force()) } } +}; + +// pair two thunks' values +/* + // XXX I don't understand this type error: + // + // type error, expression of type + // () -> (T/23, S/4) + // cannot produce expected type + // () -> ((T/23, S/4)) + +func pair(x:Thk, y:Thk) : Thk<(T,S)> { + new { force() : (T,S) { (x.force(), y.force()) } } +}; +*/ + +// project first from a pair-valued thunk +func fst(x:Thk<(T,S)>) : Thk { + new { force() : T { x.force().0 } } +}; + +// project second from a pair-valued thunk +func snd(x:Thk<(T,S)>) : Thk { + new { force() : S { x.force().1 } } +}; From 2012f695923e71e42f90ff0b99c30e92c1b6f4fc Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 4 Feb 2019 15:20:46 -0700 Subject: [PATCH 03/52] readme stub --- samples/collections/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 samples/collections/README.md diff --git a/samples/collections/README.md b/samples/collections/README.md new file mode 100644 index 00000000000..6952420bd6a --- /dev/null +++ b/samples/collections/README.md @@ -0,0 +1,8 @@ +[See #127](https://github.com/dfinity-lab/actorscript/issues/127) + +Modules' statuses +================== +[x] List: On par with SML basis library. +[ ] Thunk: Type def and some operations are done; XXX: see `pair` function for a strange type error +[ ] Stream: Type def done; all operations are pending... + From 35cf9aa97acfb2c0e8b50236092e15e7bf889485 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 4 Feb 2019 16:04:28 -0700 Subject: [PATCH 04/52] nits --- samples/collections/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/collections/README.md b/samples/collections/README.md index 6952420bd6a..840bcf1cc78 100644 --- a/samples/collections/README.md +++ b/samples/collections/README.md @@ -2,7 +2,7 @@ Modules' statuses ================== -[x] List: On par with SML basis library. -[ ] Thunk: Type def and some operations are done; XXX: see `pair` function for a strange type error -[ ] Stream: Type def done; all operations are pending... +[x] **List**: On par with [`List` module from SML Basis library](http://sml-family.org/Basis/list.html). +[ ] **Thunk**: Type def and some operations are done; XXX: see `pair` function for a strange type error. +[ ] **Stream**: Type def done; all operations are pending... From 5a470d79c05000e71f8bef07330540e17073b137 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 4 Feb 2019 16:33:10 -0700 Subject: [PATCH 05/52] a stream function: mapfilter --- samples/collections/README.md | 2 +- samples/collections/stream.as | 70 ++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/samples/collections/README.md b/samples/collections/README.md index 840bcf1cc78..d8c9fba2bf9 100644 --- a/samples/collections/README.md +++ b/samples/collections/README.md @@ -4,5 +4,5 @@ Modules' statuses ================== [x] **List**: On par with [`List` module from SML Basis library](http://sml-family.org/Basis/list.html). [ ] **Thunk**: Type def and some operations are done; XXX: see `pair` function for a strange type error. -[ ] **Stream**: Type def done; all operations are pending... +[ ] **Stream**: Type def done; most operations are pending... diff --git a/samples/collections/stream.as b/samples/collections/stream.as index 7f2c3016f33..0564a083bb3 100644 --- a/samples/collections/stream.as +++ b/samples/collections/stream.as @@ -1,19 +1,69 @@ -/* +/* * Streams, a la functional programming, in ActorScript. + * + * Streams are lazy lists that may or may not end. + * If non-empty, a stream contains a value "today", + * and a thunk for the value "tomorrow", if any. + * */ -// Thunks are not primitive in AS, +// Done: +// +// - standard stream definition (well, two versions) +// - standard higher-order combinator: mapfilter + +// TODO-Matthew: Write: +// +// - standard stream combinators: take, drop, merge, sort, etc... +// - iterator objects, for use in 'for ... in ...' patterns +// - streams+pairs: zip, split, etc +// - regression tests for everything that is below + +// TODO-Matthew: File issues: +// +// - unhelpful error message around variable shadowing (search for XXX below) +// + +// Thunks are not primitive in AS, // ..but we can encode them as objects with a force method: type Thk = {force:() -> T}; -// "Stream head optional" is the (optional) head of the stream; The -// "tail" of the stream head is a thunk, that when forced, produces -// the next stream head, or `null`. -type Sho = ?(T,Thk>); +// A "Stream Head" ("Sh") is the head of the stream, which _always_ +// contains a value "today"; Its "tail" is a thunk that produces the +// next stream head ("tomorrow"), or `null`. +type Sh = (T, ?Thk>); -// A "stream" contains a value "today", and a thunk for the value -// "tomorrow". These streams have optional endings, represented by -// the `Sho` type; they may or may not end. -type Stream = (T,Thk>); +// "Optional Stream Head" (Osh) is the optional head of the stream. +// This type is related to Sh, but is not equivalent. +type Osh = ?(T, Thk>); +// type Stream = +// ??? Sh or Osh +// Q: Which is more more "conventional?" +// +// map-and-filter; tail recursive. +// acts eagerly when the predicate fails, +// and lazily when it succeeds. +func mapfilter(l : Osh, f:T -> ?S) : Osh = { + func rec(l : Osh) : Osh { + switch l { + case null { null }; + case (?(h,t)) { + switch (f(h)) { + case null { rec(t.force()) }; + case (?h_){ + // XXX -- When we shadow `t` we get a strange/wrong type error: + // + // let t = new{force():Osh{ rec(t.force()) }}; + // ?(h_,t) + // + let s = new{force():Osh{ rec(t.force()) }}; + ?(h_,s) + }; + } + }; + } + }; + rec(l) +}; From e3da12b155c9ceafbe196238855b60c6357c9e61 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 4 Feb 2019 16:34:26 -0700 Subject: [PATCH 06/52] another stream function: map --- samples/collections/stream.as | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/samples/collections/stream.as b/samples/collections/stream.as index 0564a083bb3..97782881471 100644 --- a/samples/collections/stream.as +++ b/samples/collections/stream.as @@ -67,3 +67,17 @@ func mapfilter(l : Osh, f:T -> ?S) : Osh = { }; rec(l) }; + +// stream map; tail recursive. lazily. +func map(l : Osh, f:T -> S) : Osh = { + func rec(l : Osh) : Osh { + switch l { + case null { null }; + case (?(h,t)) { + let s = new{force():Osh{ rec(t.force()) }}; + ?(f(h),s) + }; + } + }; + rec(l) +}; From 6c9ad8e11eada73d674380f36bcabd5956af7da3 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 4 Feb 2019 16:49:41 -0700 Subject: [PATCH 07/52] stream function 'merge' --- samples/collections/stream.as | 80 +++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/samples/collections/stream.as b/samples/collections/stream.as index 97782881471..be1c22f594d 100644 --- a/samples/collections/stream.as +++ b/samples/collections/stream.as @@ -10,11 +10,11 @@ // Done: // // - standard stream definition (well, two versions) -// - standard higher-order combinator: mapfilter +// - standard higher-order combinators: map, mapfilter, merge // TODO-Matthew: Write: // -// - standard stream combinators: take, drop, merge, sort, etc... +// - (more) stream combinators: take, drop, sort, etc... // - iterator objects, for use in 'for ... in ...' patterns // - streams+pairs: zip, split, etc // - regression tests for everything that is below @@ -42,42 +42,68 @@ type Osh = ?(T, Thk>); // Q: Which is more more "conventional?" // -// map-and-filter; tail recursive. -// acts eagerly when the predicate fails, -// and lazily when it succeeds. -func mapfilter(l : Osh, f:T -> ?S) : Osh = { +// stream map; tail recursive. lazy. +func map(l : Osh, f:T -> S) : Osh = { func rec(l : Osh) : Osh { switch l { case null { null }; case (?(h,t)) { - switch (f(h)) { - case null { rec(t.force()) }; - case (?h_){ - // XXX -- When we shadow `t` we get a strange/wrong type error: - // - // let t = new{force():Osh{ rec(t.force()) }}; - // ?(h_,t) - // - let s = new{force():Osh{ rec(t.force()) }}; - ?(h_,s) - }; - } + let s = new{force():Osh{ rec(t.force()) }}; + ?(f(h),s) }; } }; rec(l) }; -// stream map; tail recursive. lazily. -func map(l : Osh, f:T -> S) : Osh = { - func rec(l : Osh) : Osh { - switch l { - case null { null }; - case (?(h,t)) { - let s = new{force():Osh{ rec(t.force()) }}; - ?(f(h),s) +// stream merge (aka "collate"); tail recursive. lazy. +func merge(s1 : Osh, s2 : Osh, f:(T,T) -> Bool) : Osh = { + func rec(s1 : Osh, s2 : Osh) : Osh { + switch (s1, s2) { + case (null, _) { s2 }; + case (_, null) { s1 }; + case (?(h1,t1), ?(h2,t2)) { + if (f(h1,h2)) { + // case: h1 is "today", h2 is "later"... + let s = new{force():Osh{ rec(t1.force(), s2) }}; + ?(h1,s) + } else { + // case: h2 is "today", h2 is "later"... + let s = new{force():Osh{ rec(s1, t2.force()) }}; + ?(h2,s) + } + } + } + }; + rec(s1, s2) +}; + +// stream map-and-filter; tail recursive. +// acts eagerly when the predicate fails, +// and lazily when it succeeds. +func mapfilter(l : Osh, f:T -> ?S) : Osh = { + func rec(s : Osh) : Osh { + switch s { + case null { null }; + case (?(h,t)) { + switch (f(h)) { + case null { rec(t.force()) }; + case (?h_){ + // XXX -- When we shadow `t` we get a strange/wrong type error: + // + // type error, expression of type + // Osh = ?(S/3, Thk>) + // cannot produce expected type + // ?(T/28, Thk>) + // + // let t = new{force():Osh{ rec(t.force()) }}; + // ?(h_,t) + let s = new{force():Osh{ rec(t.force()) }}; + ?(h_,s) + }; + } }; } }; rec(l) -}; +} From 4686876b23fe565a89042ce1bab78ea87c077c31 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 5 Feb 2019 05:29:04 -0700 Subject: [PATCH 08/52] nits; typos --- samples/collections/list.as | 2 +- samples/collections/stream.as | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/collections/list.as b/samples/collections/list.as index b68241d3960..fe4cfd11930 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -299,7 +299,7 @@ func all(l: List, f:T -> Bool) : Bool = { }; // Called 'collate' in SML basis library -// Here, we e use a 'less-than-or-eq' relation, not a 3-valued 'order' type. +// (But we use a 'less-than-or-eq' relation, not a 3-valued 'order' type, as in SML). func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { func rec(l1: List, l2: List) : List { switch (l1, l2) { diff --git a/samples/collections/stream.as b/samples/collections/stream.as index be1c22f594d..9945016de04 100644 --- a/samples/collections/stream.as +++ b/samples/collections/stream.as @@ -42,7 +42,7 @@ type Osh = ?(T, Thk>); // Q: Which is more more "conventional?" // -// stream map; tail recursive. lazy. +// stream map; trivially tail recursive. lazy. func map(l : Osh, f:T -> S) : Osh = { func rec(l : Osh) : Osh { switch l { @@ -56,7 +56,7 @@ func map(l : Osh, f:T -> S) : Osh = { rec(l) }; -// stream merge (aka "collate"); tail recursive. lazy. +// stream merge (aka "collate"); trivially tail recursive. lazy. func merge(s1 : Osh, s2 : Osh, f:(T,T) -> Bool) : Osh = { func rec(s1 : Osh, s2 : Osh) : Osh { switch (s1, s2) { From 4d3f8647172511d10c953bfec5c0377746be5a3f Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 5 Feb 2019 10:30:04 -0700 Subject: [PATCH 09/52] address feedback from Andreas --- samples/collections/README.md | 17 ++++- samples/collections/list.as | 137 ++++++++++++++++++---------------- 2 files changed, 85 insertions(+), 69 deletions(-) diff --git a/samples/collections/README.md b/samples/collections/README.md index d8c9fba2bf9..f7b51b22f97 100644 --- a/samples/collections/README.md +++ b/samples/collections/README.md @@ -1,8 +1,19 @@ [See #127](https://github.com/dfinity-lab/actorscript/issues/127) -Modules' statuses +Critical modules ================== -[x] **List**: On par with [`List` module from SML Basis library](http://sml-family.org/Basis/list.html). -[ ] **Thunk**: Type def and some operations are done; XXX: see `pair` function for a strange type error. +[x] **List**: See [`List` module from SML Basis library](http://sml-family.org/Basis/list.html). +[ ] **Hashtrie**: Persistent maps, as functional hash tries. +[ ] **Hashtable**: Mutable maps, as imperative hash tables. + +Secondary modules +================== +These modules _may_ be useful in the collections library: [ ] **Stream**: Type def done; most operations are pending... +Other modules +================== +These modules are merely exercises (toys/examples), and _not_ essential to the collections library: +[ ] **Thunk**: Type def and some operations are done; XXX: see `pair` function for a strange type error. + + diff --git a/samples/collections/list.as b/samples/collections/list.as index fe4cfd11930..c12f63e6299 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -30,7 +30,7 @@ func nil() : List = null; // test for empty list -func isnil(l : List) : Bool { +func isNil(l : List) : Bool { switch l { case null { true }; case _ { false }; @@ -41,51 +41,28 @@ func isnil(l : List) : Bool { func push(x : T, l : List) : List = ?(x, l); -// get head of list -func hd(l : List) : ?T = { +// XXX: deprecated (use pattern matching instead) +func head(l : List) : ?T = { switch l { case null { null }; case (?(h, _)) { ?h }; } }; -// get tail of list, as a list -func tl(l : List) : List = { +// XXX: deprecated (use pattern matching instead) +func tail(l : List) : List = { switch l { case null { null }; case (?(_, t)) { t }; } }; -// get tail of list, as an optional list -func tlo(l : List) : ?List = { - switch l { - case null { null }; - case (?(_, t)) { ?t }; - } -}; - -/* -// last element (SML Basis library); tail recursive -func last(l : List) : T = { - switch l { - // XXX - // Q: What's the type of 'assert false'? - // Shouldn't it be 'Any', and not '()'? - // - //case null { assert false }; - case (?(x,null)) { x }; - case (?(_,t)) { last(t) }; - } -}; -*/ - // last element, optionally; tail recursive -func lasto(l : List) : ?T = { +func last(l : List) : ?T = { switch l { case null { null }; case (?(x,null)) { ?x }; - case (?(_,t)) { lasto(t) }; + case (?(_,t)) { last(t) }; } }; @@ -111,21 +88,12 @@ func len(l : List) : Nat = { // array-like list access, but in linear time; tail recursive func nth(l : List, n : Nat) : ?T = { switch (n, l) { - case (0, _) { hd(l) }; case (_, null) { null }; + case (0, ?(h,t)) { ?h }; case (_, ?(_,t)) { nth(t, n - 1) }; } }; -// array-like list access, but in linear time; tail recursive -func nth_(l : List, n : Nat) : ?T = { - switch (n, tlo(l)) { - case (0, _) { hd(l) }; - case (_, null) { null }; - case (_, ?t) { nth_(t, n - 1) }; - } -}; - // reverse; tail recursive func rev(l : List) : List = { func rec(l : List, r : List) : List { @@ -174,7 +142,7 @@ func filter(l : List, f:T -> Bool) : List = { // map-and-filter; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func mapfilter(l : List, f:T -> ?S) : List = { +func mapFilter(l : List, f:T -> ?S) : List = { func rec(l : List) : List { switch l { case null { null }; @@ -204,11 +172,9 @@ func append(l : List, m : List) : List = { // concat (aka "list join"); tail recursive, but requires "two passes" func concat(l : List>) : List = { // 1/2: fold from left to right, reverse-appending the sublists... - // XXX -- I'd like to write this (shorter) version: - // let r = foldl, List>(l, null, revAppend); let r = { let f = func(a:List, b:List) : List { revAppend(a,b) }; - foldl, List>(l, null, f) + foldLeft, List>(l, null, f) }; // 2/2: ...re-reverse the elements, to their original order: rev(r) @@ -242,7 +208,7 @@ func drop(l : List, n:Nat) : List = { }; // fold list left-to-right using f; tail recursive -func foldl(l : List, a:S, f:(T,S) -> S) : S = { +func foldLeft(l : List, a:S, f:(T,S) -> S) : S = { func rec(l:List, a:S) : S = { switch l { case null { a }; @@ -252,8 +218,8 @@ func foldl(l : List, a:S, f:(T,S) -> S) : S = { rec(l,a) }; -// fold list right-to-left using f; tail recursive -func foldr(l : List, a:S, f:(T,S) -> S) : S = { +// fold list right-to-left using f; non-tail recursive +func foldRight(l : List, a:S, f:(T,S) -> S) : S = { func rec(l:List) : S = { switch l { case null { a }; @@ -298,8 +264,7 @@ func all(l: List, f:T -> Bool) : Bool = { rec(l) }; -// Called 'collate' in SML basis library -// (But we use a 'less-than-or-eq' relation, not a 3-valued 'order' type, as in SML). +// Given two ordered lists, merge them into a single ordered list func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { func rec(l1: List, l2: List) : List { switch (l1, l2) { @@ -317,6 +282,46 @@ func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { rec(l1, l2) }; +// Compare two lists lexicographic` ordering. tail recursive. +// XXX: Eventually, follow `collate` design from SML Basis, with real sum types, use 3-valued `order` type here. +// +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 + } + }; + } + }; + rec(l1, l2) +}; + +// Compare two lists for equality. tail recursive. +// `isEq(l1, l2)` =equiv= `lessThanEq(l1,l2) && lessThanEq(l2,l1)`, but the former is more efficient. +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 + } + }; + } + }; + rec(l1, l2) +}; + // using a predicate, create two lists from one: the "true" list, and the "false" list. // (See SML basis library); non-tail recursive func partition(l: List, f:T -> Bool) : (List, List) { @@ -350,7 +355,7 @@ func tabulate(n:Nat, f:Nat -> T) : List { // # Example usage type X = Nat; -func opnat_eq(a : ?Nat, b : ?Nat) : Bool { +func opnatEq(a : ?Nat, b : ?Nat) : Bool { switch (a, b) { case (null, null) { true }; case (?aaa, ?bbb) { aaa == bbb }; @@ -369,32 +374,32 @@ let l1 = nil(); let l2 = push(2, l1); let l3 = push(3, l2); -// ## Projection -- use nth_ -assert (opnat_eq(nth_(l3, 0), ?3)); -assert (opnat_eq(nth_(l3, 1), ?2)); -assert (opnat_eq(nth_(l3, 2), null)); -assert (opnat_eq (hd(l3), ?3)); -assert (opnat_eq (hd(l2), ?2)); -assert (opnat_isnull(hd(l1))); +// ## Projection -- use nth +assert (opnatEq(nth(l3, 0), ?3)); +assert (opnatEq(nth(l3, 1), ?2)); +assert (opnatEq(nth(l3, 2), null)); +//assert (opnatEq (hd(l3), ?3)); +//assert (opnatEq (hd(l2), ?2)); +//assert (opnat_isnull(hd(l1))); /* // ## Projection -- use nth -assert (opnat_eq(nth(l3, 0), ?3)); -assert (opnat_eq(nth(l3, 1), ?2)); -assert (opnat_eq(nth(l3, 2), null)); -assert (opnat_eq (hd(l3), ?3)); -assert (opnat_eq (hd(l2), ?2)); +assert (opnatEq(nth(l3, 0), ?3)); +assert (opnatEq(nth(l3, 1), ?2)); +assert (opnatEq(nth(l3, 2), null)); +assert (opnatEq (hd(l3), ?3)); +assert (opnatEq (hd(l2), ?2)); assert (opnat_isnull(hd(l1))); */ // ## Deconstruction let (a1, t1) = pop(l3); -assert (opnat_eq(a1, ?3)); +assert (opnatEq(a1, ?3)); let (a2, t2) = pop(l2); -assert (opnat_eq(a2, ?2)); +assert (opnatEq(a2, ?2)); let (a3, t3) = pop(l1); -assert (opnat_eq(a3, null)); -assert (isnil(t3)); +assert (opnatEq(a3, null)); +assert (isNil(t3)); // ## List functions assert (len(l1) == 0); From 1a839576b5b84331ee422fd93bbbf9439ea71841 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 5 Feb 2019 10:44:24 -0700 Subject: [PATCH 10/52] fix type error, with Andreas's help --- samples/collections/thunk.as | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/samples/collections/thunk.as b/samples/collections/thunk.as index e3a39ce6e66..c13612b7b01 100644 --- a/samples/collections/thunk.as +++ b/samples/collections/thunk.as @@ -16,18 +16,9 @@ func app(f:T->S, x:Thk) : Thk { }; // pair two thunks' values -/* - // XXX I don't understand this type error: - // - // type error, expression of type - // () -> (T/23, S/4) - // cannot produce expected type - // () -> ((T/23, S/4)) - func pair(x:Thk, y:Thk) : Thk<(T,S)> { - new { force() : (T,S) { (x.force(), y.force()) } } + new { force() : ((T,S)) { (x.force(), y.force()) } } }; -*/ // project first from a pair-valued thunk func fst(x:Thk<(T,S)>) : Thk { From 0ebde573f863623713557756c8d7a0aa6636f327 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 5 Feb 2019 10:44:24 -0700 Subject: [PATCH 11/52] fix type error, with Andreas's help --- samples/collections/thunk.as | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/samples/collections/thunk.as b/samples/collections/thunk.as index e3a39ce6e66..c13612b7b01 100644 --- a/samples/collections/thunk.as +++ b/samples/collections/thunk.as @@ -16,18 +16,9 @@ func app(f:T->S, x:Thk) : Thk { }; // pair two thunks' values -/* - // XXX I don't understand this type error: - // - // type error, expression of type - // () -> (T/23, S/4) - // cannot produce expected type - // () -> ((T/23, S/4)) - func pair(x:Thk, y:Thk) : Thk<(T,S)> { - new { force() : (T,S) { (x.force(), y.force()) } } + new { force() : ((T,S)) { (x.force(), y.force()) } } }; -*/ // project first from a pair-valued thunk func fst(x:Thk<(T,S)>) : Thk { From 2b682e33d6e5148bdb14c4a514d45f05e3c3e8f8 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 5 Feb 2019 10:50:06 -0700 Subject: [PATCH 12/52] update readme --- samples/collections/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/collections/README.md b/samples/collections/README.md index f7b51b22f97..243c32394d2 100644 --- a/samples/collections/README.md +++ b/samples/collections/README.md @@ -14,6 +14,6 @@ These modules _may_ be useful in the collections library: Other modules ================== These modules are merely exercises (toys/examples), and _not_ essential to the collections library: -[ ] **Thunk**: Type def and some operations are done; XXX: see `pair` function for a strange type error. +[ ] **Thunk**: Type def and some operations are done. From 5a9149f9fc0e8787983ad01530926cfc837a58fd Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Feb 2019 13:53:13 -0700 Subject: [PATCH 13/52] starting hashtrie; many loose ends remain --- samples/collections/hashtrie.as | 122 ++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 samples/collections/hashtrie.as diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as new file mode 100644 index 00000000000..20e6c3dd536 --- /dev/null +++ b/samples/collections/hashtrie.as @@ -0,0 +1,122 @@ +/* + * Hash Tries. + * + * Functional maps (and sets) whose representation is "canonical", and history independent. + * + * By contrast, AVL Trees, RB Trees, and other representations do not + * enjoy history independence, and are each more complex to implement + * (e.g., each requires "rebalancing"; these trees never do). + * + * 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 + */ + +// Done: +// +// - (hacky) type definition; XXX: need real sum types to clean it up +// - find operation + +// TODO-Matthew: +// +// - insert operation +// - remove operation +// - handle hash collisions gracefully +// +// - iterator objects, for use in 'for ... in ...' patterns +// - regression tests for everything that is below + + +type Hash = Nat; + +// XXX: This Node type is a "sloppy union" between "BinNodes" (left/right fields) and "Leaves" (key/val fields): +type Node = {left:Trie; right:Trie; key:?K; val:?V}; +type Trie = ?Node; + +// Simplifying assumption, for now: All defined paths in the trie have a uniform length, +// the same as the number of bits of a hash, starting at the LSB, that we use for indexing. +// +// - If the number is too low, our expected O(log n) bounds become expected O(n). +// - If the number is too high, we waste constant factors for representing small sets/maps. +// +// TODO: Make this more robust by making this number adaptive for each +// path, and based on the content of the trie along that path. +// +let HASH_BITS = 4; + +// XXX: Until we have real sum types: +func assertIsNull(x : ?X) { + switch x { + case null { assert(true) }; + case (?_) { assert(false) }; + }; +}; + +// XXX: Until we have real sum types: +func assertIsBin(t : Trie) { + switch t { + case null { assert(false) }; + case (?n) { + assertIsNull(n.key); + assertIsNull(n.val); + }; + } +}; + +// XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: +func getLeafKey(t : Node) : K { + assertIsNull>(t.left); + assertIsNull>(t.right); + switch (t.key) { + case (?k) { k }; + case null { getLeafKey(t) }; + } +}; + +// XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: +func getLeafVal(t : Node) : ?V { + assertIsNull>(t.left); + assertIsNull>(t.right); + t.val +}; + +func getBit(n:Nat, pos:Nat) : Bool { + // Q: Are there bit-level operations planned for the future? + // XXX: Temporary infinite loop here, as a "placeholder": + getBit(n, pos) +}; + +func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { + // 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 ) { + assertIsBin(t); + switch t { + case null { + // the trie may be "sparse" along paths leading to no keys, and may end early. + null + }; + case (?n) { + let bit = getBit(k_hash, bitpos); + if 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 + } + }; + } + } + }; + rec(t, 0) +}; From c8629f5333a7e76f6b9c8008597437dc8aeba1e5 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Feb 2019 15:12:53 -0700 Subject: [PATCH 14/52] hashtrie construction operations; everything needs testing now --- samples/collections/hashtrie.as | 93 ++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 20e6c3dd536..28c493c674a 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -1,13 +1,13 @@ /* - * Hash Tries. - * + * Hash Tries. + * * Functional maps (and sets) whose representation is "canonical", and history independent. * * By contrast, AVL Trees, RB Trees, and other representations do not * enjoy history independence, and are each more complex to implement * (e.g., each requires "rebalancing"; these trees never do). * - * See this POPL 1989 paper (Section 6): + * 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 @@ -17,22 +17,24 @@ // // - (hacky) type definition; XXX: need real sum types to clean it up // - find operation +// - insert operation +// - remove operation +// - replace operation (remove+insert via a single traversal) // TODO-Matthew: // -// - insert operation -// - remove operation +// - regression tests for everything that is below +// // - handle hash collisions gracefully // // - iterator objects, for use in 'for ... in ...' patterns -// - regression tests for everything that is below type Hash = Nat; // XXX: This Node type is a "sloppy union" between "BinNodes" (left/right fields) and "Leaves" (key/val fields): type Node = {left:Trie; right:Trie; key:?K; val:?V}; -type Trie = ?Node; +type Trie = ?Node; // Simplifying assumption, for now: All defined paths in the trie have a uniform length, // the same as the number of bits of a hash, starting at the LSB, that we use for indexing. @@ -57,7 +59,7 @@ func assertIsNull(x : ?X) { func assertIsBin(t : Trie) { switch t { case null { assert(false) }; - case (?n) { + case (?n) { assertIsNull(n.key); assertIsNull(n.val); }; @@ -87,15 +89,82 @@ func getBit(n:Nat, pos:Nat) : Bool { getBit(n, pos) }; +func empty() : Trie = + null +; + +// helper function for constructing new paths of uniform length +func buildNewPath(bitpos:Nat, k:K, k_hash:Hash, 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 = getBit(k_hash, bitpos); + if bit { ?(new {left=path; right=null; key=null; val=null}) } + else { ?(new {left=null; right=path; key=null; val=null}) } + } else { + // create new leaf for (k,v) pair + ?(new {left=null; right=null; key=?k; val=ov }) + } + }; + 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) { + // 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 ) { + assertIsBin(t); + switch t { + case null { (buildNewPath(bitpos, k, k_hash, v), null) }; + case (?n) { + let bit = getBit(k_hash, bitpos); + // rebuild either the left or right path with the inserted (k,v) pair + if 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_) + } + }; + } + } 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) + }; + } + } + }; + 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) +}; + +// insert 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) +}; + +// 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 { // 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 ) { assertIsBin(t); switch t { - case null { + case null { // the trie may be "sparse" along paths leading to no keys, and may end early. - null + null }; case (?n) { let bit = getBit(k_hash, bitpos); @@ -105,9 +174,9 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { } } else { // No more walking; we should be at a leaf now, by construction invariants. - switch t { + switch t { case null { null }; - case (?l) { + case (?l) { // TODO: Permit hash collisions by walking a list/array of KV pairs in each leaf: if (keq(getLeafKey(l), k)) { getLeafVal(l) From d4f9e919bd77ab582cd390521f245a46896d5583 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Feb 2019 15:30:08 -0700 Subject: [PATCH 15/52] minor: md formatting --- samples/collections/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/collections/README.md b/samples/collections/README.md index 243c32394d2..6c8bf969500 100644 --- a/samples/collections/README.md +++ b/samples/collections/README.md @@ -2,18 +2,18 @@ Critical modules ================== -[x] **List**: See [`List` module from SML Basis library](http://sml-family.org/Basis/list.html). -[ ] **Hashtrie**: Persistent maps, as functional hash tries. -[ ] **Hashtable**: Mutable maps, as imperative hash tables. + - [x] **List**: See [`List` module from SML Basis library](http://sml-family.org/Basis/list.html). + - [ ] **Hashtrie**: Persistent maps, as functional hash tries. + - [ ] **Hashtable**: Mutable maps, as imperative hash tables. Secondary modules ================== These modules _may_ be useful in the collections library: -[ ] **Stream**: Type def done; most operations are pending... + - [ ] **Stream**: Type def done; most operations are pending... Other modules ================== These modules are merely exercises (toys/examples), and _not_ essential to the collections library: -[ ] **Thunk**: Type def and some operations are done. + - [ ] **Thunk**: Type def and some operations are done. From 5c25eeddf21d50691f142be0075c6d24c846e091 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Feb 2019 15:32:10 -0700 Subject: [PATCH 16/52] minor: typo --- samples/collections/hashtrie.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 28c493c674a..95b8aa837b8 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -150,7 +150,7 @@ func insert(t : Trie, k:K, k_hash:Hash, v:V) : (Trie, ?V) { replace(t, k, k_hash, ?v) }; -// insert the given key's value in the trie; return the new trie +// 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) }; From a978eb79844b87ec87f4e21fb91eb61777c7ec2f Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Feb 2019 16:05:01 -0700 Subject: [PATCH 17/52] minor --- samples/collections/hashtrie.as | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 95b8aa837b8..f929568635c 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -87,6 +87,7 @@ func getBit(n:Nat, pos:Nat) : Bool { // Q: Are there bit-level operations planned for the future? // XXX: Temporary infinite loop here, as a "placeholder": getBit(n, pos) + //n & (1 << pos) }; func empty() : Trie = From 06b19fea2cd091642db420d95f9f7122800b8ef6 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 11 Feb 2019 17:15:26 -0800 Subject: [PATCH 18/52] encode functional sets as hashtries; run a baby test, encoding hashes as boolean lists --- samples/collections/hashtrie.as | 76 ++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index f929568635c..eb242076be2 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -29,8 +29,9 @@ // // - iterator objects, for use in 'for ... in ...' patterns - -type Hash = Nat; +// TODO: Replace this definition WordX, for some X, once we have these types in AS. +type Hash = ?(Bool, Hash); +//type Hash = Word16; // XXX: This Node type is a "sloppy union" between "BinNodes" (left/right fields) and "Leaves" (key/val fields): type Node = {left:Trie; right:Trie; key:?K; val:?V}; @@ -83,11 +84,15 @@ func getLeafVal(t : Node) : ?V { t.val }; -func getBit(n:Nat, pos:Nat) : Bool { - // Q: Are there bit-level operations planned for the future? - // XXX: Temporary infinite loop here, as a "placeholder": - getBit(n, pos) - //n & (1 << pos) +// TODO: Replace with bitwise operations on Words, once we have each of those in AS. For now, we encode hashes as lists of booleans. +func getHashBit(h:Hash, pos:Nat) : Bool { + switch h { + case null { false }; // TODO: Should be an error case + case (?(b, _)) { + if (pos == 0) { b } + else { getHashBit(h, pos-1) } + }; + } }; func empty() : Trie = @@ -100,7 +105,7 @@ func buildNewPath(bitpos:Nat, k:K, k_hash:Hash, ov:?V) : Trie { if ( bitpos < HASH_BITS ) { // create new bin node for this bit of the hash let path = rec(bitpos+1); - let bit = getBit(k_hash, bitpos); + let bit = getHashBit(k_hash, bitpos); if bit { ?(new {left=path; right=null; key=null; val=null}) } else { ?(new {left=null; right=path; key=null; val=null}) } } else { @@ -120,7 +125,7 @@ func replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { switch t { case null { (buildNewPath(bitpos, k, k_hash, v), null) }; case (?n) { - let bit = getBit(k_hash, bitpos); + let bit = getHashBit(k_hash, bitpos); // rebuild either the left or right path with the inserted (k,v) pair if bit { let (l, v_) = rec(n.left, bitpos+1); @@ -168,7 +173,7 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { null }; case (?n) { - let bit = getBit(k_hash, bitpos); + let bit = getHashBit(k_hash, bitpos); if bit { rec(n.left, bitpos+1) } else { rec(n.right, bitpos+1) } }; @@ -190,3 +195,54 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { }; rec(t, 0) }; + +/////////////////////////////////////////////////////////////////////// + +/* + Sets are partial maps from element type to unit type, + i.e., the partial map represents the set with its domain. +*/ + +// TODO-Matthew: +// +// - for now, we pass a hash value each time we pass an element value; +// in the future, we might avoid passing element hashes with each element in the API; +// related to: https://github.com/dfinity-lab/actorscript/issues/157 +// + +type Set = Trie; + +func setEmpty():Set = + empty(); + +func setInsert(s:Set, x:T, xh:Hash):Set = { + let (s2, _) = insert(s, x, xh, ()); + s2 +}; + +func setRemove(s:Set, x:T, xh:Hash):Set = { + let (s2, _) = remove(s, x, xh); + s2 +}; + +func setMem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { + switch (find(s, x, xh, eq)) { + case null { false }; + case (?_) { true }; + } +}; + +func setUnion(s1:Set, s2:Set):Set { /* TODO */ setUnion(s1,s2) }; +func setDiff(s1:Set, s2:Set):Set { /* TODO */ setDiff(s1,s2) }; +func setIntersect(s1:Set, s2:Set):Set { /* TODO */ setIntersect(s1,s2) }; + +// Insert numbers [1..8] into the set, using their bits as their hashes: +let s0 : Set = setEmpty(); +let s1 : Set = setInsert(s0, 1, ?(false,?(false,?(false,?(false, null))))); // 0 0 0 0 +let s2 : Set = setInsert(s1, 2, ?(true, ?(false,?(false,?(false, null))))); // 1 0 0 0 +let s3 : Set = setInsert(s2, 3, ?(false,?(true, ?(false,?(false, null))))); // 0 1 0 0 +let s4 : Set = setInsert(s3, 4, ?(true, ?(true, ?(false,?(false, null))))); // 1 1 0 0 +let s5 : Set = setInsert(s4, 5, ?(false,?(false,?(true, ?(false, null))))); // 0 0 1 0 +let s6 : Set = setInsert(s5, 6, ?(true, ?(false,?(true, ?(false, null))))); // 1 0 1 0 +let s7 : Set = setInsert(s6, 7, ?(true, ?(true, ?(true, ?(false, null))))); // 1 1 1 0 +let s8 : Set = setInsert(s7, 8, ?(false,?(false,?(false,?(true, null))))); // 1 1 1 0 From 95f41e4a4238ca29649468e14307f784e71a7e6c Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Mon, 11 Feb 2019 17:51:03 -0800 Subject: [PATCH 19/52] hashtrie: debugging reveals some bugs --- samples/collections/hashtrie.as | 128 ++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 23 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index eb242076be2..11e4613c817 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -1,16 +1,20 @@ /* - * Hash Tries. - * - * Functional maps (and sets) whose representation is "canonical", and history independent. - * - * By contrast, AVL Trees, RB Trees, and other representations do not - * enjoy history independence, and are each more complex to implement - * (e.g., each requires "rebalancing"; these trees never do). - * - * 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 + Hash Tries in ActorScript + ------------------------- + + 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 + + By contrast, other usual functional representations of maps (AVL + Trees, Red-Black Trees) do not enjoy history independence, and are + each more complex to implement (e.g., each requires "rebalancing"; + these trees never do). + */ // Done: @@ -20,9 +24,13 @@ // - insert operation // - remove operation // - replace operation (remove+insert via a single traversal) +// - basic encoding of sets, and some set operations // TODO-Matthew: // +// - write trie operations that operate over pairs of tries: +// for set union, difference and intersection. +// // - regression tests for everything that is below // // - handle hash collisions gracefully @@ -84,10 +92,14 @@ func getLeafVal(t : Node) : ?V { t.val }; -// TODO: Replace with bitwise operations on Words, once we have each of those in AS. For now, we encode hashes as lists of booleans. +// TODO: Replace with bitwise operations on Words, once we have each of those in AS. +// For now, we encode hashes as lists of booleans. func getHashBit(h:Hash, pos:Nat) : Bool { switch h { - case null { false }; // TODO: Should be an error case + case null { + // XXX: Should be an error case; it shouldn't happen in our tests if we set them up right. + false + }; case (?(b, _)) { if (pos == 0) { b } else { getHashBit(h, pos-1) } @@ -121,10 +133,10 @@ func replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { // 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 ) { - assertIsBin(t); 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 bit { @@ -236,13 +248,83 @@ func setUnion(s1:Set, s2:Set):Set { /* TODO */ setUnion(s1,s2) }; func setDiff(s1:Set, s2:Set):Set { /* TODO */ setDiff(s1,s2) }; func setIntersect(s1:Set, s2:Set):Set { /* TODO */ setIntersect(s1,s2) }; +//////////////////////////////////////////////////////////////////// + +func setPrint(s:Set) { + func rec(s:Set, ind:Nat) { + func indPrint(i:Nat) { + if (i == 0) { } else { print "| "; indPrint(i-1) } + }; + switch s { + case null { + indPrint(ind); + print "null\n"; + }; + case (?n) { + switch (n.key) { + case null { + indPrint(ind); + print "(bin \n"; + rec(n.left, ind+1); + rec(n.right, ind+1); + indPrint(ind); + print ")\n" + }; + case (?k) { + indPrint(ind); + print "(leaf "; + printInt k; + print ")\n"; + }; + } + }; + } + }; + rec(s,0); +}; + +func setInsertDb(s:Set, x:Nat, xh:Hash):Set = { + print " setInsert("; + printInt x; + print ")"; + let r = setInsert(s,x,xh); + print ";\n"; + setPrint(r); + r +}; + +func setMemDb(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { + func natEq(n:Nat,m:Nat):Bool{ n == m}; + print " setMem("; + print sname; + print ", "; + printInt x; + print ")"; + let b = setMem(s,x,xh,natEq); + if b { print " = true" } else { print " = false" }; + print ";\n"; + b +}; + +print "inserting...\n"; // Insert numbers [1..8] into the set, using their bits as their hashes: let s0 : Set = setEmpty(); -let s1 : Set = setInsert(s0, 1, ?(false,?(false,?(false,?(false, null))))); // 0 0 0 0 -let s2 : Set = setInsert(s1, 2, ?(true, ?(false,?(false,?(false, null))))); // 1 0 0 0 -let s3 : Set = setInsert(s2, 3, ?(false,?(true, ?(false,?(false, null))))); // 0 1 0 0 -let s4 : Set = setInsert(s3, 4, ?(true, ?(true, ?(false,?(false, null))))); // 1 1 0 0 -let s5 : Set = setInsert(s4, 5, ?(false,?(false,?(true, ?(false, null))))); // 0 0 1 0 -let s6 : Set = setInsert(s5, 6, ?(true, ?(false,?(true, ?(false, null))))); // 1 0 1 0 -let s7 : Set = setInsert(s6, 7, ?(true, ?(true, ?(true, ?(false, null))))); // 1 1 1 0 -let s8 : Set = setInsert(s7, 8, ?(false,?(false,?(false,?(true, null))))); // 1 1 1 0 +let s1 : Set = setInsertDb(s0, 1, ?(false,?(false,?(false,?(false, null))))); // 0 0 0 0 +let s2 : Set = setInsertDb(s1, 2, ?(true, ?(false,?(false,?(false, null))))); // 1 0 0 0 +let s3 : Set = setInsertDb(s2, 3, ?(false,?(true, ?(false,?(false, null))))); // 0 1 0 0 +let s4 : Set = setInsertDb(s3, 4, ?(true, ?(true, ?(false,?(false, null))))); // 1 1 0 0 +let s5 : Set = setInsertDb(s4, 5, ?(false,?(false,?(true, ?(false, null))))); // 0 0 1 0 +let s6 : Set = setInsertDb(s5, 6, ?(true, ?(false,?(true, ?(false, null))))); // 1 0 1 0 +let s7 : Set = setInsertDb(s6, 7, ?(true, ?(true, ?(true, ?(false, null))))); // 1 1 1 0 +let s8 : Set = setInsertDb(s7, 8, ?(false,?(false,?(false,?(true, null))))); // 1 1 1 0 +print "done.\n"; +print "testing membership...\n"; +assert( setMemDb(s1, "s1", 1, ?(false,?(false,?(false,?(false, null))))) ); +assert( setMemDb(s2, "s2", 1, ?(false,?(false,?(false,?(false, null))))) ); +assert( setMemDb(s3, "s3", 1, ?(false,?(false,?(false,?(false, null))))) ); +assert( setMemDb(s4, "s4", 1, ?(false,?(false,?(false,?(false, null))))) ); +assert( setMemDb(s5, "s5", 1, ?(false,?(false,?(false,?(false, null))))) ); +assert( setMemDb(s6, "s6", 1, ?(false,?(false,?(false,?(false, null))))) ); +assert( setMemDb(s7, "s7", 1, ?(false,?(false,?(false,?(false, null))))) ); +assert( setMemDb(s8, "s8", 1, ?(false,?(false,?(false,?(false, null))))) ); +print "done.\n"; From 415d6313f56dfe5202f4071fd9fdd26029d4f408 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 12 Feb 2019 04:47:36 -0800 Subject: [PATCH 20/52] hashtries: simple tests on sets pass; tests insertion & membership --- samples/collections/hashtrie.as | 157 ++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 39 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 11e4613c817..1055a1d6676 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -25,24 +25,34 @@ // - remove operation // - replace operation (remove+insert via a single traversal) // - basic encoding of sets, and some set operations +// - basic tests (and primitive debugging) for set operations // TODO-Matthew: // // - write trie operations that operate over pairs of tries: // for set union, difference and intersection. // -// - regression tests for everything that is below +// - (more) regression tests for everything that is below // // - handle hash collisions gracefully // // - iterator objects, for use in 'for ... in ...' patterns +// TEMP: A "bit string" as a linked list of bits: +type Bits = ?(Bool, Bits); + // TODO: Replace this definition WordX, for some X, once we have these types in AS. -type Hash = ?(Bool, Hash); +type Hash = Bits; //type Hash = Word16; // XXX: This Node type is a "sloppy union" between "BinNodes" (left/right fields) and "Leaves" (key/val fields): -type Node = {left:Trie; right:Trie; key:?K; val:?V}; +type Node = { + left:Trie; + right:Trie; + key:?K; + val:?V +}; + type Trie = ?Node; // Simplifying assumption, for now: All defined paths in the trie have a uniform length, @@ -100,9 +110,9 @@ func getHashBit(h:Hash, pos:Nat) : Bool { // XXX: Should be an error case; it shouldn't happen in our tests if we set them up right. false }; - case (?(b, _)) { + case (?(b, h_)) { if (pos == 0) { b } - else { getHashBit(h, pos-1) } + else { getHashBit(h_, pos-1) } }; } }; @@ -118,8 +128,12 @@ func buildNewPath(bitpos:Nat, k:K, k_hash:Hash, ov:?V) : Trie { // 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=path; right=null; key=null; val=null}) } - else { ?(new {left=null; right=path; key=null; val=null}) } + if (not bit) { + ?(new {left=path; right=null; key=null; val=null}) + } + else { + ?(new {left=null; right=path; key=null; val=null}) + } } else { // create new leaf for (k,v) pair ?(new {left=null; right=null; key=?k; val=ov }) @@ -139,7 +153,7 @@ func replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { assertIsBin(t); let bit = getHashBit(k_hash, bitpos); // rebuild either the left or right path with the inserted (k,v) pair - if bit { + if (not bit) { let (l, v_) = rec(n.left, bitpos+1); (?(new {left=l; right=n.right; key=null; val=null }), v_) } @@ -178,16 +192,16 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { // 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 ) { - assertIsBin(t); 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 bit { rec(n.left, bitpos+1) } - else { rec(n.right, bitpos+1) } + if (not bit) { rec(n.left, bitpos+1) } + else { rec(n.right, bitpos+1) } }; } } else { @@ -251,27 +265,40 @@ func setIntersect(s1:Set, s2:Set):Set { /* TODO */ setIntersect(s //////////////////////////////////////////////////////////////////// func setPrint(s:Set) { - func rec(s:Set, ind:Nat) { + func rec(s:Set, ind:Nat, bits:Hash) { func indPrint(i:Nat) { 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 s { case null { - indPrint(ind); - print "null\n"; + //indPrint(ind); + //bitsPrintRev(bits); + //print "(null)\n"; }; case (?n) { switch (n.key) { case null { - indPrint(ind); - print "(bin \n"; - rec(n.left, ind+1); - rec(n.right, ind+1); - indPrint(ind); - print ")\n" + //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); + //indPrint(ind); + bitsPrintRev(bits); print "(leaf "; printInt k; print ")\n"; @@ -280,9 +307,11 @@ func setPrint(s:Set) { }; } }; - rec(s,0); + rec(s, 0, null); }; +//////////////////////////////////////////////////////////////////////////////// + func setInsertDb(s:Set, x:Nat, xh:Hash):Set = { print " setInsert("; printInt x; @@ -306,25 +335,75 @@ func setMemDb(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { b }; +///////////////////////////////////////////////////////////////////////////////// + +let hash_0 = ?(false,?(false,?(false,?(false, null)))); +let hash_1 = ?(false,?(false,?(false,?(true, null)))); +let hash_2 = ?(false,?(false,?(true, ?(false, null)))); +let hash_3 = ?(false,?(false,?(true, ?(true, null)))); +let hash_4 = ?(false,?(true, ?(false,?(false, null)))); +let hash_5 = ?(false,?(true, ?(true, ?(true, null)))); +let hash_6 = ?(false,?(true, ?(true, ?(false, null)))); +let hash_7 = ?(false,?(true, ?(true, ?(true, null)))); +let hash_8 = ?(true, ?(false,?(false,?(false, null)))); + print "inserting...\n"; -// Insert numbers [1..8] into the set, using their bits as their hashes: +// Insert numbers [0..8] into the set, using their bits as their hashes: let s0 : Set = setEmpty(); -let s1 : Set = setInsertDb(s0, 1, ?(false,?(false,?(false,?(false, null))))); // 0 0 0 0 -let s2 : Set = setInsertDb(s1, 2, ?(true, ?(false,?(false,?(false, null))))); // 1 0 0 0 -let s3 : Set = setInsertDb(s2, 3, ?(false,?(true, ?(false,?(false, null))))); // 0 1 0 0 -let s4 : Set = setInsertDb(s3, 4, ?(true, ?(true, ?(false,?(false, null))))); // 1 1 0 0 -let s5 : Set = setInsertDb(s4, 5, ?(false,?(false,?(true, ?(false, null))))); // 0 0 1 0 -let s6 : Set = setInsertDb(s5, 6, ?(true, ?(false,?(true, ?(false, null))))); // 1 0 1 0 -let s7 : Set = setInsertDb(s6, 7, ?(true, ?(true, ?(true, ?(false, null))))); // 1 1 1 0 -let s8 : Set = setInsertDb(s7, 8, ?(false,?(false,?(false,?(true, null))))); // 1 1 1 0 +let s1 : Set = setInsertDb(s0, 0, hash_0); +let s2 : Set = setInsertDb(s1, 1, hash_1); +let s3 : Set = setInsertDb(s2, 2, hash_2); +let s4 : Set = setInsertDb(s3, 3, hash_3); +let s5 : Set = setInsertDb(s4, 4, hash_4); +let s6 : Set = setInsertDb(s5, 5, hash_5); +let s7 : Set = setInsertDb(s6, 6, hash_6); +let s8 : Set = setInsertDb(s7, 7, hash_7); +let s9 : Set = setInsertDb(s8, 8, hash_8); print "done.\n"; print "testing membership...\n"; -assert( setMemDb(s1, "s1", 1, ?(false,?(false,?(false,?(false, null))))) ); -assert( setMemDb(s2, "s2", 1, ?(false,?(false,?(false,?(false, null))))) ); -assert( setMemDb(s3, "s3", 1, ?(false,?(false,?(false,?(false, null))))) ); -assert( setMemDb(s4, "s4", 1, ?(false,?(false,?(false,?(false, null))))) ); -assert( setMemDb(s5, "s5", 1, ?(false,?(false,?(false,?(false, null))))) ); -assert( setMemDb(s6, "s6", 1, ?(false,?(false,?(false,?(false, null))))) ); -assert( setMemDb(s7, "s7", 1, ?(false,?(false,?(false,?(false, null))))) ); -assert( setMemDb(s8, "s8", 1, ?(false,?(false,?(false,?(false, null))))) ); + +// Element 0: Test memberships of each set defined above for element 0 +assert( not( setMemDb(s0, "s0", 0, hash_0 ) )); +assert( setMemDb(s1, "s1", 0, hash_0 ) ); +assert( setMemDb(s2, "s2", 0, hash_0 ) ); +assert( setMemDb(s3, "s3", 0, hash_0 ) ); +assert( setMemDb(s4, "s4", 0, hash_0 ) ); +assert( setMemDb(s5, "s5", 0, hash_0 ) ); +assert( setMemDb(s6, "s6", 0, hash_0 ) ); +assert( setMemDb(s7, "s7", 0, hash_0 ) ); +assert( setMemDb(s8, "s8", 0, hash_0 ) ); + +// Element 1: Test memberships of each set defined above for element 1 +assert( not(setMemDb(s0, "s0", 1, hash_1 )) ); +assert( not(setMemDb(s1, "s1", 1, hash_1 )) ); +assert( setMemDb(s2, "s2", 1, hash_1 ) ); +assert( setMemDb(s3, "s3", 1, hash_1 ) ); +assert( setMemDb(s4, "s4", 1, hash_1 ) ); +assert( setMemDb(s5, "s5", 1, hash_1 ) ); +assert( setMemDb(s6, "s6", 1, hash_1 ) ); +assert( setMemDb(s7, "s7", 1, hash_1 ) ); +assert( setMemDb(s8, "s8", 1, hash_1 ) ); + +// Element 2: Test memberships of each set defined above for element 2 +assert( not(setMemDb(s0, "s0", 2, hash_2 )) ); +assert( not(setMemDb(s1, "s1", 2, hash_2 )) ); +assert( not(setMemDb(s2, "s2", 2, hash_2 )) ); +assert( setMemDb(s3, "s3", 2, hash_2 ) ); +assert( setMemDb(s4, "s4", 2, hash_2 ) ); +assert( setMemDb(s5, "s5", 2, hash_2 ) ); +assert( setMemDb(s6, "s6", 2, hash_2 ) ); +assert( setMemDb(s7, "s7", 2, hash_2 ) ); +assert( setMemDb(s8, "s8", 2, hash_2 ) ); + +// Element 3: Test memberships of each set defined above for element 3 +assert( not(setMemDb(s0, "s0", 3, hash_3 )) ); +assert( not(setMemDb(s1, "s1", 3, hash_3 )) ); +assert( not(setMemDb(s2, "s2", 3, hash_3 )) ); +assert( not(setMemDb(s3, "s3", 3, hash_3 )) ); +assert( setMemDb(s4, "s4", 3, hash_3 ) ); +assert( setMemDb(s5, "s5", 3, hash_3 ) ); +assert( setMemDb(s6, "s6", 3, hash_3 ) ); +assert( setMemDb(s7, "s7", 3, hash_3 ) ); +assert( setMemDb(s8, "s8", 3, hash_3 ) ); + print "done.\n"; From 9236308d71c539cd20f57c0a05676351d63540b1 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 12 Feb 2019 08:36:41 -0800 Subject: [PATCH 21/52] hashtries: finish little tests on sets; fix typo --- samples/collections/hashtrie.as | 67 ++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 1055a1d6676..1a4ad376422 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -44,6 +44,7 @@ type Bits = ?(Bool, Bits); // TODO: Replace this definition WordX, for some X, once we have these types in AS. type Hash = Bits; //type Hash = Word16; +//type Hash = Word8; // XXX: This Node type is a "sloppy union" between "BinNodes" (left/right fields) and "Leaves" (key/val fields): type Node = { @@ -342,7 +343,7 @@ let hash_1 = ?(false,?(false,?(false,?(true, null)))); let hash_2 = ?(false,?(false,?(true, ?(false, null)))); let hash_3 = ?(false,?(false,?(true, ?(true, null)))); let hash_4 = ?(false,?(true, ?(false,?(false, null)))); -let hash_5 = ?(false,?(true, ?(true, ?(true, null)))); +let hash_5 = ?(false,?(true, ?(false,?(true, null)))); let hash_6 = ?(false,?(true, ?(true, ?(false, null)))); let hash_7 = ?(false,?(true, ?(true, ?(true, null)))); let hash_8 = ?(true, ?(false,?(false,?(false, null)))); @@ -372,6 +373,7 @@ assert( setMemDb(s5, "s5", 0, hash_0 ) ); assert( setMemDb(s6, "s6", 0, hash_0 ) ); assert( setMemDb(s7, "s7", 0, hash_0 ) ); assert( setMemDb(s8, "s8", 0, hash_0 ) ); +assert( setMemDb(s9, "s9", 0, hash_0 ) ); // Element 1: Test memberships of each set defined above for element 1 assert( not(setMemDb(s0, "s0", 1, hash_1 )) ); @@ -383,6 +385,7 @@ assert( setMemDb(s5, "s5", 1, hash_1 ) ); assert( setMemDb(s6, "s6", 1, hash_1 ) ); assert( setMemDb(s7, "s7", 1, hash_1 ) ); assert( setMemDb(s8, "s8", 1, hash_1 ) ); +assert( setMemDb(s9, "s9", 1, hash_1 ) ); // Element 2: Test memberships of each set defined above for element 2 assert( not(setMemDb(s0, "s0", 2, hash_2 )) ); @@ -394,6 +397,7 @@ assert( setMemDb(s5, "s5", 2, hash_2 ) ); assert( setMemDb(s6, "s6", 2, hash_2 ) ); assert( setMemDb(s7, "s7", 2, hash_2 ) ); assert( setMemDb(s8, "s8", 2, hash_2 ) ); +assert( setMemDb(s9, "s9", 2, hash_2 ) ); // Element 3: Test memberships of each set defined above for element 3 assert( not(setMemDb(s0, "s0", 3, hash_3 )) ); @@ -405,5 +409,66 @@ assert( setMemDb(s5, "s5", 3, hash_3 ) ); assert( setMemDb(s6, "s6", 3, hash_3 ) ); assert( setMemDb(s7, "s7", 3, hash_3 ) ); assert( setMemDb(s8, "s8", 3, hash_3 ) ); +assert( setMemDb(s9, "s9", 3, hash_3 ) ); + +// Element 4: Test memberships of each set defined above for element 4 +assert( not(setMemDb(s0, "s0", 4, hash_4 )) ); +assert( not(setMemDb(s1, "s1", 4, hash_4 )) ); +assert( not(setMemDb(s2, "s2", 4, hash_4 )) ); +assert( not(setMemDb(s3, "s3", 4, hash_4 )) ); +assert( not(setMemDb(s4, "s4", 4, hash_4 )) ); +assert( setMemDb(s5, "s5", 4, hash_4 ) ); +assert( setMemDb(s6, "s6", 4, hash_4 ) ); +assert( setMemDb(s7, "s7", 4, hash_4 ) ); +assert( setMemDb(s8, "s8", 4, hash_4 ) ); +assert( setMemDb(s9, "s9", 4, hash_4 ) ); + +// Element 5: Test memberships of each set defined above for element 5 +assert( not(setMemDb(s0, "s0", 5, hash_5 )) ); +assert( not(setMemDb(s1, "s1", 5, hash_5 )) ); +assert( not(setMemDb(s2, "s2", 5, hash_5 )) ); +assert( not(setMemDb(s3, "s3", 5, hash_5 )) ); +assert( not(setMemDb(s4, "s4", 5, hash_5 )) ); +assert( not(setMemDb(s5, "s5", 5, hash_5 )) ); +assert( setMemDb(s6, "s6", 5, hash_5 ) ); +assert( setMemDb(s7, "s7", 5, hash_5 ) ); +assert( setMemDb(s8, "s8", 5, hash_5 ) ); +assert( setMemDb(s9, "s9", 5, hash_5 ) ); + +// Element 6: Test memberships of each set defined above for element 6 +assert( not(setMemDb(s0, "s0", 6, hash_6 )) ); +assert( not(setMemDb(s1, "s1", 6, hash_6 )) ); +assert( not(setMemDb(s2, "s2", 6, hash_6 )) ); +assert( not(setMemDb(s3, "s3", 6, hash_6 )) ); +assert( not(setMemDb(s4, "s4", 6, hash_6 )) ); +assert( not(setMemDb(s5, "s5", 6, hash_6 )) ); +assert( not(setMemDb(s6, "s6", 6, hash_6 )) ); +assert( setMemDb(s7, "s7", 6, hash_6 ) ); +assert( setMemDb(s8, "s8", 6, hash_6 ) ); +assert( setMemDb(s9, "s9", 6, hash_6 ) ); + +// Element 7: Test memberships of each set defined above for element 7 +assert( not(setMemDb(s0, "s0", 7, hash_7 )) ); +assert( not(setMemDb(s1, "s1", 7, hash_7 )) ); +assert( not(setMemDb(s2, "s2", 7, hash_7 )) ); +assert( not(setMemDb(s3, "s3", 7, hash_7 )) ); +assert( not(setMemDb(s4, "s4", 7, hash_7 )) ); +assert( not(setMemDb(s5, "s5", 7, hash_7 )) ); +assert( not(setMemDb(s6, "s6", 7, hash_7 )) ); +assert( not(setMemDb(s7, "s7", 7, hash_7 )) ); +assert( setMemDb(s8, "s8", 7, hash_7 ) ); +assert( setMemDb(s9, "s9", 7, hash_7 ) ); + +// Element 8: Test memberships of each set defined above for element 8 +assert( not(setMemDb(s0, "s0", 8, hash_8 )) ); +assert( not(setMemDb(s1, "s1", 8, hash_8 )) ); +assert( not(setMemDb(s2, "s2", 8, hash_8 )) ); +assert( not(setMemDb(s3, "s3", 8, hash_8 )) ); +assert( not(setMemDb(s4, "s4", 8, hash_8 )) ); +assert( not(setMemDb(s6, "s6", 8, hash_8 )) ); +assert( not(setMemDb(s6, "s6", 8, hash_8 )) ); +assert( not(setMemDb(s7, "s7", 8, hash_8 )) ); +assert( not(setMemDb(s8, "s8", 8, hash_8 )) ); +assert( setMemDb(s9, "s9", 8, hash_8 ) ); print "done.\n"; From 829003a6ade904c2673fb5c8f64d24ae07e112d5 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 19 Feb 2019 06:37:46 -0700 Subject: [PATCH 22/52] hashtrie: more emulation of sum types (see AST-42) --- samples/collections/hashtrie.as | 52 ++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 1a4ad376422..a2dd6846e7b 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -46,16 +46,6 @@ type Hash = Bits; //type Hash = Word16; //type Hash = Word8; -// XXX: This Node type is a "sloppy union" between "BinNodes" (left/right fields) and "Leaves" (key/val fields): -type Node = { - left:Trie; - right:Trie; - key:?K; - val:?V -}; - -type Trie = ?Node; - // Simplifying assumption, for now: All defined paths in the trie have a uniform length, // the same as the number of bits of a hash, starting at the LSB, that we use for indexing. // @@ -67,7 +57,43 @@ type Trie = ?Node; // let HASH_BITS = 4; -// XXX: Until we have real sum types: +// XXX: See AST-42 +type Node = { + left:Trie; + right:Trie; + key:?K; + val:?V +}; +type Trie = ?Node; + +/* See AST-42 (sum types); we want this type definition instead: + +type BinNode = { + left:Trie; + right:Trie; +}; +type LeafNode = { + key:K; + val:V +}; +type Trie = + Leaf of LeafNode +| Bin of BinNode +| Empty; +*/ + +func makeEmpty() : Trie + = null; + +func makeBin(l:Trie, r:Trie) : Trie { + ?(new {left=l; right=r; key=null; val=null }) +}; +func makeLeaf(k:K, v:V) : Trie { + ?(new {left=null; right=null; key=?k; val=?v }) +}; + + +// XXX: until AST-42: func assertIsNull(x : ?X) { switch x { case null { assert(true) }; @@ -75,7 +101,7 @@ func assertIsNull(x : ?X) { }; }; -// XXX: Until we have real sum types: +// XXX: until AST-42: func assertIsBin(t : Trie) { switch t { case null { assert(false) }; @@ -86,7 +112,7 @@ func assertIsBin(t : Trie) { } }; -// XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: +// XXX: until AST-42: func getLeafKey(t : Node) : K { assertIsNull>(t.left); assertIsNull>(t.right); From 5c36751d71af75ed76928c2c591e137da86b2781 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Tue, 19 Feb 2019 06:52:24 -0700 Subject: [PATCH 23/52] squash! hashtrie: more emulation of sum types (see AST-42) --- samples/collections/hashtrie.as | 52 +++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index a2dd6846e7b..052c255ab20 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -70,28 +70,57 @@ type Trie = ?Node; type BinNode = { left:Trie; - right:Trie; + right:Trie; }; type LeafNode = { - key:K; - val:V + key:K; + val:V }; -type Trie = - Leaf of LeafNode -| Bin of BinNode +type Trie = + Leaf of LeafNode +| Bin of BinNode | Empty; */ -func makeEmpty() : Trie +// XXX: until AST-42: +func makeEmpty() : Trie = null; -func makeBin(l:Trie, r:Trie) : Trie { +// XXX: until AST-42: +func makeBin(l:Trie, r:Trie) : Trie { ?(new {left=l; right=r; key=null; val=null }) }; -func makeLeaf(k:K, v:V) : Trie { + +// XXX: until AST-42: +func isBin(t:Trie) : Bool { + switch t { + case null { false }; + case (?t_) { + switch (t_.key) { + 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 }) }; +// XXX: until AST-42: +func isLeaf(t:Trie) : Bool { + switch t { + case null { false }; + case (?t_) { + switch (t_.key) { + case null { false }; + case _ { true }; + } + }; + } +}; // XXX: until AST-42: func assertIsNull(x : ?X) { @@ -249,6 +278,11 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { rec(t, 0) }; +// merge tries, preferring the right trie where there are collisions in common keys +func merge(tl:Trie, tr:Trie) : Trie { + tl // XXX +}; + /////////////////////////////////////////////////////////////////////// /* From 2354ec7898acdd4dd25a43ff3b10bb8b54cfb1dc Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 20 Feb 2019 16:52:05 -0700 Subject: [PATCH 24/52] simple merge on hash tries; union on sets --- samples/collections/hashtrie.as | 96 ++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 18 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 052c255ab20..71ede4df49a 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -46,11 +46,17 @@ type Hash = Bits; //type Hash = Word16; //type Hash = Word8; -// Simplifying assumption, for now: All defined paths in the trie have a uniform length, -// the same as the number of bits of a hash, starting at the LSB, that we use for indexing. +// Uniform depth assumption: // -// - If the number is too low, our expected O(log n) bounds become expected O(n). -// - If the number is too high, we waste constant factors for representing small sets/maps. +// We make a simplifying assumption, for now: All defined paths in the +// trie have a uniform length, the same as the number of bits of a +// hash, starting at the LSB, that we use for indexing. +// +// - If the number is too low, our expected O(log n) bounds become +// expected O(n). +// +// - If the number is too high, we waste constant factors for +// representing small sets/maps. // // TODO: Make this more robust by making this number adaptive for each // path, and based on the content of the trie along that path. @@ -68,18 +74,11 @@ type Trie = ?Node; /* See AST-42 (sum types); we want this type definition instead: -type BinNode = { - left:Trie; - right:Trie; -}; -type LeafNode = { - key:K; - val:V -}; -type Trie = - Leaf of LeafNode -| Bin of BinNode -| Empty; +// 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 }; + */ // XXX: until AST-42: @@ -122,6 +121,14 @@ func isLeaf(t:Trie) : Bool { } }; +// XXX: until AST-42: +func isEmpty(t:Trie) : Bool { + switch t { + case null { true }; + case (?_) { false }; + }; +}; + // XXX: until AST-42: func assertIsNull(x : ?X) { switch x { @@ -280,7 +287,33 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { // merge tries, preferring the right trie where there are collisions in common keys func merge(tl:Trie, tr:Trie) : Trie { - tl // XXX + 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 + }; + } + }; + } }; /////////////////////////////////////////////////////////////////////// @@ -319,7 +352,11 @@ func setMem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { } }; -func setUnion(s1:Set, s2:Set):Set { /* TODO */ setUnion(s1,s2) }; +func setUnion(s1:Set, s2:Set):Set { + let s3 = merge(s1, s2); + s3 +}; + func setDiff(s1:Set, s2:Set):Set { /* TODO */ setDiff(s1,s2) }; func setIntersect(s1:Set, s2:Set):Set { /* TODO */ setIntersect(s1,s2) }; @@ -396,6 +433,18 @@ func setMemDb(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { b }; +func unionDb(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setUnion("; + print s1name; + print ", "; + print s2name; + print ")"; + let r = setUnion(s1, s2); + print ";\n"; + setPrint(r); + r +}; + ///////////////////////////////////////////////////////////////////////////////// let hash_0 = ?(false,?(false,?(false,?(false, null)))); @@ -421,6 +470,17 @@ let s7 : Set = setInsertDb(s6, 6, hash_6); let s8 : Set = setInsertDb(s7, 7, hash_7); let s9 : Set = setInsertDb(s8, 8, hash_8); print "done.\n"; + +print "unioning...\n"; +let s1s2 : Set = unionDb(s1, "s1", s2, "s2"); +let s2s1 : Set = unionDb(s2, "s2", s1, "s1"); +let s3s2 : Set = unionDb(s3, "s3", s2, "s2"); +let s4s2 : Set = unionDb(s4, "s4", s2, "s2"); +let s1s5 : Set = unionDb(s1, "s1", s5, "s5"); +let s0s2 : Set = unionDb(s0, "s0", s2, "s2"); +print "done.\n"; + + print "testing membership...\n"; // Element 0: Test memberships of each set defined above for element 0 From 0ed35ee33d1e5ba8defa669d1ecbcfcb6750c43b Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 20 Feb 2019 16:53:10 -0700 Subject: [PATCH 25/52] minor: update issue link --- samples/collections/hashtrie.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 71ede4df49a..45ce42be807 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -327,7 +327,7 @@ func merge(tl:Trie, tr:Trie) : Trie { // // - for now, we pass a hash value each time we pass an element value; // in the future, we might avoid passing element hashes with each element in the API; -// related to: https://github.com/dfinity-lab/actorscript/issues/157 +// related to: https://dfinity.atlassian.net/browse/AST-32 // type Set = Trie; From 6ff2bf81277eb19899da83bf2735ba74b2164357 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 21 Feb 2019 11:07:59 -0700 Subject: [PATCH 26/52] hash tries: conj, disj; sets: intersect --- samples/collections/hashtrie.as | 241 +++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 3 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 45ce42be807..a7d0db2140e 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -129,6 +129,14 @@ func isEmpty(t:Trie) : Bool { }; }; +// XXX: until AST-42: +func isNull(x : ?X) : Bool { + switch x { + case null { true }; + case (?_) { false }; + }; +}; + // XXX: until AST-42: func assertIsNull(x : ?X) { switch x { @@ -137,6 +145,14 @@ func assertIsNull(x : ?X) { }; }; +// XXX: until AST-42: +func assertIsEmpty(t : Trie) { + switch t { + case null { assert(true) }; + case (?_) { assert(false) }; + }; +}; + // XXX: until AST-42: func assertIsBin(t : Trie) { switch t { @@ -148,6 +164,21 @@ func assertIsBin(t : Trie) { } }; +// A "twig" is a binary node with two empty subtrees. It arises +// internally, for simplifying some pattern-matching logic that +// deals with pairs of tries that we try to walk recursively. +func isTwig(t : Trie) : Bool { + switch t { + case null { false }; + case (?n) { + isEmpty(n.left) and + isEmpty(n.right) and + isNull(n.key) and + isNull(n.val); + }; + } +}; + // XXX: until AST-42: func getLeafKey(t : Node) : K { assertIsNull>(t.left); @@ -285,7 +316,10 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { rec(t, 0) }; -// merge tries, preferring the right trie where there are collisions in common keys +// merge tries, preferring the right trie where there are collisions +// 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) { case (null, _) { return tr }; @@ -316,6 +350,196 @@ func merge(tl:Trie, tr:Trie) : Trie { } }; +// 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 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 } + } + }; + } + }; + }}; + rec(tl, tr) +}; + +// This operation generalizes the notion of "set union" to finite maps. +// Produces a "disjunctive image" of the two tries, where the values of +// matching keys are combined with the given binary operator. +func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) : Trie { + // We use these "twigs" to simplify the pattern-matching cases + // below. twigs are equivalent to empty tries. we introduce them + // when one side or the other (but not both) input tries is empty. + // + let leftTwig : Trie = makeBin(makeEmpty(),makeEmpty()); + let rightTwig : Trie = makeBin(makeEmpty(),makeEmpty()); + // + // using twigs, we only ever handle the cases for: + // empty-empty, bin-bin, leaf-leaf, leaf-empty, empty-leaf. + // + // in particular, we do not explicitly handle: + // empty-bin, bin-empty, leaf-bin, bin-leaf. + // + func rec(tl:Trie, tr:Trie) : Trie { + switch (tl, tr) { + // empty-empty terminates early, all other cases do not. + case (null, null) { makeEmpty() }; + case (null, _ ) { rec(leftTwig, tr) }; + case (_, null) { rec(tl, rightTwig) }; + 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) or isTwig(tl)); + assert(isLeaf(tr) or isTwig(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() + } + }; + // empty-leaf case + case (null, null, ?kr, ?vr) { + makeLeaf(kr, vbin(null, ?vr)) + }; + // leaf-empty case + case (?kl, ?vl, null, null) { + makeLeaf(kl, vbin(?vl, null)) + }; + // empty-empty case + case (null, null, null, null) { + makeEmpty() + }; + // XXX impossible, and unnecessary with AST-42. + case _ { makeEmpty() }; + } + }; + } + }; + }}; + rec(tl, tr) +}; + + +// This operation generalizes the notion of "set intersection" to +// finite maps. Produces a "conjuctive image" of the two tries, where +// the values of matching keys are combined with the given binary +// operator, and unmatched key-value pairrs are not present in the output. +func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : Trie { + // We use these "twigs" to simplify the pattern-matching cases + // below. twigs are equivalent to empty tries. we introduce them + // when one side or the other (but not both) input tries is empty. + // + let leftTwig : Trie = makeBin(makeEmpty(),makeEmpty()); + let rightTwig : Trie = makeBin(makeEmpty(),makeEmpty()); + // + // using twigs, we only ever handle the cases for: + // empty-empty, bin-bin, leaf-leaf, leaf-empty, empty-leaf. + // + // in particular, we do not explicitly handle: + // empty-bin, bin-empty, leaf-bin, bin-leaf. + // + func rec(tl:Trie, tr:Trie) : Trie { + switch (tl, tr) { + // empty-empty terminates early, all other cases do not. + case (null, null) { makeEmpty() }; + case (null, _ ) { rec(leftTwig, tr) }; + case (_, null) { rec(tl, rightTwig) }; + 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) or isTwig(tl)); + assert(isLeaf(tr) or isTwig(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() + } + }; + // empty-leaf case + case (null, null, ?kr, ?vr) { makeEmpty() }; + // leaf-empty case + case (?kl, ?vl, null, null) { makeEmpty() }; + // empty-empty case + case (null, null, null, null) { makeEmpty()}; + // XXX impossible, and unnecessary with AST-42. + case _ { makeEmpty() }; + } + }; + } + }; + }}; + rec(tl, tr) +}; + /////////////////////////////////////////////////////////////////////// /* @@ -329,6 +553,9 @@ func merge(tl:Trie, tr:Trie) : Trie { // in the future, we might avoid passing element hashes with each element in the API; // related to: https://dfinity.atlassian.net/browse/AST-32 // +// - similarly, we pass an equality function when we do some operations. +// in the future, we might avoid this via https://dfinity.atlassian.net/browse/AST-32 +// type Set = Trie; @@ -357,8 +584,16 @@ func setUnion(s1:Set, s2:Set):Set { s3 }; -func setDiff(s1:Set, s2:Set):Set { /* TODO */ setDiff(s1,s2) }; -func setIntersect(s1:Set, s2:Set):Set { /* TODO */ setIntersect(s1,s2) }; +func setDiff(s1:Set, s2:Set, eq:(T,T)->Bool):Set { + let s3 = diff(s1, s2, eq); + s3 +}; + +func setIntersect(s1:Set, s2:Set, eq:(T,T)->Bool):Set { + let noop : ((),())->(()) = func (_:(),_:()):(())=(); + let s3 = conj(s1, s2, eq, noop); + s3 +}; //////////////////////////////////////////////////////////////////// From a4d439a2b4192c501b6b5b3e42bc6d6498512505 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 21 Feb 2019 15:15:04 -0700 Subject: [PATCH 27/52] hashtrie: debugging conj and disj; disj has bugs --- samples/collections/hashtrie.as | 133 ++++++++++++++++---------------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index a7d0db2140e..14105839c2f 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -475,68 +475,48 @@ func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) // the values of matching keys are combined with the given binary // operator, and unmatched key-value pairrs are not present in the output. func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : Trie { - // We use these "twigs" to simplify the pattern-matching cases - // below. twigs are equivalent to empty tries. we introduce them - // when one side or the other (but not both) input tries is empty. - // - let leftTwig : Trie = makeBin(makeEmpty(),makeEmpty()); - let rightTwig : Trie = makeBin(makeEmpty(),makeEmpty()); - // - // using twigs, we only ever handle the cases for: - // empty-empty, bin-bin, leaf-leaf, leaf-empty, empty-leaf. - // - // in particular, we do not explicitly handle: - // empty-bin, bin-empty, leaf-bin, bin-leaf. - // func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { - // empty-empty terminates early, all other cases do not. - case (null, null) { makeEmpty() }; - case (null, _ ) { rec(leftTwig, tr) }; - case (_, null) { rec(tl, rightTwig) }; - 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) or isTwig(tl)); - assert(isLeaf(tr) or isTwig(tr)); - switch (nl.key, nl.val, nr.key, nr.val) { - // leaf-leaf case + 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() - } - }; - // empty-leaf case - case (null, null, ?kr, ?vr) { makeEmpty() }; - // leaf-empty case - case (?kl, ?vl, null, null) { makeEmpty() }; - // empty-empty case - case (null, null, null, null) { makeEmpty()}; + 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() }; - } - }; - } - }; - }}; + } + }; + } + } + }}; rec(tl, tr) }; @@ -645,6 +625,8 @@ func setPrint(s:Set) { //////////////////////////////////////////////////////////////////////////////// +func natEq(n:Nat,m:Nat):Bool{ n == m}; + func setInsertDb(s:Set, x:Nat, xh:Hash):Set = { print " setInsert("; printInt x; @@ -656,7 +638,6 @@ func setInsertDb(s:Set, x:Nat, xh:Hash):Set = { }; func setMemDb(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { - func natEq(n:Nat,m:Nat):Bool{ n == m}; print " setMem("; print sname; print ", "; @@ -668,13 +649,28 @@ func setMemDb(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { b }; -func unionDb(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { +func setUnionDb(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { print " setUnion("; print s1name; print ", "; print s2name; print ")"; - let r = setUnion(s1, s2); + let r1 = setUnion(s1, s2); + let r2 = disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); + print ";\n"; + setPrint(r1); + print "=========\n"; + setPrint(r2); + r1 +}; + +func setIntersectDb(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setIntersect("; + print s1name; + print ", "; + print s2name; + print ")"; + let r = setIntersect(s1, s2, natEq); print ";\n"; setPrint(r); r @@ -707,12 +703,17 @@ let s9 : Set = setInsertDb(s8, 8, hash_8); print "done.\n"; print "unioning...\n"; -let s1s2 : Set = unionDb(s1, "s1", s2, "s2"); -let s2s1 : Set = unionDb(s2, "s2", s1, "s1"); -let s3s2 : Set = unionDb(s3, "s3", s2, "s2"); -let s4s2 : Set = unionDb(s4, "s4", s2, "s2"); -let s1s5 : Set = unionDb(s1, "s1", s5, "s5"); -let s0s2 : Set = unionDb(s0, "s0", s2, "s2"); +let s1s2 : Set = setUnionDb(s1, "s1", s2, "s2"); +let s2s1 : Set = setUnionDb(s2, "s2", s1, "s1"); +let s3s2 : Set = setUnionDb(s3, "s3", s2, "s2"); +let s4s2 : Set = setUnionDb(s4, "s4", s2, "s2"); +let s1s5 : Set = setUnionDb(s1, "s1", s5, "s5"); +let s0s2 : Set = setUnionDb(s0, "s0", s2, "s2"); +print "done.\n"; + +print "intersecting...\n"; +let s3is6 : Set = setIntersectDb(s3, "s3", s6, "s6"); +let s2is1 : Set = setIntersectDb(s2, "s2", s1, "s1"); print "done.\n"; From 30ea11ba757222465d9243f72f6e70f9ef67119a Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 21 Feb 2019 16:21:15 -0700 Subject: [PATCH 28/52] hashtrie: debug disj; write+test setCard, setEq --- samples/collections/hashtrie.as | 235 ++++++++++++++++++++------------ 1 file changed, 151 insertions(+), 84 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 14105839c2f..c2f0e5276af 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -81,10 +81,42 @@ type LeafNode = { key:K; val:V }; */ +// XXX: until AST-42: +func isNull(x : ?X) : Bool { + switch x { + case null { true }; + case (?_) { false }; + }; +}; + +// XXX: until AST-42: +func assertIsNull(x : ?X) { + switch x { + case null { assert(true) }; + case (?_) { assert(false) }; + }; +}; + // XXX: until AST-42: func makeEmpty() : Trie = null; +// XXX: until AST-42: +func isEmpty(t:Trie) : Bool { + switch t { + case null { true }; + case (?_) { false }; + }; +}; + +// XXX: until AST-42: +func assertIsEmpty(t : Trie) { + switch t { + 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 }) @@ -108,6 +140,19 @@ func makeLeaf(k:K, v:V) : Trie { ?(new {left=null; right=null; key=?k; val=?v }) }; +// XXX: until AST-42: +func matchLeaf(t:Trie) : ?(K,V) { + switch t { + case null { null }; + case (?t_) { + switch (t_.key, t_.val) { + case (?k,?v) ?(k,v); + case (_) null; + } + }; + } +}; + // XXX: until AST-42: func isLeaf(t:Trie) : Bool { switch t { @@ -120,39 +165,6 @@ func isLeaf(t:Trie) : Bool { }; } }; - -// XXX: until AST-42: -func isEmpty(t:Trie) : Bool { - switch t { - case null { true }; - case (?_) { false }; - }; -}; - -// XXX: until AST-42: -func isNull(x : ?X) : Bool { - switch x { - case null { true }; - case (?_) { false }; - }; -}; - -// XXX: until AST-42: -func assertIsNull(x : ?X) { - switch x { - case null { assert(true) }; - case (?_) { assert(false) }; - }; -}; - -// XXX: until AST-42: -func assertIsEmpty(t : Trie) { - switch t { - case null { assert(true) }; - case (?_) { assert(false) }; - }; -}; - // XXX: until AST-42: func assertIsBin(t : Trie) { switch t { @@ -164,21 +176,6 @@ func assertIsBin(t : Trie) { } }; -// A "twig" is a binary node with two empty subtrees. It arises -// internally, for simplifying some pattern-matching logic that -// deals with pairs of tries that we try to walk recursively. -func isTwig(t : Trie) : Bool { - switch t { - case null { false }; - case (?n) { - isEmpty(n.left) and - isEmpty(n.right) and - isNull(n.key) and - isNull(n.val); - }; - } -}; - // XXX: until AST-42: func getLeafKey(t : Node) : K { assertIsNull>(t.left); @@ -211,9 +208,8 @@ func getHashBit(h:Hash, pos:Nat) : Bool { } }; -func empty() : Trie = - null -; +// 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 { @@ -398,25 +394,32 @@ func diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { // Produces a "disjunctive image" of the two tries, where the values of // matching keys are combined with the given binary operator. func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) : Trie { - // We use these "twigs" to simplify the pattern-matching cases - // below. twigs are equivalent to empty tries. we introduce them - // when one side or the other (but not both) input tries is empty. - // - let leftTwig : Trie = makeBin(makeEmpty(),makeEmpty()); - let rightTwig : Trie = makeBin(makeEmpty(),makeEmpty()); - // - // using twigs, we only ever handle the cases for: - // empty-empty, bin-bin, leaf-leaf, leaf-empty, empty-leaf. - // - // in particular, we do not explicitly handle: - // empty-bin, bin-empty, leaf-bin, bin-leaf. - // + 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)) } + } + }; + }}; + 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)) } + } + }; + }}; func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { // empty-empty terminates early, all other cases do not. - case (null, null) { makeEmpty() }; - case (null, _ ) { rec(leftTwig, tr) }; - case (_, null) { rec(tl, rightTwig) }; + case (null, null) { makeEmpty() }; + case (null, _ ) { recR(tr) }; + case (_, null) { recL(tl) }; case (? nl, ? nr) { switch (isBin(tl), isBin(tr)) { case (true, true) { @@ -435,8 +438,8 @@ func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) makeEmpty() }; case (false, false) { - assert(isLeaf(tl) or isTwig(tl)); - assert(isLeaf(tr) or isTwig(tr)); + assert(isLeaf(tl)); + assert(isLeaf(tr)); switch (nl.key, nl.val, nr.key, nr.val) { // leaf-leaf case case (?kl, ?vl, ?kr, ?vr) { @@ -447,18 +450,6 @@ func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) makeEmpty() } }; - // empty-leaf case - case (null, null, ?kr, ?vr) { - makeLeaf(kr, vbin(null, ?vr)) - }; - // leaf-empty case - case (?kl, ?vl, null, null) { - makeLeaf(kl, vbin(?vl, null)) - }; - // empty-empty case - case (null, null, null, null) { - makeEmpty() - }; // XXX impossible, and unnecessary with AST-42. case _ { makeEmpty() }; } @@ -469,7 +460,6 @@ func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) rec(tl, tr) }; - // This operation generalizes the notion of "set intersection" to // finite maps. Produces a "conjuctive image" of the two tries, where // the values of matching keys are combined with the given binary @@ -478,7 +468,7 @@ func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { case (null, null) { return makeEmpty() }; - case (null, ? nr) { return makeEmpty() }; + case (null, ? nr) { return makeEmpty() }; case (? nl, null) { return makeEmpty() }; case (? nl, ? nr) { switch (isBin(tl), isBin(tr)) { @@ -487,7 +477,7 @@ func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : let t1 = rec(nl.right, nr.right); makeBin(t0, t1) }; - case (false, true) { + case (false, true) { assert(false); // XXX impossible, until we lift uniform depth assumption makeEmpty() @@ -520,6 +510,48 @@ func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : rec(tl, tr) }; +func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { + func rec(t:Trie) : X { + 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)) }; + } + }; + }}; + rec(t) +}; + +// Test for equality, but naively, based on structure. +// Does not attempt to remove "junk" in the tree; +// For instance, a "smarter" approach would equate +// `#bin{left=#empty;right=#empty}` +// with +// `#empty`. +// We do not observe that equality here. +func equalStructure( + tl:Trie, + tr:Trie, + keq:(K,K)->Bool, + veq:(V,V)->Bool +) : Bool { + func rec(tl:Trie, tr:Trie) : Bool { + switch (tl, tr) { + case (null, null) { true }; + 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 } + } + }; + }}; + rec(tl, tr) +}; + /////////////////////////////////////////////////////////////////////// /* @@ -552,6 +584,19 @@ func setRemove(s:Set, x:T, xh:Hash):Set = { s2 }; +func setEq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { + // XXX: Todo: use a smarter check + equalStructure(s1, s2, eq, unitEq) +}; + +func setCard(s:Set) : Nat { + foldUp + (s, + func(n:Nat,m:Nat):Nat{n+m}, + func(_:T,_:()):Nat{1}, + 0) +}; + func setMem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { switch (find(s, x, xh, eq)) { case null { false }; @@ -626,6 +671,7 @@ func setPrint(s:Set) { //////////////////////////////////////////////////////////////////////////////// func natEq(n:Nat,m:Nat):Bool{ n == m}; +func unitEq (_:(),_:()):Bool{ true }; func setInsertDb(s:Set, x:Nat, xh:Hash):Set = { print " setInsert("; @@ -655,8 +701,10 @@ func setUnionDb(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { print ", "; print s2name; print ")"; + // also: test that merge agrees with disj: let r1 = setUnion(s1, s2); let r2 = disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); + assert(equalStructure(r1, r2, natEq, unitEq)); print ";\n"; setPrint(r1); print "=========\n"; @@ -691,15 +739,34 @@ let hash_8 = ?(true, ?(false,?(false,?(false, null)))); print "inserting...\n"; // Insert numbers [0..8] into the set, using their bits as their hashes: let s0 : Set = setEmpty(); +assert(setCard(s0) == 0); + let s1 : Set = setInsertDb(s0, 0, hash_0); +assert(setCard(s1) == 1); + let s2 : Set = setInsertDb(s1, 1, hash_1); +assert(setCard(s2) == 2); + let s3 : Set = setInsertDb(s2, 2, hash_2); +assert(setCard(s3) == 3); + let s4 : Set = setInsertDb(s3, 3, hash_3); +assert(setCard(s4) == 4); + let s5 : Set = setInsertDb(s4, 4, hash_4); +assert(setCard(s5) == 5); + let s6 : Set = setInsertDb(s5, 5, hash_5); +assert(setCard(s6) == 6); + let s7 : Set = setInsertDb(s6, 6, hash_6); +assert(setCard(s7) == 7); + let s8 : Set = setInsertDb(s7, 7, hash_7); +assert(setCard(s8) == 8); + let s9 : Set = setInsertDb(s8, 8, hash_8); +assert(setCard(s9) == 9); print "done.\n"; print "unioning...\n"; From 5ddfcbbf25554446eb844db60859fabadbad3ebc Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Fri, 22 Feb 2019 05:48:52 -0700 Subject: [PATCH 29/52] hashtrie: filter --- samples/collections/hashtrie.as | 77 ++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index c2f0e5276af..278db3a7298 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -101,13 +101,18 @@ func assertIsNull(x : ?X) { func makeEmpty() : Trie = null; -// XXX: until AST-42: -func isEmpty(t:Trie) : Bool { - switch t { - case null { true }; - case (?_) { false }; - }; -}; +// Note: More general version of this operation below, which tests for +// "deep emptiness" (subtrees that have branching structure, but no +// leaves; these can result from naive filtering operations, for +// instance). +// +// // XXX: until AST-42: +// func isEmpty(t:Trie) : Bool { +// switch t { +// case null { true }; +// case (?_) { false }; +// }; +// }; // XXX: until AST-42: func assertIsEmpty(t : Trie) { @@ -524,6 +529,58 @@ func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { rec(t) }; +// Test for "deep emptiness": subtrees that have branching structure, +// but no leaves. These can result from naive filtering operations; +// filter uses this function to avoid creating such subtrees. +func isEmpty(t:Trie) : Bool { + func rec(t:Trie) : Bool { + switch t { + case (null) { true }; + case (?n) { + switch (matchLeaf(t)) { + case (?(k,v)) { false }; + case null { rec(n.left) and rec(n.right) }; + } + }; + } + }; + rec(t) +}; + +func filter(t:Trie, f:(K,V)->Bool) : Trie { + func rec(t:Trie) : Trie { + 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); + } + }; + } + }; + } + }; + rec(t) +}; + // Test for equality, but naively, based on structure. // Does not attempt to remove "junk" in the tree; // For instance, a "smarter" approach would equate @@ -591,9 +648,9 @@ func setEq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { func setCard(s:Set) : Nat { foldUp - (s, - func(n:Nat,m:Nat):Nat{n+m}, - func(_:T,_:()):Nat{1}, + (s, + func(n:Nat,m:Nat):Nat{n+m}, + func(_:T,_:()):Nat{1}, 0) }; From 3a3524b372bac606772aed0e81f7df216c1476f2 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Fri, 22 Feb 2019 05:51:55 -0700 Subject: [PATCH 30/52] hashtrie: mapFilter --- samples/collections/hashtrie.as | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 278db3a7298..1d4c75a0311 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -547,7 +547,7 @@ func isEmpty(t:Trie) : Bool { rec(t) }; -func filter(t:Trie, f:(K,V)->Bool) : Trie { +func filter(t:Trie, f:(K,V)->Bool) : Trie { func rec(t:Trie) : Trie { switch t { case (null) { null }; @@ -581,6 +581,34 @@ func filter(t:Trie, f:(K,V)->Bool) : Trie { rec(t) }; +func mapFilter(t:Trie, f:(K,V)->?(K,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); + } + }; + } + }; + } + }; + rec(t) +}; + // Test for equality, but naively, based on structure. // Does not attempt to remove "junk" in the tree; // For instance, a "smarter" approach would equate From ebc656ab0e82c54c143b563a50aca263fe9872ba Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Fri, 22 Feb 2019 06:07:57 -0700 Subject: [PATCH 31/52] hashtrie: fold, exists, forall --- samples/collections/hashtrie.as | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 1d4c75a0311..0fe728fc488 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -515,6 +515,10 @@ func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : rec(tl, tr) }; +// This operation gives a recursor for the internal structure of +// tries. Many common operations are instantiations of this function, +// either as clients, or as hand-specialized versions (e.g., see map, +// mapFilter, exists and forAll below). func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { func rec(t:Trie) : X { switch t { @@ -529,6 +533,53 @@ func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { rec(t) }; +// Fold over the key-value pairs of the trie, using an accumulator. +// The key-value pairs have no reliable or meaningful ordering. +func fold(t:Trie, f:(K,V,X)->X, x:X) : X { + func rec(t:Trie, x:X) : X { + 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)) }; + } + }; + }}; + rec(t, x) +}; + +// specialized foldUp operation. +func exists(t:Trie, f:(K,V)->Bool) : Bool { + func rec(t:Trie) : Bool { + 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) }; + } + }; + }}; + 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) }; + } + }; + }}; + rec(t) +}; + +// specialized foldUp operation. // Test for "deep emptiness": subtrees that have branching structure, // but no leaves. These can result from naive filtering operations; // filter uses this function to avoid creating such subtrees. @@ -637,6 +688,8 @@ func equalStructure( rec(tl, tr) }; + + /////////////////////////////////////////////////////////////////////// /* From 12a7c2fe0187cf13b1cecd798974d8f30b257d5e Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Fri, 22 Feb 2019 06:10:45 -0700 Subject: [PATCH 32/52] minor: doc disj --- samples/collections/hashtrie.as | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 0fe728fc488..043b0045185 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -398,6 +398,12 @@ func diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { // This operation generalizes the notion of "set union" to finite maps. // Produces a "disjunctive image" of the two tries, 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(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) : Trie { func recL(t:Trie) : Trie { switch t { @@ -468,7 +474,7 @@ func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) // This operation generalizes the notion of "set intersection" to // finite maps. Produces a "conjuctive image" of the two tries, where // the values of matching keys are combined with the given binary -// operator, and unmatched key-value pairrs are not present in the output. +// 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) : Trie { func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { From 04795342f16683cdbd035f4d7315fe5e7876a374 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Fri, 22 Feb 2019 06:21:18 -0700 Subject: [PATCH 33/52] hashtrie: update TODOs --- samples/collections/hashtrie.as | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 043b0045185..4381b9517c7 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -26,15 +26,19 @@ // - replace operation (remove+insert via a single traversal) // - basic encoding of sets, and some set operations // - basic tests (and primitive debugging) for set operations - -// TODO-Matthew: -// // - write trie operations that operate over pairs of tries: // for set union, difference and intersection. + +// TODO-Matthew: // // - (more) regression tests for everything that is below // -// - handle hash collisions gracefully +// - 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. // // - iterator objects, for use in 'for ... in ...' patterns From 06700be05a5585ea28821117466744c8e9922383 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 14:30:22 -0700 Subject: [PATCH 34/52] package list functions into a record, in lieu of modules; cleanup --- samples/collections/list.as | 285 +++++++++++++++++------------------- 1 file changed, 135 insertions(+), 150 deletions(-) diff --git a/samples/collections/list.as b/samples/collections/list.as index c12f63e6299..5844183aefe 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -1,10 +1,10 @@ -/* +/* * Lists, a la functional programming, in ActorScript. */ // Done: // -// - standard list definition +// - standard list definition // - standard list recursors: foldl, foldr, iter // - standard higher-order combinators: map, filter, etc. // - (Every function here: http://sml-family.org/Basis/list.html) @@ -26,11 +26,11 @@ type List = ?(T, List); // empty list -func nil() : List = +func List__nil() : List = null; // test for empty list -func isNil(l : List) : Bool { +func List__isNil(l : List) : Bool { switch l { case null { true }; case _ { false }; @@ -38,36 +38,20 @@ func isNil(l : List) : Bool { }; // aka "list cons" -func push(x : T, l : List) : List = +func List__push(x : T, l : List) : List = ?(x, l); -// XXX: deprecated (use pattern matching instead) -func head(l : List) : ?T = { - switch l { - case null { null }; - case (?(h, _)) { ?h }; - } -}; - -// XXX: deprecated (use pattern matching instead) -func tail(l : List) : List = { - switch l { - case null { null }; - case (?(_, t)) { t }; - } -}; - // last element, optionally; tail recursive -func last(l : List) : ?T = { +func List__last(l : List) : ?T = { switch l { case null { null }; case (?(x,null)) { ?x }; - case (?(_,t)) { last(t) }; + case (?(_,t)) { List__last(t) }; } }; // treat the list as a stack; combines 'hd' and (non-failing) 'tl' into one operation -func pop(l : List) : (?T, List) = { +func List__pop(l : List) : (?T, List) = { switch l { case null { (null, null) }; case (?(h, t)) { (?h, t) }; @@ -75,7 +59,7 @@ func pop(l : List) : (?T, List) = { }; // length; tail recursive -func len(l : List) : Nat = { +func List__len(l : List) : Nat = { func rec(l : List, n : Nat) : Nat { switch l { case null { n }; @@ -86,16 +70,16 @@ func len(l : List) : Nat = { }; // array-like list access, but in linear time; tail recursive -func nth(l : List, n : Nat) : ?T = { +func List__nth(l : List, n : Nat) : ?T = { switch (n, l) { - case (_, null) { null }; - case (0, ?(h,t)) { ?h }; - case (_, ?(_,t)) { nth(t, n - 1) }; + case (_, null) { null }; + case (0, (?(h,t))) { ?h }; + case (_, (?(_,t))) { List__nth(t, n - 1) }; } }; // reverse; tail recursive -func rev(l : List) : List = { +func List__rev(l : List) : List = { func rec(l : List, r : List) : List { switch l { case null { r }; @@ -106,7 +90,7 @@ func rev(l : List) : List = { }; // Called "app" in SML Basis, and "iter" in OCaml; tail recursive -func iter(l : List, f:T -> ()) : () = { +func List__iter(l : List, f:T -> ()) : () = { func rec(l : List) : () { switch l { case null { () }; @@ -118,7 +102,7 @@ func iter(l : List, f:T -> ()) : () = { // map; non-tail recursive // (Note: need mutable Cons tails for tail-recursive map) -func map(l : List, f:T -> S) : List = { +func List__map(l : List, f:T -> S) : List = { func rec(l : List) : List { switch l { case null { null }; @@ -130,7 +114,7 @@ func map(l : List, f:T -> S) : List = { // filter; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func filter(l : List, f:T -> Bool) : List = { +func List__filter(l : List, f:T -> Bool) : List = { func rec(l : List) : List { switch l { case null { null }; @@ -142,7 +126,7 @@ func filter(l : List, f:T -> Bool) : List = { // map-and-filter; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func mapFilter(l : List, f:T -> ?S) : List = { +func List__mapFilter(l : List, f:T -> ?S) : List = { func rec(l : List) : List { switch l { case null { null }; @@ -159,7 +143,7 @@ func mapFilter(l : List, f:T -> ?S) : List = { // append; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func append(l : List, m : List) : List = { +func List__append(l : List, m : List) : List = { func rec(l : List) : List { switch l { case null { m }; @@ -170,45 +154,45 @@ func append(l : List, m : List) : List = { }; // concat (aka "list join"); tail recursive, but requires "two passes" -func concat(l : List>) : List = { +func List__concat(l : List>) : List = { // 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) + let r = + { let f = func(a:List, b:List) : List { List__revAppend(a,b) }; + List__foldLeft, List>(l, null, f) }; // 2/2: ...re-reverse the elements, to their original order: - rev(r) + List__rev(r) }; // (See SML Basis library); tail recursive -func revAppend(l1 : List, l2 : List) : List = { +func List__revAppend(l1 : List, l2 : List) : List = { switch l1 { case null { l2 }; - case (?(h,t)) { revAppend(t, ?(h,l2)) }; + case (?(h,t)) { List__revAppend(t, ?(h,l2)) }; } }; // take; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func take(l : List, n:Nat) : List = { +func List__take(l : List, n:Nat) : List = { switch (l, n) { case (_, 0) { null }; case (null,_) { null }; - case (?(h, t), m) {?(h, take(t, m - 1))}; + case (?(h, t), m) {?(h, List__take(t, m - 1))}; } }; // drop; tail recursive -func drop(l : List, n:Nat) : List = { +func List__drop(l : List, n:Nat) : List = { switch (l, n) { case (l_, 0) { l_ }; case (null, _) { null }; - case ((?(h,t)), m) { drop(t, m - 1) }; + case ((?(h,t)), m) { List__drop(t, m - 1) }; } }; // fold list left-to-right using f; tail recursive -func foldLeft(l : List, a:S, f:(T,S) -> S) : S = { +func List__foldLeft(l : List, a:S, f:(T,S) -> S) : S = { func rec(l:List, a:S) : S = { switch l { case null { a }; @@ -219,7 +203,7 @@ func foldLeft(l : List, a:S, f:(T,S) -> S) : S = { }; // fold list right-to-left using f; non-tail recursive -func foldRight(l : List, a:S, f:(T,S) -> S) : S = { +func List__foldRight(l : List, a:S, f:(T,S) -> S) : S = { func rec(l:List) : S = { switch l { case null { a }; @@ -230,7 +214,7 @@ func foldRight(l : List, a:S, f:(T,S) -> S) : S = { }; // test if there exists list element for which given predicate is true -func find(l: List, f:T -> Bool) : ?T = { +func List__find(l: List, f:T -> Bool) : ?T = { func rec(l:List) : ?T { switch l { case null { null }; @@ -241,7 +225,7 @@ func find(l: List, f:T -> Bool) : ?T = { }; // test if there exists list element for which given predicate is true -func exists(l: List, f:T -> Bool) : Bool = { +func List__exists(l: List, f:T -> Bool) : Bool = { func rec(l:List) : Bool { switch l { case null { false }; @@ -254,7 +238,7 @@ func exists(l: List, f:T -> Bool) : Bool = { }; // test if given predicate is true for all list elements -func all(l: List, f:T -> Bool) : Bool = { +func List__all(l: List, f:T -> Bool) : Bool = { func rec(l:List) : Bool { switch l { case null { true }; @@ -265,7 +249,7 @@ func all(l: List, f:T -> Bool) : Bool = { }; // Given two ordered lists, merge them into a single ordered list -func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { +func List__merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { func rec(l1: List, l2: List) : List { switch (l1, l2) { case (null, _) { l2 }; @@ -285,7 +269,7 @@ func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { // Compare two lists lexicographic` ordering. tail recursive. // XXX: Eventually, follow `collate` design from SML Basis, with real sum types, use 3-valued `order` type here. // -func lessThanEq(l1: List, l2: List, lte:(T,T) -> Bool) : Bool { +func List__lessThanEq(l1: List, l2: List, lte:(T,T) -> Bool) : Bool { func rec(l1: List, l2: List) : Bool { switch (l1, l2) { case (null, _) { true }; @@ -304,7 +288,7 @@ func lessThanEq(l1: List, l2: List, lte:(T,T) -> Bool) : Bool { // Compare two lists for equality. tail recursive. // `isEq(l1, l2)` =equiv= `lessThanEq(l1,l2) && lessThanEq(l2,l1)`, but the former is more efficient. -func isEq(l1: List, l2: List, eq:(T,T) -> Bool) : Bool { +func List__isEq(l1: List, l2: List, eq:(T,T) -> Bool) : Bool { func rec(l1: List, l2: List) : Bool { switch (l1, l2) { case (null, null) { true }; @@ -324,7 +308,7 @@ func isEq(l1: List, l2: List, eq:(T,T) -> Bool) : Bool { // using a predicate, create two lists from one: the "true" list, and the "false" list. // (See SML basis library); non-tail recursive -func partition(l: List, f:T -> Bool) : (List, List) { +func List__partition(l: List, f:T -> Bool) : (List, List) { func rec(l: List) : (List, List) { switch l { case null { (null, null) }; @@ -343,113 +327,114 @@ func partition(l: List, f:T -> Bool) : (List, List) { // generate a list based on a length, and a function from list index to list element; // (See SML basis library); non-tail recursive -func tabulate(n:Nat, f:Nat -> T) : List { +func List__tabulate(n:Nat, f:Nat -> T) : List { func rec(i:Nat) : List { if (i == n) { null } else { ?(f(i), rec(i+1)) } }; rec(0) }; + +// Create a record, +// as a standin until we have "real" modules to create namespaces: +let List = new { + // Meta-level stuff: + // --------------------- + moduleName = "List" + + // Actual module contents + // ----------------------- + ; nil = List__nil + ; isNil = List__isNil + ; push = List__push + ; last = List__last + ; pop = List__pop + ; len = List__len + ; nth = List__nth + ; rev = List__rev + ; iter = List__iter + ; filter = List__filter + ; mapFilter = List__mapFilter + ; append = List__append + ; concat = List__concat + ; revAppend = List__revAppend + ; take = List__take + ; drop = List__drop + ; foldLeft = List__foldLeft + ; foldRight = List__foldRight + ; find = List__find + ; exists = List__exists + ; all = List__all + ; merge = List__merge + ; lessThanEq = List__lessThanEq + ; partition = List__partition + ; tabulate = List__tabulate +}; + ////////////////////////////////////////////////////////////////// // # Example usage type X = Nat; -func opnatEq(a : ?Nat, b : ?Nat) : Bool { - switch (a, b) { - case (null, null) { true }; - case (?aaa, ?bbb) { aaa == bbb }; - case (_, _ ) { false }; - } -}; -func opnat_isnull(a : ?Nat) : Bool { - switch a { - case (null) { true }; - case (?aaa) { false }; - } -}; - -// ## Construction -let l1 = nil(); -let l2 = push(2, l1); -let l3 = push(3, l2); -// ## Projection -- use nth -assert (opnatEq(nth(l3, 0), ?3)); -assert (opnatEq(nth(l3, 1), ?2)); -assert (opnatEq(nth(l3, 2), null)); -//assert (opnatEq (hd(l3), ?3)); -//assert (opnatEq (hd(l2), ?2)); -//assert (opnat_isnull(hd(l1))); +func List__tests() { -/* -// ## Projection -- use nth -assert (opnatEq(nth(l3, 0), ?3)); -assert (opnatEq(nth(l3, 1), ?2)); -assert (opnatEq(nth(l3, 2), null)); -assert (opnatEq (hd(l3), ?3)); -assert (opnatEq (hd(l2), ?2)); -assert (opnat_isnull(hd(l1))); -*/ - -// ## Deconstruction -let (a1, t1) = pop(l3); -assert (opnatEq(a1, ?3)); -let (a2, t2) = pop(l2); -assert (opnatEq(a2, ?2)); -let (a3, t3) = pop(l1); -assert (opnatEq(a3, null)); -assert (isNil(t3)); - -// ## List functions -assert (len(l1) == 0); -assert (len(l2) == 1); -assert (len(l3) == 2); - -// ## List functions -assert (len(l1) == 0); -assert (len(l2) == 1); -assert (len(l3) == 2); + func opnatEq(a : ?Nat, b : ?Nat) : Bool { + switch (a, b) { + case (null, null) { true }; + case (?aaa, ?bbb) { aaa == bbb }; + case (_, _ ) { false }; + } + }; + func opnat_isnull(a : ?Nat) : Bool { + switch a { + case (null) { true }; + case (?aaa) { false }; + } + }; -// -// TODO: Write list equaliy test; write tests for each function -// + // ## Construction + let l1 = List__nil(); + let l2 = List__push(2, l1); + let l3 = List__push(3, l2); + + // ## Projection -- use nth + assert (opnatEq(List__nth(l3, 0), ?3)); + assert (opnatEq(List__nth(l3, 1), ?2)); + assert (opnatEq(List__nth(l3, 2), null)); + //assert (opnatEq (hd(l3), ?3)); + //assert (opnatEq (hd(l2), ?2)); + //assert (opnat_isnull(hd(l1))); + + /* + // ## Projection -- use nth + assert (opnatEq(nth(l3, 0), ?3)); + assert (opnatEq(nth(l3, 1), ?2)); + assert (opnatEq(nth(l3, 2), null)); + assert (opnatEq (hd(l3), ?3)); + assert (opnatEq (hd(l2), ?2)); + assert (opnat_isnull(hd(l1))); + */ + + // ## Deconstruction + let (a1, t1) = List.pop(l3); + assert (opnatEq(a1, ?3)); + let (a2, t2) = List.pop(l2); + assert (opnatEq(a2, ?2)); + let (a3, t3) = List.pop(l1); + assert (opnatEq(a3, null)); + assert (List.isNil(t3)); + + // ## List functions + assert (List.len(l1) == 0); + assert (List.len(l2) == 1); + assert (List.len(l3) == 2); + + // ## List functions + assert (List.len(l1) == 0); + assert (List.len(l2) == 1); + assert (List.len(l3) == 2); +}; -//////////////////////////////////////////////////////////////// -// For comparison: -// -// SML Basis Library Interface -// http://sml-family.org/Basis/list.html -// -// datatype 'a list = nil | :: of 'a * 'a list -// exception Empty -// -// Done in AS (marked "x"): -// ----------------------------------------------------------------- -// x val null : 'a list -> bool -// x val length : 'a list -> int -// x val @ : 'a list * 'a list -> 'a list -// x val hd : 'a list -> 'a -// x val tl : 'a list -> 'a list -// x val last : 'a list -> 'a -// ??? val getItem : 'a list -> ('a * 'a list) option --------- Q: What does this function "do"? Is it just witnessing a type isomorphism? -// x val nth : 'a list * int -> 'a -// x val take : 'a list * int -> 'a list -// x val drop : 'a list * int -> 'a list -// x val rev : 'a list -> 'a list -// x val concat : 'a list list -> 'a list -// x val revAppend : 'a list * 'a list -> 'a list -// x val app : ('a -> unit) -> 'a list -> unit -// x val map : ('a -> 'b) -> 'a list -> 'b list -// x val mapPartial : ('a -> 'b option) -> 'a list -> 'b list -// x val find : ('a -> bool) -> 'a list -> 'a option -// x val filter : ('a -> bool) -> 'a list -> 'a list -// x val partition : ('a -> bool) -> 'a list -> 'a list * 'a list -// x val foldl : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b -// x val foldr : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b -// x val exists : ('a -> bool) -> 'a list -> bool -// x val all : ('a -> bool) -> 'a list -> bool -// x val tabulate : int * (int -> 'a) -> 'a list -// x val collate : ('a * 'a -> order) -> 'a list * 'a list -> order -// -//////////////////////////////////////////////////////////// +// Run the tests +List__tests(); From 33076854ac08915a7737c498a055d9d2d41b399a Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 15:41:13 -0700 Subject: [PATCH 35/52] Trie and Set modules; Makefile for regression tests --- samples/collections/Makefile | 31 ++ samples/collections/README.md | 3 +- samples/collections/hashtrie.as | 585 ++++++++------------------------ samples/collections/set.as | 381 +++++++++++++++++++++ 4 files changed, 554 insertions(+), 446 deletions(-) create mode 100644 samples/collections/Makefile create mode 100644 samples/collections/set.as diff --git a/samples/collections/Makefile b/samples/collections/Makefile new file mode 100644 index 00000000000..b9d945eadae --- /dev/null +++ b/samples/collections/Makefile @@ -0,0 +1,31 @@ +ASC=../../src/asc +MODULE_NAME="\x1b[1;32mModule:\x1b[1;34m" +BEGIN="\x1b[0;1mBegin...\x1b[0m" +DONE="\x1b[1mDone.\n---------------------------------------------------\x1b[0m" + +.PHONY: default all Trie List Set + +default: all + +all: \ + List \ + Trie \ + Set + +List: + @echo $(MODULE_NAME) $@ + @echo $(BEGIN) + $(ASC) -r list.as + @echo $(DONE) + +Trie: + @echo $(MODULE_NAME) $@ + @echo $(BEGIN) + $(ASC) -r list.as hashtrie.as + @echo $(DONE) + +Set: + @echo $(MODULE_NAME) $@ + @echo $(BEGIN) + $(ASC) -r list.as hashtrie.as set.as + @echo $(DONE) diff --git a/samples/collections/README.md b/samples/collections/README.md index 6c8bf969500..1be6257d30c 100644 --- a/samples/collections/README.md +++ b/samples/collections/README.md @@ -3,7 +3,8 @@ Critical modules ================== - [x] **List**: See [`List` module from SML Basis library](http://sml-family.org/Basis/list.html). - - [ ] **Hashtrie**: Persistent maps, as functional hash tries. + - [x] **Hashtrie**: Persistent maps, as functional hash tries. + - [x] **Set**: Persistent sets, based directly on persistent maps. - [ ] **Hashtable**: Mutable maps, as imperative hash tables. Secondary modules diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 4381b9517c7..9e5f2e3dd55 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -33,7 +33,7 @@ // // - (more) regression tests for everything that is below // -// - handle hash collisions gracefully; +// - handle hash collisions gracefully; // ==> Blocked on AS module support, for using List module. // // - adapt the path length of each subtree to its cardinality; avoid @@ -86,7 +86,7 @@ type LeafNode = { key:K; val:V }; */ // XXX: until AST-42: -func isNull(x : ?X) : Bool { +func Trie__isNull(x : ?X) : Bool { switch x { case null { true }; case (?_) { false }; @@ -94,7 +94,7 @@ func isNull(x : ?X) : Bool { }; // XXX: until AST-42: -func assertIsNull(x : ?X) { +func Trie__assertIsNull(x : ?X) { switch x { case null { assert(true) }; case (?_) { assert(false) }; @@ -102,7 +102,7 @@ func assertIsNull(x : ?X) { }; // XXX: until AST-42: -func makeEmpty() : Trie +func Trie__makeEmpty() : Trie = null; // Note: More general version of this operation below, which tests for @@ -119,7 +119,7 @@ func makeEmpty() : Trie // }; // XXX: until AST-42: -func assertIsEmpty(t : Trie) { +func Trie__assertIsEmpty(t : Trie) { switch t { case null { assert(true) }; case (?_) { assert(false) }; @@ -127,12 +127,12 @@ func assertIsEmpty(t : Trie) { }; // XXX: until AST-42: -func makeBin(l:Trie, r:Trie) : Trie { +func Trie__makeBin(l:Trie, r:Trie) : Trie { ?(new {left=l; right=r; key=null; val=null }) }; // XXX: until AST-42: -func isBin(t:Trie) : Bool { +func Trie__isBin(t:Trie) : Bool { switch t { case null { false }; case (?t_) { @@ -145,12 +145,12 @@ func isBin(t:Trie) : Bool { }; // XXX: until AST-42: -func makeLeaf(k:K, v:V) : Trie { +func Trie__makeLeaf(k:K, v:V) : Trie { ?(new {left=null; right=null; key=?k; val=?v }) }; // XXX: until AST-42: -func matchLeaf(t:Trie) : ?(K,V) { +func Trie__matchLeaf(t:Trie) : ?(K,V) { switch t { case null { null }; case (?t_) { @@ -163,7 +163,7 @@ func matchLeaf(t:Trie) : ?(K,V) { }; // XXX: until AST-42: -func isLeaf(t:Trie) : Bool { +func Trie__isLeaf(t:Trie) : Bool { switch t { case null { false }; case (?t_) { @@ -175,36 +175,36 @@ func isLeaf(t:Trie) : Bool { } }; // XXX: until AST-42: -func assertIsBin(t : Trie) { +func Trie__assertIsBin(t : Trie) { switch t { case null { assert(false) }; case (?n) { - assertIsNull(n.key); - assertIsNull(n.val); + Trie__assertIsNull(n.key); + Trie__assertIsNull(n.val); }; } }; // XXX: until AST-42: -func getLeafKey(t : Node) : K { - assertIsNull>(t.left); - assertIsNull>(t.right); +func Trie__getLeafKey(t : Node) : K { + Trie__assertIsNull>(t.left); + Trie__assertIsNull>(t.right); switch (t.key) { case (?k) { k }; - case null { getLeafKey(t) }; + case null { Trie__getLeafKey(t) }; } }; // XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: -func getLeafVal(t : Node) : ?V { - assertIsNull>(t.left); - assertIsNull>(t.right); +func Trie__getLeafVal(t : Node) : ?V { + Trie__assertIsNull>(t.left); + Trie__assertIsNull>(t.right); t.val }; // TODO: Replace with bitwise operations on Words, once we have each of those in AS. // For now, we encode hashes as lists of booleans. -func getHashBit(h:Hash, pos:Nat) : Bool { +func Trie__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. @@ -212,21 +212,21 @@ func getHashBit(h:Hash, pos:Nat) : Bool { }; case (?(b, h_)) { if (pos == 0) { b } - else { getHashBit(h_, pos-1) } + else { Trie__getHashBit(h_, pos-1) } }; } }; // part of "public interface": -func empty() : Trie = makeEmpty(); +func Trie__empty() : Trie = Trie__makeEmpty(); // helper function for constructing new paths of uniform length -func buildNewPath(bitpos:Nat, k:K, k_hash:Hash, ov:?V) : Trie { +func Trie__buildNewPath(bitpos:Nat, k:K, k_hash:Hash, 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); + let bit = Trie__getHashBit(k_hash, bitpos); if (not bit) { ?(new {left=path; right=null; key=null; val=null}) } @@ -242,15 +242,15 @@ func buildNewPath(bitpos:Nat, k:K, k_hash:Hash, ov:?V) : Trie { }; // 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 Trie__replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { // 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 null { (Trie__buildNewPath(bitpos, k, k_hash, v), null) }; case (?n) { - assertIsBin(t); - let bit = getHashBit(k_hash, bitpos); + Trie__assertIsBin(t); + let bit = Trie__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); @@ -265,7 +265,7 @@ func replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?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 null { (Trie__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) @@ -277,17 +277,17 @@ func replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { }; // 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 Trie__insert(t : Trie, k:K, k_hash:Hash, v:V) : (Trie, ?V) { + Trie__replace(t, k, k_hash, ?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 Trie__remove(t : Trie, k:K, k_hash:Hash) : (Trie, ?V) { + Trie__replace(t, k, k_hash, 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 Trie__find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { // 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 ) { @@ -297,8 +297,8 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { null }; case (?n) { - assertIsBin(t); - let bit = getHashBit(k_hash, bitpos); + Trie__assertIsBin(t); + let bit = Trie__getHashBit(k_hash, bitpos); if (not bit) { rec(n.left, bitpos+1) } else { rec(n.right, bitpos+1) } }; @@ -309,8 +309,8 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { 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) + if (keq(Trie__getLeafKey(l), k)) { + Trie__getLeafVal(l) } else { null } @@ -325,16 +325,17 @@ func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { // 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 { +func Trie__merge(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { case (null, _) { return tr }; case (_, null) { return tl }; case (?nl,?nr) { - switch (isBin(tl), isBin(tr)) { + switch (Trie__isBin(tl), + Trie__isBin(tr)) { case (true, true) { - let t0 = merge(nl.left, nr.left); - let t1 = merge(nl.right, nr.right); - makeBin(t0, t1) + let t0 = Trie__merge(nl.left, nr.left); + let t1 = Trie__merge(nl.right, nr.right); + Trie__makeBin(t0, t1) }; case (false, true) { assert(false); @@ -358,17 +359,18 @@ func merge(tl:Trie, tr:Trie) : Trie { // 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 Trie__diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { - case (null, _) { return makeEmpty() }; + case (null, _) { return Trie__makeEmpty() }; case (_, null) { return tl }; case (?nl,?nr) { - switch (isBin(tl), isBin(tr)) { + switch (Trie__isBin(tl), + Trie__isBin(tr)) { case (true, true) { let t0 = rec(nl.left, nr.left); let t1 = rec(nl.right, nr.right); - makeBin(t0, t1) + Trie__makeBin(t0, t1) }; case (false, true) { assert(false); @@ -385,7 +387,7 @@ func diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { switch (nl.key, nr.key) { case (?kl, ?kr) { if (keq(kl, kr)) { - makeEmpty(); + Trie__makeEmpty(); } else { tl }}; @@ -408,14 +410,17 @@ func diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { // situations, the operator accepts optional values, but is never // applied to (null, null). // -func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) : Trie { +func Trie__disj(tl:Trie, tr:Trie, + keq:(K,K)->Bool, vbin:(?V,?W)->X) + : Trie +{ 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)) } + switch (Trie__matchLeaf(t)) { + case (?(k,v)) { Trie__makeLeaf(k, vbin(?v, null)) }; + case _ { Trie__makeBin(recL(n.left), recL(n.right)) } } }; }}; @@ -423,50 +428,51 @@ func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) 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)) } + switch (Trie__matchLeaf(t)) { + case (?(k,w)) { Trie__makeLeaf(k, vbin(null, ?w)) }; + case _ { Trie__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. - case (null, null) { makeEmpty() }; + case (null, null) { Trie__makeEmpty() }; case (null, _ ) { recR(tr) }; case (_, null) { recL(tl) }; case (? nl, ? nr) { - switch (isBin(tl), isBin(tr)) { + switch (Trie__isBin(tl), + Trie__isBin(tr)) { case (true, true) { let t0 = rec(nl.left, nr.left); let t1 = rec(nl.right, nr.right); - makeBin(t0, t1) + Trie__makeBin(t0, t1) }; case (false, true) { assert(false); // XXX impossible, until we lift uniform depth assumption - makeEmpty() + Trie__makeEmpty() }; case (true, false) { assert(false); // XXX impossible, until we lift uniform depth assumption - makeEmpty() + Trie__makeEmpty() }; case (false, false) { - assert(isLeaf(tl)); - assert(isLeaf(tr)); + assert(Trie__isLeaf(tl)); + assert(Trie__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)); + Trie__makeLeaf(kl, vbin(?vl, ?vr)); } else { // XXX: handle hash collisions here. - makeEmpty() + Trie__makeEmpty() } }; // XXX impossible, and unnecessary with AST-42. - case _ { makeEmpty() }; + case _ { Trie__makeEmpty() }; } }; } @@ -479,44 +485,48 @@ func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) // finite maps. Produces a "conjuctive image" of the two tries, 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(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : Trie { +func Trie__conj(tl:Trie, tr:Trie, + keq:(K,K)->Bool, vbin:(V,W)->X) + : Trie +{ 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 (null, null) { return Trie__makeEmpty() }; + case (null, ? nr) { return Trie__makeEmpty() }; + case (? nl, null) { return Trie__makeEmpty() }; case (? nl, ? nr) { - switch (isBin(tl), isBin(tr)) { + switch (Trie__isBin(tl), + Trie__isBin(tr)) { case (true, true) { let t0 = rec(nl.left, nr.left); let t1 = rec(nl.right, nr.right); - makeBin(t0, t1) + Trie__makeBin(t0, t1) }; case (false, true) { assert(false); // XXX impossible, until we lift uniform depth assumption - makeEmpty() + Trie__makeEmpty() }; case (true, false) { assert(false); // XXX impossible, until we lift uniform depth assumption - makeEmpty() + Trie__makeEmpty() }; case (false, false) { - assert(isLeaf(tl)); - assert(isLeaf(tr)); + assert(Trie__isLeaf(tl)); + assert(Trie__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)); + Trie__makeLeaf(kl, vbin(vl, vr)); } else { // XXX: handle hash collisions here. - makeEmpty() + Trie__makeEmpty() } }; // XXX impossible, and unnecessary with AST-42. - case _ { makeEmpty() }; + case _ { Trie__makeEmpty() }; } }; } @@ -529,12 +539,12 @@ func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : // tries. Many common operations are instantiations of this function, // either as clients, or as hand-specialized versions (e.g., see map, // mapFilter, exists and forAll below). -func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { +func Trie__foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { func rec(t:Trie) : X { switch t { case (null) { empty }; case (?n) { - switch (matchLeaf(t)) { + switch (Trie__matchLeaf(t)) { case (?(k,v)) { leaf(k,v) }; case null { bin(rec(n.left), rec(n.right)) }; } @@ -545,12 +555,12 @@ func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { // Fold over the key-value pairs of the trie, using an accumulator. // The key-value pairs have no reliable or meaningful ordering. -func fold(t:Trie, f:(K,V,X)->X, x:X) : X { +func Trie__fold(t:Trie, f:(K,V,X)->X, x:X) : X { func rec(t:Trie, x:X) : X { switch t { case (null) x; case (?n) { - switch (matchLeaf(t)) { + switch (Trie__matchLeaf(t)) { case (?(k,v)) { f(k,v,x) }; case null { rec(n.left,rec(n.right,x)) }; } @@ -560,12 +570,12 @@ func fold(t:Trie, f:(K,V,X)->X, x:X) : X { }; // specialized foldUp operation. -func exists(t:Trie, f:(K,V)->Bool) : Bool { +func Trie__exists(t:Trie, f:(K,V)->Bool) : Bool { func rec(t:Trie) : Bool { switch t { case (null) { false }; case (?n) { - switch (matchLeaf(t)) { + switch (Trie__matchLeaf(t)) { case (?(k,v)) { f(k,v) }; case null { rec(n.left) or rec(n.right) }; } @@ -575,12 +585,12 @@ func exists(t:Trie, f:(K,V)->Bool) : Bool { }; // specialized foldUp operation. -func forAll(t:Trie, f:(K,V)->Bool) : Bool { +func Trie__forAll(t:Trie, f:(K,V)->Bool) : Bool { func rec(t:Trie) : Bool { switch t { case (null) { true }; case (?n) { - switch (matchLeaf(t)) { + switch (Trie__matchLeaf(t)) { case (?(k,v)) { f(k,v) }; case null { rec(n.left) and rec(n.right) }; } @@ -593,12 +603,12 @@ func forAll(t:Trie, f:(K,V)->Bool) : Bool { // Test for "deep emptiness": subtrees that have branching structure, // but no leaves. These can result from naive filtering operations; // filter uses this function to avoid creating such subtrees. -func isEmpty(t:Trie) : Bool { +func Trie__isEmpty(t:Trie) : Bool { func rec(t:Trie) : Bool { switch t { case (null) { true }; case (?n) { - switch (matchLeaf(t)) { + switch (Trie__matchLeaf(t)) { case (?(k,v)) { false }; case null { rec(n.left) and rec(n.right) }; } @@ -608,19 +618,19 @@ func isEmpty(t:Trie) : Bool { rec(t) }; -func filter(t:Trie, f:(K,V)->Bool) : Trie { +func Trie__filter(t:Trie, f:(K,V)->Bool) : Trie { func rec(t:Trie) : Trie { switch t { case (null) { null }; case (?n) { - switch (matchLeaf(t)) { + switch (Trie__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) + Trie__makeLeaf(k,v) } else { null } @@ -628,11 +638,12 @@ func filter(t:Trie, f:(K,V)->Bool) : Trie { case null { let l = rec(n.left); let r = rec(n.right); - switch (isEmpty(l),isEmpty(r)) { + switch (Trie__isEmpty(l), + Trie__isEmpty(r)) { case (true, true) null; case (false, true) r; case (true, false) l; - case (false, false) makeBin(l, r); + case (false, false) Trie__makeBin(l, r); } }; } @@ -642,25 +653,26 @@ func filter(t:Trie, f:(K,V)->Bool) : Trie { rec(t) }; -func mapFilter(t:Trie, f:(K,V)->?(K,W)) : Trie { +func Trie__mapFilter(t:Trie, f:(K,V)->?(K,W)) : Trie { func rec(t:Trie) : Trie { switch t { case (null) { null }; case (?n) { - switch (matchLeaf(t)) { + switch (Trie__matchLeaf(t)) { case (?(k,v)) { switch (f(k,v)) { case (null) null; - case (?(k,w)) { makeLeaf(k,w) }; + case (?(k,w)) { Trie__makeLeaf(k,w) }; }}; case null { let l = rec(n.left); let r = rec(n.right); - switch (isEmpty(l),isEmpty(r)) { + switch (Trie__isEmpty(l), + Trie__isEmpty(r)) { case (true, true) null; case (false, true) r; case (true, false) l; - case (false, false) makeBin(l, r); + case (false, false) Trie__makeBin(l, r); } }; } @@ -677,7 +689,7 @@ func mapFilter(t:Trie, f:(K,V)->?(K,W)) : Trie { // with // `#empty`. // We do not observe that equality here. -func equalStructure( +func Trie__equalStructure( tl:Trie, tr:Trie, keq:(K,K)->Bool, @@ -686,8 +698,11 @@ func equalStructure( func rec(tl:Trie, tr:Trie) : Bool { switch (tl, tr) { case (null, null) { true }; + case (_, null) { false }; + case (null, _) { false }; case (?nl, ?nr) { - switch (matchLeaf(tl), matchLeaf(tr)) { + switch (Trie__matchLeaf(tl), + Trie__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) }; @@ -698,348 +713,28 @@ func equalStructure( rec(tl, tr) }; - - -/////////////////////////////////////////////////////////////////////// - -/* - Sets are partial maps from element type to unit type, - i.e., the partial map represents the set with its domain. -*/ - -// TODO-Matthew: -// -// - for now, we pass a hash value each time we pass an element value; -// in the future, we might avoid passing element hashes with each element in the API; -// related to: https://dfinity.atlassian.net/browse/AST-32 -// -// - similarly, we pass an equality function when we do some operations. -// in the future, we might avoid this via https://dfinity.atlassian.net/browse/AST-32 -// - -type Set = Trie; - -func setEmpty():Set = - empty(); - -func setInsert(s:Set, x:T, xh:Hash):Set = { - let (s2, _) = insert(s, x, xh, ()); - s2 -}; - -func setRemove(s:Set, x:T, xh:Hash):Set = { - let (s2, _) = remove(s, x, xh); - s2 -}; - -func setEq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { - // XXX: Todo: use a smarter check - equalStructure(s1, s2, eq, unitEq) -}; - -func setCard(s:Set) : Nat { - foldUp - (s, - func(n:Nat,m:Nat):Nat{n+m}, - func(_:T,_:()):Nat{1}, - 0) +// Create a record, +// as a standin until we have "real" modules to create namespaces: +let Trie = new { + // Meta-level stuff: + // --------------------- + moduleName = "Trie" + + ; empty = Trie__empty + ; insert = Trie__insert + ; remove = Trie__remove + ; find = Trie__find + ; replace = Trie__replace + ; merge = Trie__merge + ; diff = Trie__diff + ; disj = Trie__disj + ; conj = Trie__conj + ; foldUp = Trie__foldUp + ; fold = Trie__fold + ; exists = Trie__exists + ; forAll = Trie__forAll + ; isEmpty = Trie__isEmpty + ; filter = Trie__filter + ; mapFilter = Trie__mapFilter + ; equalStructure = Trie__equalStructure }; - -func setMem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { - switch (find(s, x, xh, eq)) { - case null { false }; - case (?_) { true }; - } -}; - -func setUnion(s1:Set, s2:Set):Set { - let s3 = merge(s1, s2); - s3 -}; - -func setDiff(s1:Set, s2:Set, eq:(T,T)->Bool):Set { - let s3 = diff(s1, s2, eq); - s3 -}; - -func setIntersect(s1:Set, s2:Set, eq:(T,T)->Bool):Set { - let noop : ((),())->(()) = func (_:(),_:()):(())=(); - let s3 = conj(s1, s2, eq, noop); - s3 -}; - -//////////////////////////////////////////////////////////////////// - -func setPrint(s:Set) { - func rec(s:Set, ind:Nat, bits:Hash) { - func indPrint(i:Nat) { - 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 s { - case null { - //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"; - }; - } - }; - } - }; - rec(s, 0, null); -}; - -//////////////////////////////////////////////////////////////////////////////// - -func natEq(n:Nat,m:Nat):Bool{ n == m}; -func unitEq (_:(),_:()):Bool{ true }; - -func setInsertDb(s:Set, x:Nat, xh:Hash):Set = { - print " setInsert("; - printInt x; - print ")"; - let r = setInsert(s,x,xh); - print ";\n"; - setPrint(r); - r -}; - -func setMemDb(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { - print " setMem("; - print sname; - print ", "; - printInt x; - print ")"; - let b = setMem(s,x,xh,natEq); - if b { print " = true" } else { print " = false" }; - print ";\n"; - b -}; - -func setUnionDb(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { - print " setUnion("; - print s1name; - print ", "; - print s2name; - print ")"; - // also: test that merge agrees with disj: - let r1 = setUnion(s1, s2); - let r2 = disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); - assert(equalStructure(r1, r2, natEq, unitEq)); - print ";\n"; - setPrint(r1); - print "=========\n"; - setPrint(r2); - r1 -}; - -func setIntersectDb(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { - print " setIntersect("; - print s1name; - print ", "; - print s2name; - print ")"; - let r = setIntersect(s1, s2, natEq); - print ";\n"; - setPrint(r); - r -}; - -///////////////////////////////////////////////////////////////////////////////// - -let hash_0 = ?(false,?(false,?(false,?(false, null)))); -let hash_1 = ?(false,?(false,?(false,?(true, null)))); -let hash_2 = ?(false,?(false,?(true, ?(false, null)))); -let hash_3 = ?(false,?(false,?(true, ?(true, null)))); -let hash_4 = ?(false,?(true, ?(false,?(false, null)))); -let hash_5 = ?(false,?(true, ?(false,?(true, null)))); -let hash_6 = ?(false,?(true, ?(true, ?(false, null)))); -let hash_7 = ?(false,?(true, ?(true, ?(true, null)))); -let hash_8 = ?(true, ?(false,?(false,?(false, null)))); - -print "inserting...\n"; -// Insert numbers [0..8] into the set, using their bits as their hashes: -let s0 : Set = setEmpty(); -assert(setCard(s0) == 0); - -let s1 : Set = setInsertDb(s0, 0, hash_0); -assert(setCard(s1) == 1); - -let s2 : Set = setInsertDb(s1, 1, hash_1); -assert(setCard(s2) == 2); - -let s3 : Set = setInsertDb(s2, 2, hash_2); -assert(setCard(s3) == 3); - -let s4 : Set = setInsertDb(s3, 3, hash_3); -assert(setCard(s4) == 4); - -let s5 : Set = setInsertDb(s4, 4, hash_4); -assert(setCard(s5) == 5); - -let s6 : Set = setInsertDb(s5, 5, hash_5); -assert(setCard(s6) == 6); - -let s7 : Set = setInsertDb(s6, 6, hash_6); -assert(setCard(s7) == 7); - -let s8 : Set = setInsertDb(s7, 7, hash_7); -assert(setCard(s8) == 8); - -let s9 : Set = setInsertDb(s8, 8, hash_8); -assert(setCard(s9) == 9); -print "done.\n"; - -print "unioning...\n"; -let s1s2 : Set = setUnionDb(s1, "s1", s2, "s2"); -let s2s1 : Set = setUnionDb(s2, "s2", s1, "s1"); -let s3s2 : Set = setUnionDb(s3, "s3", s2, "s2"); -let s4s2 : Set = setUnionDb(s4, "s4", s2, "s2"); -let s1s5 : Set = setUnionDb(s1, "s1", s5, "s5"); -let s0s2 : Set = setUnionDb(s0, "s0", s2, "s2"); -print "done.\n"; - -print "intersecting...\n"; -let s3is6 : Set = setIntersectDb(s3, "s3", s6, "s6"); -let s2is1 : Set = setIntersectDb(s2, "s2", s1, "s1"); -print "done.\n"; - - -print "testing membership...\n"; - -// Element 0: Test memberships of each set defined above for element 0 -assert( not( setMemDb(s0, "s0", 0, hash_0 ) )); -assert( setMemDb(s1, "s1", 0, hash_0 ) ); -assert( setMemDb(s2, "s2", 0, hash_0 ) ); -assert( setMemDb(s3, "s3", 0, hash_0 ) ); -assert( setMemDb(s4, "s4", 0, hash_0 ) ); -assert( setMemDb(s5, "s5", 0, hash_0 ) ); -assert( setMemDb(s6, "s6", 0, hash_0 ) ); -assert( setMemDb(s7, "s7", 0, hash_0 ) ); -assert( setMemDb(s8, "s8", 0, hash_0 ) ); -assert( setMemDb(s9, "s9", 0, hash_0 ) ); - -// Element 1: Test memberships of each set defined above for element 1 -assert( not(setMemDb(s0, "s0", 1, hash_1 )) ); -assert( not(setMemDb(s1, "s1", 1, hash_1 )) ); -assert( setMemDb(s2, "s2", 1, hash_1 ) ); -assert( setMemDb(s3, "s3", 1, hash_1 ) ); -assert( setMemDb(s4, "s4", 1, hash_1 ) ); -assert( setMemDb(s5, "s5", 1, hash_1 ) ); -assert( setMemDb(s6, "s6", 1, hash_1 ) ); -assert( setMemDb(s7, "s7", 1, hash_1 ) ); -assert( setMemDb(s8, "s8", 1, hash_1 ) ); -assert( setMemDb(s9, "s9", 1, hash_1 ) ); - -// Element 2: Test memberships of each set defined above for element 2 -assert( not(setMemDb(s0, "s0", 2, hash_2 )) ); -assert( not(setMemDb(s1, "s1", 2, hash_2 )) ); -assert( not(setMemDb(s2, "s2", 2, hash_2 )) ); -assert( setMemDb(s3, "s3", 2, hash_2 ) ); -assert( setMemDb(s4, "s4", 2, hash_2 ) ); -assert( setMemDb(s5, "s5", 2, hash_2 ) ); -assert( setMemDb(s6, "s6", 2, hash_2 ) ); -assert( setMemDb(s7, "s7", 2, hash_2 ) ); -assert( setMemDb(s8, "s8", 2, hash_2 ) ); -assert( setMemDb(s9, "s9", 2, hash_2 ) ); - -// Element 3: Test memberships of each set defined above for element 3 -assert( not(setMemDb(s0, "s0", 3, hash_3 )) ); -assert( not(setMemDb(s1, "s1", 3, hash_3 )) ); -assert( not(setMemDb(s2, "s2", 3, hash_3 )) ); -assert( not(setMemDb(s3, "s3", 3, hash_3 )) ); -assert( setMemDb(s4, "s4", 3, hash_3 ) ); -assert( setMemDb(s5, "s5", 3, hash_3 ) ); -assert( setMemDb(s6, "s6", 3, hash_3 ) ); -assert( setMemDb(s7, "s7", 3, hash_3 ) ); -assert( setMemDb(s8, "s8", 3, hash_3 ) ); -assert( setMemDb(s9, "s9", 3, hash_3 ) ); - -// Element 4: Test memberships of each set defined above for element 4 -assert( not(setMemDb(s0, "s0", 4, hash_4 )) ); -assert( not(setMemDb(s1, "s1", 4, hash_4 )) ); -assert( not(setMemDb(s2, "s2", 4, hash_4 )) ); -assert( not(setMemDb(s3, "s3", 4, hash_4 )) ); -assert( not(setMemDb(s4, "s4", 4, hash_4 )) ); -assert( setMemDb(s5, "s5", 4, hash_4 ) ); -assert( setMemDb(s6, "s6", 4, hash_4 ) ); -assert( setMemDb(s7, "s7", 4, hash_4 ) ); -assert( setMemDb(s8, "s8", 4, hash_4 ) ); -assert( setMemDb(s9, "s9", 4, hash_4 ) ); - -// Element 5: Test memberships of each set defined above for element 5 -assert( not(setMemDb(s0, "s0", 5, hash_5 )) ); -assert( not(setMemDb(s1, "s1", 5, hash_5 )) ); -assert( not(setMemDb(s2, "s2", 5, hash_5 )) ); -assert( not(setMemDb(s3, "s3", 5, hash_5 )) ); -assert( not(setMemDb(s4, "s4", 5, hash_5 )) ); -assert( not(setMemDb(s5, "s5", 5, hash_5 )) ); -assert( setMemDb(s6, "s6", 5, hash_5 ) ); -assert( setMemDb(s7, "s7", 5, hash_5 ) ); -assert( setMemDb(s8, "s8", 5, hash_5 ) ); -assert( setMemDb(s9, "s9", 5, hash_5 ) ); - -// Element 6: Test memberships of each set defined above for element 6 -assert( not(setMemDb(s0, "s0", 6, hash_6 )) ); -assert( not(setMemDb(s1, "s1", 6, hash_6 )) ); -assert( not(setMemDb(s2, "s2", 6, hash_6 )) ); -assert( not(setMemDb(s3, "s3", 6, hash_6 )) ); -assert( not(setMemDb(s4, "s4", 6, hash_6 )) ); -assert( not(setMemDb(s5, "s5", 6, hash_6 )) ); -assert( not(setMemDb(s6, "s6", 6, hash_6 )) ); -assert( setMemDb(s7, "s7", 6, hash_6 ) ); -assert( setMemDb(s8, "s8", 6, hash_6 ) ); -assert( setMemDb(s9, "s9", 6, hash_6 ) ); - -// Element 7: Test memberships of each set defined above for element 7 -assert( not(setMemDb(s0, "s0", 7, hash_7 )) ); -assert( not(setMemDb(s1, "s1", 7, hash_7 )) ); -assert( not(setMemDb(s2, "s2", 7, hash_7 )) ); -assert( not(setMemDb(s3, "s3", 7, hash_7 )) ); -assert( not(setMemDb(s4, "s4", 7, hash_7 )) ); -assert( not(setMemDb(s5, "s5", 7, hash_7 )) ); -assert( not(setMemDb(s6, "s6", 7, hash_7 )) ); -assert( not(setMemDb(s7, "s7", 7, hash_7 )) ); -assert( setMemDb(s8, "s8", 7, hash_7 ) ); -assert( setMemDb(s9, "s9", 7, hash_7 ) ); - -// Element 8: Test memberships of each set defined above for element 8 -assert( not(setMemDb(s0, "s0", 8, hash_8 )) ); -assert( not(setMemDb(s1, "s1", 8, hash_8 )) ); -assert( not(setMemDb(s2, "s2", 8, hash_8 )) ); -assert( not(setMemDb(s3, "s3", 8, hash_8 )) ); -assert( not(setMemDb(s4, "s4", 8, hash_8 )) ); -assert( not(setMemDb(s6, "s6", 8, hash_8 )) ); -assert( not(setMemDb(s6, "s6", 8, hash_8 )) ); -assert( not(setMemDb(s7, "s7", 8, hash_8 )) ); -assert( not(setMemDb(s8, "s8", 8, hash_8 )) ); -assert( setMemDb(s9, "s9", 8, hash_8 ) ); - -print "done.\n"; diff --git a/samples/collections/set.as b/samples/collections/set.as new file mode 100644 index 00000000000..7759716b849 --- /dev/null +++ b/samples/collections/set.as @@ -0,0 +1,381 @@ +// import Trie; + +/////////////////////////////////////////////////////////////////////// + +/* + Sets are partial maps from element type to unit type, + i.e., the partial map represents the set with its domain. +*/ + +// TODO-Matthew: +// +// - for now, we pass a hash value each time we pass an element value; +// in the future, we might avoid passing element hashes with each element in the API; +// related to: https://dfinity.atlassian.net/browse/AST-32 +// +// - similarly, we pass an equality function when we do some operations. +// in the future, we might avoid this via https://dfinity.atlassian.net/browse/AST-32 +// + +type Set = Trie; + +func Set__empty():Set = + Trie.empty(); + +func Set__insert(s:Set, x:T, xh:Hash):Set = { + let (s2, _) = Trie.insert(s, x, xh, ()); + s2 +}; + +func Set__remove(s:Set, x:T, xh:Hash):Set = { + let (s2, _) = Trie.remove(s, x, xh); + s2 +}; + +func Set__eq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { + // XXX: Todo: use a smarter check + Trie.equalStructure(s1, s2, eq, Set__unitEq) +}; + +func Set__card(s:Set) : Nat { + Trie.foldUp + (s, + func(n:Nat,m:Nat):Nat{n+m}, + func(_:T,_:()):Nat{1}, + 0) +}; + +func Set__mem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { + switch (Trie.find(s, x, xh, eq)) { + case null { false }; + case (?_) { true }; + } +}; + +func Set__union(s1:Set, s2:Set):Set { + let s3 = Trie.merge(s1, s2); + s3 +}; + +func Set__diff(s1:Set, s2:Set, eq:(T,T)->Bool):Set { + let s3 = Trie.diff(s1, s2, eq); + s3 +}; + +func Set__intersect(s1:Set, s2:Set, eq:(T,T)->Bool):Set { + let noop : ((),())->(()) = func (_:(),_:()):(())=(); + let s3 = Trie.conj(s1, s2, eq, noop); + s3 +}; + +func Set__unitEq (_:(),_:()):Bool{ true }; + +// Create a record, +// as a standin until we have "real" modules to create namespaces: +let Set = new { + // Meta-level stuff: + // --------------------- + moduleName = "Set" + + ; empty = Set__empty + ; insert = Set__insert + ; remove = Set__remove + ; mem = Set__mem + ; card = Set__card + ; eq = Set__eq + ; union = Set__union + ; diff = Set__diff + ; intersect = Set__intersect +}; + + +//////////////////////////////////////////////////////////////////// + +func SetDb__print(s:Set) { + func rec(s:Set, ind:Nat, bits:Hash) { + func indPrint(i:Nat) { + 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 s { + case null { + //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"; + }; + } + }; + } + }; + rec(s, 0, null); +}; + +//////////////////////////////////////////////////////////////////////////////// + +func natEq(n:Nat,m:Nat):Bool{ n == m}; + +func SetDb__insert(s:Set, x:Nat, xh:Hash):Set = { + print " setInsert("; + printInt x; + print ")"; + let r = Set.insert(s,x,xh); + print ";\n"; + SetDb__print(r); + r +}; + +func SetDb__mem(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { + print " setMem("; + print sname; + print ", "; + printInt x; + print ")"; + let b = Set.mem(s,x,xh,natEq); + if b { print " = true" } else { print " = false" }; + print ";\n"; + b +}; + +func SetDb__union(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setUnion("; + print s1name; + print ", "; + print s2name; + print ")"; + // also: test that merge agrees with disj: + let r1 = Set.union(s1, s2); + let r2 = Trie.disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); + assert(Trie.equalStructure(r1, r2, natEq, Set__unitEq)); + print ";\n"; + SetDb__print(r1); + print "=========\n"; + SetDb__print(r2); + r1 +}; + +func SetDb__intersect(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setIntersect("; + print s1name; + print ", "; + print s2name; + print ")"; + let r = Set.intersect(s1, s2, natEq); + print ";\n"; + SetDb__print(r); + r +}; + +///////////////////////////////////////////////////////////////////////////////// + +// Create a record, +// as a standin until we have "real" modules to create namespaces: +let SetDb = new { + // Meta-level stuff: + // --------------------- + moduleName = "SetDb" + ; insert = SetDb__insert + ; mem = SetDb__mem + ; union = SetDb__union + ; intersect = SetDb__intersect +}; + +///////////////////////////////////////////////////////////////////////////////// + +func SetDb__test() { + let hash_0 = ?(false,?(false,?(false,?(false, null)))); + let hash_1 = ?(false,?(false,?(false,?(true, null)))); + let hash_2 = ?(false,?(false,?(true, ?(false, null)))); + let hash_3 = ?(false,?(false,?(true, ?(true, null)))); + let hash_4 = ?(false,?(true, ?(false,?(false, null)))); + let hash_5 = ?(false,?(true, ?(false,?(true, null)))); + let hash_6 = ?(false,?(true, ?(true, ?(false, null)))); + let hash_7 = ?(false,?(true, ?(true, ?(true, null)))); + let hash_8 = ?(true, ?(false,?(false,?(false, null)))); + + print "inserting...\n"; + // Insert numbers [0..8] into the set, using their bits as their hashes: + let s0 : Set = Set.empty(); + assert(Set.card(s0) == 0); + + let s1 : Set = SetDb.insert(s0, 0, hash_0); + assert(Set.card(s1) == 1); + + let s2 : Set = SetDb.insert(s1, 1, hash_1); + assert(Set.card(s2) == 2); + + let s3 : Set = SetDb.insert(s2, 2, hash_2); + assert(Set.card(s3) == 3); + + let s4 : Set = SetDb.insert(s3, 3, hash_3); + assert(Set.card(s4) == 4); + + let s5 : Set = SetDb.insert(s4, 4, hash_4); + assert(Set.card(s5) == 5); + + let s6 : Set = SetDb.insert(s5, 5, hash_5); + assert(Set.card(s6) == 6); + + let s7 : Set = SetDb.insert(s6, 6, hash_6); + assert(Set.card(s7) == 7); + + let s8 : Set = SetDb.insert(s7, 7, hash_7); + assert(Set.card(s8) == 8); + + let s9 : Set = SetDb.insert(s8, 8, hash_8); + assert(Set.card(s9) == 9); + print "done.\n"; + + print "unioning...\n"; + let s1s2 : Set = SetDb.union(s1, "s1", s2, "s2"); + let s2s1 : Set = SetDb.union(s2, "s2", s1, "s1"); + let s3s2 : Set = SetDb.union(s3, "s3", s2, "s2"); + let s4s2 : Set = SetDb.union(s4, "s4", s2, "s2"); + let s1s5 : Set = SetDb.union(s1, "s1", s5, "s5"); + let s0s2 : Set = SetDb.union(s0, "s0", s2, "s2"); + print "done.\n"; + + print "intersecting...\n"; + let s3is6 : Set = SetDb.intersect(s3, "s3", s6, "s6"); + let s2is1 : Set = SetDb.intersect(s2, "s2", s1, "s1"); + print "done.\n"; + + + print "testing membership...\n"; + + // Element 0: Test memberships of each set defined above for element 0 + assert( not( SetDb.mem(s0, "s0", 0, hash_0 ) )); + assert( SetDb.mem(s1, "s1", 0, hash_0 ) ); + assert( SetDb.mem(s2, "s2", 0, hash_0 ) ); + assert( SetDb.mem(s3, "s3", 0, hash_0 ) ); + assert( SetDb.mem(s4, "s4", 0, hash_0 ) ); + assert( SetDb.mem(s5, "s5", 0, hash_0 ) ); + assert( SetDb.mem(s6, "s6", 0, hash_0 ) ); + assert( SetDb.mem(s7, "s7", 0, hash_0 ) ); + assert( SetDb.mem(s8, "s8", 0, hash_0 ) ); + assert( SetDb.mem(s9, "s9", 0, hash_0 ) ); + + // Element 1: Test memberships of each set defined above for element 1 + assert( not(SetDb.mem(s0, "s0", 1, hash_1 )) ); + assert( not(SetDb.mem(s1, "s1", 1, hash_1 )) ); + assert( SetDb.mem(s2, "s2", 1, hash_1 ) ); + assert( SetDb.mem(s3, "s3", 1, hash_1 ) ); + assert( SetDb.mem(s4, "s4", 1, hash_1 ) ); + assert( SetDb.mem(s5, "s5", 1, hash_1 ) ); + assert( SetDb.mem(s6, "s6", 1, hash_1 ) ); + assert( SetDb.mem(s7, "s7", 1, hash_1 ) ); + assert( SetDb.mem(s8, "s8", 1, hash_1 ) ); + assert( SetDb.mem(s9, "s9", 1, hash_1 ) ); + + // Element 2: Test memberships of each set defined above for element 2 + assert( not(SetDb.mem(s0, "s0", 2, hash_2 )) ); + assert( not(SetDb.mem(s1, "s1", 2, hash_2 )) ); + assert( not(SetDb.mem(s2, "s2", 2, hash_2 )) ); + assert( SetDb.mem(s3, "s3", 2, hash_2 ) ); + assert( SetDb.mem(s4, "s4", 2, hash_2 ) ); + assert( SetDb.mem(s5, "s5", 2, hash_2 ) ); + assert( SetDb.mem(s6, "s6", 2, hash_2 ) ); + assert( SetDb.mem(s7, "s7", 2, hash_2 ) ); + assert( SetDb.mem(s8, "s8", 2, hash_2 ) ); + assert( SetDb.mem(s9, "s9", 2, hash_2 ) ); + + // Element 3: Test memberships of each set defined above for element 3 + assert( not(SetDb.mem(s0, "s0", 3, hash_3 )) ); + assert( not(SetDb.mem(s1, "s1", 3, hash_3 )) ); + assert( not(SetDb.mem(s2, "s2", 3, hash_3 )) ); + assert( not(SetDb.mem(s3, "s3", 3, hash_3 )) ); + assert( SetDb.mem(s4, "s4", 3, hash_3 ) ); + assert( SetDb.mem(s5, "s5", 3, hash_3 ) ); + assert( SetDb.mem(s6, "s6", 3, hash_3 ) ); + assert( SetDb.mem(s7, "s7", 3, hash_3 ) ); + assert( SetDb.mem(s8, "s8", 3, hash_3 ) ); + assert( SetDb.mem(s9, "s9", 3, hash_3 ) ); + + // Element 4: Test memberships of each set defined above for element 4 + assert( not(SetDb.mem(s0, "s0", 4, hash_4 )) ); + assert( not(SetDb.mem(s1, "s1", 4, hash_4 )) ); + assert( not(SetDb.mem(s2, "s2", 4, hash_4 )) ); + assert( not(SetDb.mem(s3, "s3", 4, hash_4 )) ); + assert( not(SetDb.mem(s4, "s4", 4, hash_4 )) ); + assert( SetDb.mem(s5, "s5", 4, hash_4 ) ); + assert( SetDb.mem(s6, "s6", 4, hash_4 ) ); + assert( SetDb.mem(s7, "s7", 4, hash_4 ) ); + assert( SetDb.mem(s8, "s8", 4, hash_4 ) ); + assert( SetDb.mem(s9, "s9", 4, hash_4 ) ); + + // Element 5: Test memberships of each set defined above for element 5 + assert( not(SetDb.mem(s0, "s0", 5, hash_5 )) ); + assert( not(SetDb.mem(s1, "s1", 5, hash_5 )) ); + assert( not(SetDb.mem(s2, "s2", 5, hash_5 )) ); + assert( not(SetDb.mem(s3, "s3", 5, hash_5 )) ); + assert( not(SetDb.mem(s4, "s4", 5, hash_5 )) ); + assert( not(SetDb.mem(s5, "s5", 5, hash_5 )) ); + assert( SetDb.mem(s6, "s6", 5, hash_5 ) ); + assert( SetDb.mem(s7, "s7", 5, hash_5 ) ); + assert( SetDb.mem(s8, "s8", 5, hash_5 ) ); + assert( SetDb.mem(s9, "s9", 5, hash_5 ) ); + + // Element 6: Test memberships of each set defined above for element 6 + assert( not(SetDb.mem(s0, "s0", 6, hash_6 )) ); + assert( not(SetDb.mem(s1, "s1", 6, hash_6 )) ); + assert( not(SetDb.mem(s2, "s2", 6, hash_6 )) ); + assert( not(SetDb.mem(s3, "s3", 6, hash_6 )) ); + assert( not(SetDb.mem(s4, "s4", 6, hash_6 )) ); + assert( not(SetDb.mem(s5, "s5", 6, hash_6 )) ); + assert( not(SetDb.mem(s6, "s6", 6, hash_6 )) ); + assert( SetDb.mem(s7, "s7", 6, hash_6 ) ); + assert( SetDb.mem(s8, "s8", 6, hash_6 ) ); + assert( SetDb.mem(s9, "s9", 6, hash_6 ) ); + + // Element 7: Test memberships of each set defined above for element 7 + assert( not(SetDb.mem(s0, "s0", 7, hash_7 )) ); + assert( not(SetDb.mem(s1, "s1", 7, hash_7 )) ); + assert( not(SetDb.mem(s2, "s2", 7, hash_7 )) ); + assert( not(SetDb.mem(s3, "s3", 7, hash_7 )) ); + assert( not(SetDb.mem(s4, "s4", 7, hash_7 )) ); + assert( not(SetDb.mem(s5, "s5", 7, hash_7 )) ); + assert( not(SetDb.mem(s6, "s6", 7, hash_7 )) ); + assert( not(SetDb.mem(s7, "s7", 7, hash_7 )) ); + assert( SetDb.mem(s8, "s8", 7, hash_7 ) ); + assert( SetDb.mem(s9, "s9", 7, hash_7 ) ); + + // Element 8: Test memberships of each set defined above for element 8 + assert( not(SetDb.mem(s0, "s0", 8, hash_8 )) ); + assert( not(SetDb.mem(s1, "s1", 8, hash_8 )) ); + assert( not(SetDb.mem(s2, "s2", 8, hash_8 )) ); + assert( not(SetDb.mem(s3, "s3", 8, hash_8 )) ); + assert( not(SetDb.mem(s4, "s4", 8, hash_8 )) ); + assert( not(SetDb.mem(s6, "s6", 8, hash_8 )) ); + assert( not(SetDb.mem(s6, "s6", 8, hash_8 )) ); + assert( not(SetDb.mem(s7, "s7", 8, hash_8 )) ); + assert( not(SetDb.mem(s8, "s8", 8, hash_8 )) ); + assert( SetDb.mem(s9, "s9", 8, hash_8 ) ); + + print "done.\n"; +}; From 3ce9f33e24d81450ebf6dc287770b1446cd7616f Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 15:51:28 -0700 Subject: [PATCH 36/52] further decomposition of modules --- samples/collections/Makefile | 25 ++- samples/collections/hashtrie.as | 3 + samples/collections/list.as | 67 ------- samples/collections/listTest.as | 65 +++++++ samples/collections/set.as | 292 ------------------------------- samples/collections/setDb.as | 118 +++++++++++++ samples/collections/setDbTest.as | 175 ++++++++++++++++++ 7 files changed, 384 insertions(+), 361 deletions(-) create mode 100644 samples/collections/listTest.as create mode 100644 samples/collections/setDb.as create mode 100644 samples/collections/setDbTest.as diff --git a/samples/collections/Makefile b/samples/collections/Makefile index b9d945eadae..f44b6f2d21c 100644 --- a/samples/collections/Makefile +++ b/samples/collections/Makefile @@ -3,14 +3,17 @@ MODULE_NAME="\x1b[1;32mModule:\x1b[1;34m" BEGIN="\x1b[0;1mBegin...\x1b[0m" DONE="\x1b[1mDone.\n---------------------------------------------------\x1b[0m" -.PHONY: default all Trie List Set +.PHONY: default all Trie List ListTest Set SetDb SetDbTest default: all all: \ List \ + ListTest \ Trie \ - Set + Set \ + SetDb \ + SetDbTest \ List: @echo $(MODULE_NAME) $@ @@ -18,6 +21,12 @@ List: $(ASC) -r list.as @echo $(DONE) +ListTest: + @echo $(MODULE_NAME) $@ + @echo $(BEGIN) + $(ASC) -r list.as listTest.as + @echo $(DONE) + Trie: @echo $(MODULE_NAME) $@ @echo $(BEGIN) @@ -29,3 +38,15 @@ Set: @echo $(BEGIN) $(ASC) -r list.as hashtrie.as set.as @echo $(DONE) + +SetDb: + @echo $(MODULE_NAME) $@ + @echo $(BEGIN) + $(ASC) -r list.as hashtrie.as set.as setDb.as + @echo $(DONE) + +SetDbTest: + @echo $(MODULE_NAME) $@ + @echo $(BEGIN) + $(ASC) -r list.as hashtrie.as set.as setDb.as setDbTest.as > $@.out + @echo $(DONE) diff --git a/samples/collections/hashtrie.as b/samples/collections/hashtrie.as index 9e5f2e3dd55..c69fd127701 100644 --- a/samples/collections/hashtrie.as +++ b/samples/collections/hashtrie.as @@ -42,6 +42,9 @@ // // - iterator objects, for use in 'for ... in ...' patterns + +// import List + // TEMP: A "bit string" as a linked list of bits: type Bits = ?(Bool, Bits); diff --git a/samples/collections/list.as b/samples/collections/list.as index 5844183aefe..5a37a53192f 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -371,70 +371,3 @@ let List = new { ; tabulate = List__tabulate }; -////////////////////////////////////////////////////////////////// - -// # Example usage - -type X = Nat; - -func List__tests() { - - func opnatEq(a : ?Nat, b : ?Nat) : Bool { - switch (a, b) { - case (null, null) { true }; - case (?aaa, ?bbb) { aaa == bbb }; - case (_, _ ) { false }; - } - }; - func opnat_isnull(a : ?Nat) : Bool { - switch a { - case (null) { true }; - case (?aaa) { false }; - } - }; - - // ## Construction - let l1 = List__nil(); - let l2 = List__push(2, l1); - let l3 = List__push(3, l2); - - // ## Projection -- use nth - assert (opnatEq(List__nth(l3, 0), ?3)); - assert (opnatEq(List__nth(l3, 1), ?2)); - assert (opnatEq(List__nth(l3, 2), null)); - //assert (opnatEq (hd(l3), ?3)); - //assert (opnatEq (hd(l2), ?2)); - //assert (opnat_isnull(hd(l1))); - - /* - // ## Projection -- use nth - assert (opnatEq(nth(l3, 0), ?3)); - assert (opnatEq(nth(l3, 1), ?2)); - assert (opnatEq(nth(l3, 2), null)); - assert (opnatEq (hd(l3), ?3)); - assert (opnatEq (hd(l2), ?2)); - assert (opnat_isnull(hd(l1))); - */ - - // ## Deconstruction - let (a1, t1) = List.pop(l3); - assert (opnatEq(a1, ?3)); - let (a2, t2) = List.pop(l2); - assert (opnatEq(a2, ?2)); - let (a3, t3) = List.pop(l1); - assert (opnatEq(a3, null)); - assert (List.isNil(t3)); - - // ## List functions - assert (List.len(l1) == 0); - assert (List.len(l2) == 1); - assert (List.len(l3) == 2); - - // ## List functions - assert (List.len(l1) == 0); - assert (List.len(l2) == 1); - assert (List.len(l3) == 2); -}; - -// Run the tests -List__tests(); diff --git a/samples/collections/listTest.as b/samples/collections/listTest.as new file mode 100644 index 00000000000..0d218b5b33f --- /dev/null +++ b/samples/collections/listTest.as @@ -0,0 +1,65 @@ +// import List + +type X = Nat; + +func List__tests() { + + func opnatEq(a : ?Nat, b : ?Nat) : Bool { + switch (a, b) { + case (null, null) { true }; + case (?aaa, ?bbb) { aaa == bbb }; + case (_, _ ) { false }; + } + }; + func opnat_isnull(a : ?Nat) : Bool { + switch a { + case (null) { true }; + case (?aaa) { false }; + } + }; + + // ## Construction + let l1 = List__nil(); + let l2 = List__push(2, l1); + let l3 = List__push(3, l2); + + // ## Projection -- use nth + assert (opnatEq(List__nth(l3, 0), ?3)); + assert (opnatEq(List__nth(l3, 1), ?2)); + assert (opnatEq(List__nth(l3, 2), null)); + //assert (opnatEq (hd(l3), ?3)); + //assert (opnatEq (hd(l2), ?2)); + //assert (opnat_isnull(hd(l1))); + + /* + // ## Projection -- use nth + assert (opnatEq(nth(l3, 0), ?3)); + assert (opnatEq(nth(l3, 1), ?2)); + assert (opnatEq(nth(l3, 2), null)); + assert (opnatEq (hd(l3), ?3)); + assert (opnatEq (hd(l2), ?2)); + assert (opnat_isnull(hd(l1))); + */ + + // ## Deconstruction + let (a1, t1) = List.pop(l3); + assert (opnatEq(a1, ?3)); + let (a2, t2) = List.pop(l2); + assert (opnatEq(a2, ?2)); + let (a3, t3) = List.pop(l1); + assert (opnatEq(a3, null)); + assert (List.isNil(t3)); + + // ## List functions + assert (List.len(l1) == 0); + assert (List.len(l2) == 1); + assert (List.len(l3) == 2); + + // ## List functions + assert (List.len(l1) == 0); + assert (List.len(l2) == 1); + assert (List.len(l3) == 2); +}; + +// Run the tests +List__tests(); diff --git a/samples/collections/set.as b/samples/collections/set.as index 7759716b849..6e7d3159657 100644 --- a/samples/collections/set.as +++ b/samples/collections/set.as @@ -87,295 +87,3 @@ let Set = new { ; diff = Set__diff ; intersect = Set__intersect }; - - -//////////////////////////////////////////////////////////////////// - -func SetDb__print(s:Set) { - func rec(s:Set, ind:Nat, bits:Hash) { - func indPrint(i:Nat) { - 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 s { - case null { - //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"; - }; - } - }; - } - }; - rec(s, 0, null); -}; - -//////////////////////////////////////////////////////////////////////////////// - -func natEq(n:Nat,m:Nat):Bool{ n == m}; - -func SetDb__insert(s:Set, x:Nat, xh:Hash):Set = { - print " setInsert("; - printInt x; - print ")"; - let r = Set.insert(s,x,xh); - print ";\n"; - SetDb__print(r); - r -}; - -func SetDb__mem(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { - print " setMem("; - print sname; - print ", "; - printInt x; - print ")"; - let b = Set.mem(s,x,xh,natEq); - if b { print " = true" } else { print " = false" }; - print ";\n"; - b -}; - -func SetDb__union(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { - print " setUnion("; - print s1name; - print ", "; - print s2name; - print ")"; - // also: test that merge agrees with disj: - let r1 = Set.union(s1, s2); - let r2 = Trie.disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); - assert(Trie.equalStructure(r1, r2, natEq, Set__unitEq)); - print ";\n"; - SetDb__print(r1); - print "=========\n"; - SetDb__print(r2); - r1 -}; - -func SetDb__intersect(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { - print " setIntersect("; - print s1name; - print ", "; - print s2name; - print ")"; - let r = Set.intersect(s1, s2, natEq); - print ";\n"; - SetDb__print(r); - r -}; - -///////////////////////////////////////////////////////////////////////////////// - -// Create a record, -// as a standin until we have "real" modules to create namespaces: -let SetDb = new { - // Meta-level stuff: - // --------------------- - moduleName = "SetDb" - ; insert = SetDb__insert - ; mem = SetDb__mem - ; union = SetDb__union - ; intersect = SetDb__intersect -}; - -///////////////////////////////////////////////////////////////////////////////// - -func SetDb__test() { - let hash_0 = ?(false,?(false,?(false,?(false, null)))); - let hash_1 = ?(false,?(false,?(false,?(true, null)))); - let hash_2 = ?(false,?(false,?(true, ?(false, null)))); - let hash_3 = ?(false,?(false,?(true, ?(true, null)))); - let hash_4 = ?(false,?(true, ?(false,?(false, null)))); - let hash_5 = ?(false,?(true, ?(false,?(true, null)))); - let hash_6 = ?(false,?(true, ?(true, ?(false, null)))); - let hash_7 = ?(false,?(true, ?(true, ?(true, null)))); - let hash_8 = ?(true, ?(false,?(false,?(false, null)))); - - print "inserting...\n"; - // Insert numbers [0..8] into the set, using their bits as their hashes: - let s0 : Set = Set.empty(); - assert(Set.card(s0) == 0); - - let s1 : Set = SetDb.insert(s0, 0, hash_0); - assert(Set.card(s1) == 1); - - let s2 : Set = SetDb.insert(s1, 1, hash_1); - assert(Set.card(s2) == 2); - - let s3 : Set = SetDb.insert(s2, 2, hash_2); - assert(Set.card(s3) == 3); - - let s4 : Set = SetDb.insert(s3, 3, hash_3); - assert(Set.card(s4) == 4); - - let s5 : Set = SetDb.insert(s4, 4, hash_4); - assert(Set.card(s5) == 5); - - let s6 : Set = SetDb.insert(s5, 5, hash_5); - assert(Set.card(s6) == 6); - - let s7 : Set = SetDb.insert(s6, 6, hash_6); - assert(Set.card(s7) == 7); - - let s8 : Set = SetDb.insert(s7, 7, hash_7); - assert(Set.card(s8) == 8); - - let s9 : Set = SetDb.insert(s8, 8, hash_8); - assert(Set.card(s9) == 9); - print "done.\n"; - - print "unioning...\n"; - let s1s2 : Set = SetDb.union(s1, "s1", s2, "s2"); - let s2s1 : Set = SetDb.union(s2, "s2", s1, "s1"); - let s3s2 : Set = SetDb.union(s3, "s3", s2, "s2"); - let s4s2 : Set = SetDb.union(s4, "s4", s2, "s2"); - let s1s5 : Set = SetDb.union(s1, "s1", s5, "s5"); - let s0s2 : Set = SetDb.union(s0, "s0", s2, "s2"); - print "done.\n"; - - print "intersecting...\n"; - let s3is6 : Set = SetDb.intersect(s3, "s3", s6, "s6"); - let s2is1 : Set = SetDb.intersect(s2, "s2", s1, "s1"); - print "done.\n"; - - - print "testing membership...\n"; - - // Element 0: Test memberships of each set defined above for element 0 - assert( not( SetDb.mem(s0, "s0", 0, hash_0 ) )); - assert( SetDb.mem(s1, "s1", 0, hash_0 ) ); - assert( SetDb.mem(s2, "s2", 0, hash_0 ) ); - assert( SetDb.mem(s3, "s3", 0, hash_0 ) ); - assert( SetDb.mem(s4, "s4", 0, hash_0 ) ); - assert( SetDb.mem(s5, "s5", 0, hash_0 ) ); - assert( SetDb.mem(s6, "s6", 0, hash_0 ) ); - assert( SetDb.mem(s7, "s7", 0, hash_0 ) ); - assert( SetDb.mem(s8, "s8", 0, hash_0 ) ); - assert( SetDb.mem(s9, "s9", 0, hash_0 ) ); - - // Element 1: Test memberships of each set defined above for element 1 - assert( not(SetDb.mem(s0, "s0", 1, hash_1 )) ); - assert( not(SetDb.mem(s1, "s1", 1, hash_1 )) ); - assert( SetDb.mem(s2, "s2", 1, hash_1 ) ); - assert( SetDb.mem(s3, "s3", 1, hash_1 ) ); - assert( SetDb.mem(s4, "s4", 1, hash_1 ) ); - assert( SetDb.mem(s5, "s5", 1, hash_1 ) ); - assert( SetDb.mem(s6, "s6", 1, hash_1 ) ); - assert( SetDb.mem(s7, "s7", 1, hash_1 ) ); - assert( SetDb.mem(s8, "s8", 1, hash_1 ) ); - assert( SetDb.mem(s9, "s9", 1, hash_1 ) ); - - // Element 2: Test memberships of each set defined above for element 2 - assert( not(SetDb.mem(s0, "s0", 2, hash_2 )) ); - assert( not(SetDb.mem(s1, "s1", 2, hash_2 )) ); - assert( not(SetDb.mem(s2, "s2", 2, hash_2 )) ); - assert( SetDb.mem(s3, "s3", 2, hash_2 ) ); - assert( SetDb.mem(s4, "s4", 2, hash_2 ) ); - assert( SetDb.mem(s5, "s5", 2, hash_2 ) ); - assert( SetDb.mem(s6, "s6", 2, hash_2 ) ); - assert( SetDb.mem(s7, "s7", 2, hash_2 ) ); - assert( SetDb.mem(s8, "s8", 2, hash_2 ) ); - assert( SetDb.mem(s9, "s9", 2, hash_2 ) ); - - // Element 3: Test memberships of each set defined above for element 3 - assert( not(SetDb.mem(s0, "s0", 3, hash_3 )) ); - assert( not(SetDb.mem(s1, "s1", 3, hash_3 )) ); - assert( not(SetDb.mem(s2, "s2", 3, hash_3 )) ); - assert( not(SetDb.mem(s3, "s3", 3, hash_3 )) ); - assert( SetDb.mem(s4, "s4", 3, hash_3 ) ); - assert( SetDb.mem(s5, "s5", 3, hash_3 ) ); - assert( SetDb.mem(s6, "s6", 3, hash_3 ) ); - assert( SetDb.mem(s7, "s7", 3, hash_3 ) ); - assert( SetDb.mem(s8, "s8", 3, hash_3 ) ); - assert( SetDb.mem(s9, "s9", 3, hash_3 ) ); - - // Element 4: Test memberships of each set defined above for element 4 - assert( not(SetDb.mem(s0, "s0", 4, hash_4 )) ); - assert( not(SetDb.mem(s1, "s1", 4, hash_4 )) ); - assert( not(SetDb.mem(s2, "s2", 4, hash_4 )) ); - assert( not(SetDb.mem(s3, "s3", 4, hash_4 )) ); - assert( not(SetDb.mem(s4, "s4", 4, hash_4 )) ); - assert( SetDb.mem(s5, "s5", 4, hash_4 ) ); - assert( SetDb.mem(s6, "s6", 4, hash_4 ) ); - assert( SetDb.mem(s7, "s7", 4, hash_4 ) ); - assert( SetDb.mem(s8, "s8", 4, hash_4 ) ); - assert( SetDb.mem(s9, "s9", 4, hash_4 ) ); - - // Element 5: Test memberships of each set defined above for element 5 - assert( not(SetDb.mem(s0, "s0", 5, hash_5 )) ); - assert( not(SetDb.mem(s1, "s1", 5, hash_5 )) ); - assert( not(SetDb.mem(s2, "s2", 5, hash_5 )) ); - assert( not(SetDb.mem(s3, "s3", 5, hash_5 )) ); - assert( not(SetDb.mem(s4, "s4", 5, hash_5 )) ); - assert( not(SetDb.mem(s5, "s5", 5, hash_5 )) ); - assert( SetDb.mem(s6, "s6", 5, hash_5 ) ); - assert( SetDb.mem(s7, "s7", 5, hash_5 ) ); - assert( SetDb.mem(s8, "s8", 5, hash_5 ) ); - assert( SetDb.mem(s9, "s9", 5, hash_5 ) ); - - // Element 6: Test memberships of each set defined above for element 6 - assert( not(SetDb.mem(s0, "s0", 6, hash_6 )) ); - assert( not(SetDb.mem(s1, "s1", 6, hash_6 )) ); - assert( not(SetDb.mem(s2, "s2", 6, hash_6 )) ); - assert( not(SetDb.mem(s3, "s3", 6, hash_6 )) ); - assert( not(SetDb.mem(s4, "s4", 6, hash_6 )) ); - assert( not(SetDb.mem(s5, "s5", 6, hash_6 )) ); - assert( not(SetDb.mem(s6, "s6", 6, hash_6 )) ); - assert( SetDb.mem(s7, "s7", 6, hash_6 ) ); - assert( SetDb.mem(s8, "s8", 6, hash_6 ) ); - assert( SetDb.mem(s9, "s9", 6, hash_6 ) ); - - // Element 7: Test memberships of each set defined above for element 7 - assert( not(SetDb.mem(s0, "s0", 7, hash_7 )) ); - assert( not(SetDb.mem(s1, "s1", 7, hash_7 )) ); - assert( not(SetDb.mem(s2, "s2", 7, hash_7 )) ); - assert( not(SetDb.mem(s3, "s3", 7, hash_7 )) ); - assert( not(SetDb.mem(s4, "s4", 7, hash_7 )) ); - assert( not(SetDb.mem(s5, "s5", 7, hash_7 )) ); - assert( not(SetDb.mem(s6, "s6", 7, hash_7 )) ); - assert( not(SetDb.mem(s7, "s7", 7, hash_7 )) ); - assert( SetDb.mem(s8, "s8", 7, hash_7 ) ); - assert( SetDb.mem(s9, "s9", 7, hash_7 ) ); - - // Element 8: Test memberships of each set defined above for element 8 - assert( not(SetDb.mem(s0, "s0", 8, hash_8 )) ); - assert( not(SetDb.mem(s1, "s1", 8, hash_8 )) ); - assert( not(SetDb.mem(s2, "s2", 8, hash_8 )) ); - assert( not(SetDb.mem(s3, "s3", 8, hash_8 )) ); - assert( not(SetDb.mem(s4, "s4", 8, hash_8 )) ); - assert( not(SetDb.mem(s6, "s6", 8, hash_8 )) ); - assert( not(SetDb.mem(s6, "s6", 8, hash_8 )) ); - assert( not(SetDb.mem(s7, "s7", 8, hash_8 )) ); - assert( not(SetDb.mem(s8, "s8", 8, hash_8 )) ); - assert( SetDb.mem(s9, "s9", 8, hash_8 ) ); - - print "done.\n"; -}; diff --git a/samples/collections/setDb.as b/samples/collections/setDb.as new file mode 100644 index 00000000000..4f00eca6db1 --- /dev/null +++ b/samples/collections/setDb.as @@ -0,0 +1,118 @@ +// import Set + +//////////////////////////////////////////////////////////////////// + +func SetDb__print(s:Set) { + func rec(s:Set, ind:Nat, bits:Hash) { + func indPrint(i:Nat) { + 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 s { + case null { + //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"; + }; + } + }; + } + }; + rec(s, 0, null); +}; + +//////////////////////////////////////////////////////////////////////////////// + +func natEq(n:Nat,m:Nat):Bool{ n == m}; + +func SetDb__insert(s:Set, x:Nat, xh:Hash):Set = { + print " setInsert("; + printInt x; + print ")"; + let r = Set.insert(s,x,xh); + print ";\n"; + SetDb__print(r); + r +}; + +func SetDb__mem(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { + print " setMem("; + print sname; + print ", "; + printInt x; + print ")"; + let b = Set.mem(s,x,xh,natEq); + if b { print " = true" } else { print " = false" }; + print ";\n"; + b +}; + +func SetDb__union(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setUnion("; + print s1name; + print ", "; + print s2name; + print ")"; + // also: test that merge agrees with disj: + let r1 = Set.union(s1, s2); + let r2 = Trie.disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); + assert(Trie.equalStructure(r1, r2, natEq, Set__unitEq)); + print ";\n"; + SetDb__print(r1); + print "=========\n"; + SetDb__print(r2); + r1 +}; + +func SetDb__intersect(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setIntersect("; + print s1name; + print ", "; + print s2name; + print ")"; + let r = Set.intersect(s1, s2, natEq); + print ";\n"; + SetDb__print(r); + r +}; + +///////////////////////////////////////////////////////////////////////////////// + +// Create a record, +// as a standin until we have "real" modules to create namespaces: +let SetDb = new { + // Meta-level stuff: + // --------------------- + moduleName = "SetDb" + ; insert = SetDb__insert + ; mem = SetDb__mem + ; union = SetDb__union + ; intersect = SetDb__intersect +}; \ No newline at end of file diff --git a/samples/collections/setDbTest.as b/samples/collections/setDbTest.as new file mode 100644 index 00000000000..193e54e7772 --- /dev/null +++ b/samples/collections/setDbTest.as @@ -0,0 +1,175 @@ +// import SetDb + +func SetDb__test() { + let hash_0 = ?(false,?(false,?(false,?(false, null)))); + let hash_1 = ?(false,?(false,?(false,?(true, null)))); + let hash_2 = ?(false,?(false,?(true, ?(false, null)))); + let hash_3 = ?(false,?(false,?(true, ?(true, null)))); + let hash_4 = ?(false,?(true, ?(false,?(false, null)))); + let hash_5 = ?(false,?(true, ?(false,?(true, null)))); + let hash_6 = ?(false,?(true, ?(true, ?(false, null)))); + let hash_7 = ?(false,?(true, ?(true, ?(true, null)))); + let hash_8 = ?(true, ?(false,?(false,?(false, null)))); + + print "inserting...\n"; + // Insert numbers [0..8] into the set, using their bits as their hashes: + let s0 : Set = Set.empty(); + assert(Set.card(s0) == 0); + + let s1 : Set = SetDb.insert(s0, 0, hash_0); + assert(Set.card(s1) == 1); + + let s2 : Set = SetDb.insert(s1, 1, hash_1); + assert(Set.card(s2) == 2); + + let s3 : Set = SetDb.insert(s2, 2, hash_2); + assert(Set.card(s3) == 3); + + let s4 : Set = SetDb.insert(s3, 3, hash_3); + assert(Set.card(s4) == 4); + + let s5 : Set = SetDb.insert(s4, 4, hash_4); + assert(Set.card(s5) == 5); + + let s6 : Set = SetDb.insert(s5, 5, hash_5); + assert(Set.card(s6) == 6); + + let s7 : Set = SetDb.insert(s6, 6, hash_6); + assert(Set.card(s7) == 7); + + let s8 : Set = SetDb.insert(s7, 7, hash_7); + assert(Set.card(s8) == 8); + + let s9 : Set = SetDb.insert(s8, 8, hash_8); + assert(Set.card(s9) == 9); + print "done.\n"; + + print "unioning...\n"; + let s1s2 : Set = SetDb.union(s1, "s1", s2, "s2"); + let s2s1 : Set = SetDb.union(s2, "s2", s1, "s1"); + let s3s2 : Set = SetDb.union(s3, "s3", s2, "s2"); + let s4s2 : Set = SetDb.union(s4, "s4", s2, "s2"); + let s1s5 : Set = SetDb.union(s1, "s1", s5, "s5"); + let s0s2 : Set = SetDb.union(s0, "s0", s2, "s2"); + print "done.\n"; + + print "intersecting...\n"; + let s3is6 : Set = SetDb.intersect(s3, "s3", s6, "s6"); + let s2is1 : Set = SetDb.intersect(s2, "s2", s1, "s1"); + print "done.\n"; + + + print "testing membership...\n"; + + // Element 0: Test memberships of each set defined above for element 0 + assert( not( SetDb.mem(s0, "s0", 0, hash_0 ) )); + assert( SetDb.mem(s1, "s1", 0, hash_0 ) ); + assert( SetDb.mem(s2, "s2", 0, hash_0 ) ); + assert( SetDb.mem(s3, "s3", 0, hash_0 ) ); + assert( SetDb.mem(s4, "s4", 0, hash_0 ) ); + assert( SetDb.mem(s5, "s5", 0, hash_0 ) ); + assert( SetDb.mem(s6, "s6", 0, hash_0 ) ); + assert( SetDb.mem(s7, "s7", 0, hash_0 ) ); + assert( SetDb.mem(s8, "s8", 0, hash_0 ) ); + assert( SetDb.mem(s9, "s9", 0, hash_0 ) ); + + // Element 1: Test memberships of each set defined above for element 1 + assert( not(SetDb.mem(s0, "s0", 1, hash_1 )) ); + assert( not(SetDb.mem(s1, "s1", 1, hash_1 )) ); + assert( SetDb.mem(s2, "s2", 1, hash_1 ) ); + assert( SetDb.mem(s3, "s3", 1, hash_1 ) ); + assert( SetDb.mem(s4, "s4", 1, hash_1 ) ); + assert( SetDb.mem(s5, "s5", 1, hash_1 ) ); + assert( SetDb.mem(s6, "s6", 1, hash_1 ) ); + assert( SetDb.mem(s7, "s7", 1, hash_1 ) ); + assert( SetDb.mem(s8, "s8", 1, hash_1 ) ); + assert( SetDb.mem(s9, "s9", 1, hash_1 ) ); + + // Element 2: Test memberships of each set defined above for element 2 + assert( not(SetDb.mem(s0, "s0", 2, hash_2 )) ); + assert( not(SetDb.mem(s1, "s1", 2, hash_2 )) ); + assert( not(SetDb.mem(s2, "s2", 2, hash_2 )) ); + assert( SetDb.mem(s3, "s3", 2, hash_2 ) ); + assert( SetDb.mem(s4, "s4", 2, hash_2 ) ); + assert( SetDb.mem(s5, "s5", 2, hash_2 ) ); + assert( SetDb.mem(s6, "s6", 2, hash_2 ) ); + assert( SetDb.mem(s7, "s7", 2, hash_2 ) ); + assert( SetDb.mem(s8, "s8", 2, hash_2 ) ); + assert( SetDb.mem(s9, "s9", 2, hash_2 ) ); + + // Element 3: Test memberships of each set defined above for element 3 + assert( not(SetDb.mem(s0, "s0", 3, hash_3 )) ); + assert( not(SetDb.mem(s1, "s1", 3, hash_3 )) ); + assert( not(SetDb.mem(s2, "s2", 3, hash_3 )) ); + assert( not(SetDb.mem(s3, "s3", 3, hash_3 )) ); + assert( SetDb.mem(s4, "s4", 3, hash_3 ) ); + assert( SetDb.mem(s5, "s5", 3, hash_3 ) ); + assert( SetDb.mem(s6, "s6", 3, hash_3 ) ); + assert( SetDb.mem(s7, "s7", 3, hash_3 ) ); + assert( SetDb.mem(s8, "s8", 3, hash_3 ) ); + assert( SetDb.mem(s9, "s9", 3, hash_3 ) ); + + // Element 4: Test memberships of each set defined above for element 4 + assert( not(SetDb.mem(s0, "s0", 4, hash_4 )) ); + assert( not(SetDb.mem(s1, "s1", 4, hash_4 )) ); + assert( not(SetDb.mem(s2, "s2", 4, hash_4 )) ); + assert( not(SetDb.mem(s3, "s3", 4, hash_4 )) ); + assert( not(SetDb.mem(s4, "s4", 4, hash_4 )) ); + assert( SetDb.mem(s5, "s5", 4, hash_4 ) ); + assert( SetDb.mem(s6, "s6", 4, hash_4 ) ); + assert( SetDb.mem(s7, "s7", 4, hash_4 ) ); + assert( SetDb.mem(s8, "s8", 4, hash_4 ) ); + assert( SetDb.mem(s9, "s9", 4, hash_4 ) ); + + // Element 5: Test memberships of each set defined above for element 5 + assert( not(SetDb.mem(s0, "s0", 5, hash_5 )) ); + assert( not(SetDb.mem(s1, "s1", 5, hash_5 )) ); + assert( not(SetDb.mem(s2, "s2", 5, hash_5 )) ); + assert( not(SetDb.mem(s3, "s3", 5, hash_5 )) ); + assert( not(SetDb.mem(s4, "s4", 5, hash_5 )) ); + assert( not(SetDb.mem(s5, "s5", 5, hash_5 )) ); + assert( SetDb.mem(s6, "s6", 5, hash_5 ) ); + assert( SetDb.mem(s7, "s7", 5, hash_5 ) ); + assert( SetDb.mem(s8, "s8", 5, hash_5 ) ); + assert( SetDb.mem(s9, "s9", 5, hash_5 ) ); + + // Element 6: Test memberships of each set defined above for element 6 + assert( not(SetDb.mem(s0, "s0", 6, hash_6 )) ); + assert( not(SetDb.mem(s1, "s1", 6, hash_6 )) ); + assert( not(SetDb.mem(s2, "s2", 6, hash_6 )) ); + assert( not(SetDb.mem(s3, "s3", 6, hash_6 )) ); + assert( not(SetDb.mem(s4, "s4", 6, hash_6 )) ); + assert( not(SetDb.mem(s5, "s5", 6, hash_6 )) ); + assert( not(SetDb.mem(s6, "s6", 6, hash_6 )) ); + assert( SetDb.mem(s7, "s7", 6, hash_6 ) ); + assert( SetDb.mem(s8, "s8", 6, hash_6 ) ); + assert( SetDb.mem(s9, "s9", 6, hash_6 ) ); + + // Element 7: Test memberships of each set defined above for element 7 + assert( not(SetDb.mem(s0, "s0", 7, hash_7 )) ); + assert( not(SetDb.mem(s1, "s1", 7, hash_7 )) ); + assert( not(SetDb.mem(s2, "s2", 7, hash_7 )) ); + assert( not(SetDb.mem(s3, "s3", 7, hash_7 )) ); + assert( not(SetDb.mem(s4, "s4", 7, hash_7 )) ); + assert( not(SetDb.mem(s5, "s5", 7, hash_7 )) ); + assert( not(SetDb.mem(s6, "s6", 7, hash_7 )) ); + assert( not(SetDb.mem(s7, "s7", 7, hash_7 )) ); + assert( SetDb.mem(s8, "s8", 7, hash_7 ) ); + assert( SetDb.mem(s9, "s9", 7, hash_7 ) ); + + // Element 8: Test memberships of each set defined above for element 8 + assert( not(SetDb.mem(s0, "s0", 8, hash_8 )) ); + assert( not(SetDb.mem(s1, "s1", 8, hash_8 )) ); + assert( not(SetDb.mem(s2, "s2", 8, hash_8 )) ); + assert( not(SetDb.mem(s3, "s3", 8, hash_8 )) ); + assert( not(SetDb.mem(s4, "s4", 8, hash_8 )) ); + assert( not(SetDb.mem(s6, "s6", 8, hash_8 )) ); + assert( not(SetDb.mem(s6, "s6", 8, hash_8 )) ); + assert( not(SetDb.mem(s7, "s7", 8, hash_8 )) ); + assert( not(SetDb.mem(s8, "s8", 8, hash_8 )) ); + assert( SetDb.mem(s9, "s9", 8, hash_8 ) ); + + print "done.\n"; +}; + +SetDb__test(); \ No newline at end of file From 6bdb6d5f363637ba09807e1c305ce393100710d3 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 15:54:08 -0700 Subject: [PATCH 37/52] file rename --- samples/collections/Makefile | 8 ++++---- samples/collections/{hashtrie.as => trie.as} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename samples/collections/{hashtrie.as => trie.as} (100%) diff --git a/samples/collections/Makefile b/samples/collections/Makefile index f44b6f2d21c..0776a9f7c35 100644 --- a/samples/collections/Makefile +++ b/samples/collections/Makefile @@ -30,23 +30,23 @@ ListTest: Trie: @echo $(MODULE_NAME) $@ @echo $(BEGIN) - $(ASC) -r list.as hashtrie.as + $(ASC) -r list.as trie.as @echo $(DONE) Set: @echo $(MODULE_NAME) $@ @echo $(BEGIN) - $(ASC) -r list.as hashtrie.as set.as + $(ASC) -r list.as trie.as set.as @echo $(DONE) SetDb: @echo $(MODULE_NAME) $@ @echo $(BEGIN) - $(ASC) -r list.as hashtrie.as set.as setDb.as + $(ASC) -r list.as trie.as set.as setDb.as @echo $(DONE) SetDbTest: @echo $(MODULE_NAME) $@ @echo $(BEGIN) - $(ASC) -r list.as hashtrie.as set.as setDb.as setDbTest.as > $@.out + $(ASC) -r list.as trie.as set.as setDb.as setDbTest.as > $@.out @echo $(DONE) diff --git a/samples/collections/hashtrie.as b/samples/collections/trie.as similarity index 100% rename from samples/collections/hashtrie.as rename to samples/collections/trie.as From dabdab442eb76b05f849633969e1c9025e3f3f9d Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 15:57:11 -0700 Subject: [PATCH 38/52] clean up --- samples/collections/{ => nonCritcalPath}/stream.as | 0 samples/collections/{ => nonCritcalPath}/thunk.as | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename samples/collections/{ => nonCritcalPath}/stream.as (100%) rename samples/collections/{ => nonCritcalPath}/thunk.as (100%) diff --git a/samples/collections/stream.as b/samples/collections/nonCritcalPath/stream.as similarity index 100% rename from samples/collections/stream.as rename to samples/collections/nonCritcalPath/stream.as diff --git a/samples/collections/thunk.as b/samples/collections/nonCritcalPath/thunk.as similarity index 100% rename from samples/collections/thunk.as rename to samples/collections/nonCritcalPath/thunk.as From 7c1fde419969568a9b63376d3410ae227da570b8 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 16:10:21 -0700 Subject: [PATCH 39/52] fix typo --- samples/collections/{nonCritcalPath => nonCriticalPath}/stream.as | 0 samples/collections/{nonCritcalPath => nonCriticalPath}/thunk.as | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename samples/collections/{nonCritcalPath => nonCriticalPath}/stream.as (100%) rename samples/collections/{nonCritcalPath => nonCriticalPath}/thunk.as (100%) diff --git a/samples/collections/nonCritcalPath/stream.as b/samples/collections/nonCriticalPath/stream.as similarity index 100% rename from samples/collections/nonCritcalPath/stream.as rename to samples/collections/nonCriticalPath/stream.as diff --git a/samples/collections/nonCritcalPath/thunk.as b/samples/collections/nonCriticalPath/thunk.as similarity index 100% rename from samples/collections/nonCritcalPath/thunk.as rename to samples/collections/nonCriticalPath/thunk.as From 646d903ec0857baf8ee51bba33cfaa6902567ad4 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 16:22:43 -0700 Subject: [PATCH 40/52] minor: remove confusing comments --- samples/collections/list.as | 5 ----- samples/collections/set.as | 3 --- samples/collections/setDb.as | 2 -- samples/collections/trie.as | 3 --- 4 files changed, 13 deletions(-) diff --git a/samples/collections/list.as b/samples/collections/list.as index 5a37a53192f..dcaeb083211 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -338,12 +338,7 @@ func List__tabulate(n:Nat, f:Nat -> T) : List { // Create a record, // as a standin until we have "real" modules to create namespaces: let List = new { - // Meta-level stuff: - // --------------------- moduleName = "List" - - // Actual module contents - // ----------------------- ; nil = List__nil ; isNil = List__isNil ; push = List__push diff --git a/samples/collections/set.as b/samples/collections/set.as index 6e7d3159657..995f63988ea 100644 --- a/samples/collections/set.as +++ b/samples/collections/set.as @@ -73,10 +73,7 @@ func Set__unitEq (_:(),_:()):Bool{ true }; // Create a record, // as a standin until we have "real" modules to create namespaces: let Set = new { - // Meta-level stuff: - // --------------------- moduleName = "Set" - ; empty = Set__empty ; insert = Set__insert ; remove = Set__remove diff --git a/samples/collections/setDb.as b/samples/collections/setDb.as index 4f00eca6db1..8363b8dae78 100644 --- a/samples/collections/setDb.as +++ b/samples/collections/setDb.as @@ -108,8 +108,6 @@ func SetDb__intersect(s1:Set, s1name:Text, s2:Set, s2name:Text):Set( // Create a record, // as a standin until we have "real" modules to create namespaces: let Trie = new { - // Meta-level stuff: - // --------------------- moduleName = "Trie" - ; empty = Trie__empty ; insert = Trie__insert ; remove = Trie__remove From fca20a99d1a65aabd40e46d79c693387985cc32c Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 19:11:42 -0700 Subject: [PATCH 41/52] rough draft of Produce Exchange, skeleton --- samples/collections/produceExchange.as | 214 +++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 samples/collections/produceExchange.as diff --git a/samples/collections/produceExchange.as b/samples/collections/produceExchange.as new file mode 100644 index 00000000000..dfc40a2ca7b --- /dev/null +++ b/samples/collections/produceExchange.as @@ -0,0 +1,214 @@ +// Produce Exchange Dapp +// ===================== +// +// Start here: +// - Detailed examples: https://dfinity.atlassian.net/wiki/x/joXUBg +// - More background: https://dfinity.atlassian.net/wiki/x/4gg5Bg +// + +// Open Questions: +// ------------------------------------------------- + +// 1. Massive result messages: +// How do we represent and send these? +// +// - lazy lists? (seems "easy" from AS programmer perspective, but +// requires non-first-order data in the IDL) +// +// - list iterators? (almost as good as lazy lists, but requires +// references in the IDL, and complicates the GC story). +// +// - arrays? (expensive to build and send; can become way *too big*). +// + +// 2. For now, wan we assume that the canister is maintained by the +// central authority? + +//////////////////////////////////////////////////////////////// + +// Use the standard library of AS: +// =============================== +// + +// Collections implement internal tables: +// -------------------------------------- +// import Table (same as Trie?) xxx + +// import Date +// xxx Dates, eventually from a standard library: +type Date = Nat; + +// xxx standard weight units? +type Weight = Nat; + +// xxx standard price units? +type Price = Nat; + +///////////////////////////////////////////////////////////////// + +// Fixed types +// =============================== +// +// We assume some fixed types (for now). +// Updating these types requires a canister upgrade. +// +// ?? defined by the central authority, aka, the "canister maintainer"? +// + +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 TruckCapacity = Weight; + +type Quantity = Nat; + +type PricePerUnit = Price; // needed to calculate prices +type PriceTotal = Price; + +type WeightPerUnit = Weight; // needed to meet truck constraints + +type RegionId = Nat; // xxx variant type? + + +// +// Unique Ids +// ---------- +// Internally, each type of Id serves as a "row key" for a table (or two). +// + +type ProduceId = Nat; +type ProducerId = Nat; +type RetailerId = Nat; +type TruckTypeId = Nat; +type InventoryId = Nat; +type TransporterId = Nat; +type RouteId = Nat; +type OrderId = Nat; + +// +// Query parameters and results +// ---------------------------- +// + +type OrderInfo = shared { + produce: ProduceId; + producer: ProducerId; + quant: Quantity; + ppu: PricePerUnit; + transporter: TransporterId; + truck_type: TruckTypeId; + weight: Weight; + region_begin:RegionId; + region_end: RegionId; + date_begin: Date; + date_end: Date; + prod_cost: PriceTotal; + trans_cost: PriceTotal; +}; + +// xxx same as an OrderInfo? If different, then how? +type QueryAllResult = shared { + produce: ProduceId; + producer: ProducerId; + quant: Quantity; + ppu: PricePerUnit; + transporter: TransporterId; + truck_type: TruckTypeId; + weight: Weight; + region_begin:RegionId; + region_end: RegionId; + date_begin: Date; + date_end: Date; + prod_cost: PriceTotal; + trans_cost: PriceTotal; +}; + +// xxx how to represent huge result messages? +type QueryAllResults = [QueryAllResult]; + +// the "Service" +actor ProduceExchange { + + // Producer-based ingress messages: + // ================================ + + producerAddInventory( + prod: ProduceId, + quant:Quantity, + ppu: PricePerUnit, + begin:Date, + end: Date, + ) : async ?InventoryId { + // xxx + null + }; + + producerRemInventory(id:InventoryId) : async ?() { + // xxx + null + }; + + producerOrders(id:ProducerId) : async ?[OrderId] { + // xxx + null + }; + + // Transporter-based ingress messages: + // =================================== + + transporterAddRoute( + trans: TransporterId, + rstart: RegionId, + rend: RegionId, + start: Date, + end: Date, + cost: Price, + tt: TruckTypeId + ) : async ?RouteId { + // xxx + null + }; + + transporterRemRoute(id:RouteId) : async ?() { + // xxx + null + }; + + transporterOrders(id:TransporterId) : async ?[OrderId] { + // xxx + null + }; + + // Retailer-based ingress messages: + // =================================== + + retailerQueryAll(id:RetailerId) : async ?QueryAllResults { + // xxx + null + }; + + retailerPlaceOrder( + id:RetailerId, + inventory:InventoryId, + route:RouteId) : async ?OrderId + { + // xxx + null + }; + + retailerOrders(id:RetailerId) : async ?[OrderId] { + // xxx + null + }; + + // (Producer/Transporter/Retailer) ingress messages: + // ======================================================== + + orderInfo(id:OrderId) : async ?OrderInfo { + // xxx + null + }; + +}; From 52fd3a30f56313860711aae8ed4a6e7e5fb0f559 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Wed, 6 Mar 2019 19:27:00 -0700 Subject: [PATCH 42/52] Makefile avoids re-running asc --- samples/collections/Makefile | 62 +++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/samples/collections/Makefile b/samples/collections/Makefile index 0776a9f7c35..50dc01b8c23 100644 --- a/samples/collections/Makefile +++ b/samples/collections/Makefile @@ -3,50 +3,60 @@ MODULE_NAME="\x1b[1;32mModule:\x1b[1;34m" BEGIN="\x1b[0;1mBegin...\x1b[0m" DONE="\x1b[1mDone.\n---------------------------------------------------\x1b[0m" -.PHONY: default all Trie List ListTest Set SetDb SetDbTest +.PHONY: default all clean default: all all: \ - List \ - ListTest \ - Trie \ - Set \ - SetDb \ - SetDbTest \ - -List: - @echo $(MODULE_NAME) $@ + List.out \ + ListTest.out \ + Trie.out \ + Set.out \ + SetDb.out \ + SetDbTest.out \ + ProduceExchange.out \ + +clean: + rm -f *.out + +List.out: list.as + @echo $(MODULE_NAME) $(basename $@) @echo $(BEGIN) - $(ASC) -r list.as + $(ASC) -r $^ > $@ @echo $(DONE) -ListTest: - @echo $(MODULE_NAME) $@ +ListTest.out: list.as listTest.as + @echo $(MODULE_NAME) $(basename $@) @echo $(BEGIN) - $(ASC) -r list.as listTest.as + $(ASC) -r $^ > $@ @echo $(DONE) -Trie: - @echo $(MODULE_NAME) $@ +Trie.out: list.as trie.as + @echo $(MODULE_NAME) $(basename $@) @echo $(BEGIN) - $(ASC) -r list.as trie.as + $(ASC) -r $^ > $@ @echo $(DONE) -Set: - @echo $(MODULE_NAME) $@ +Set.out: list.as trie.as set.as + @echo $(MODULE_NAME) $(basename $@) @echo $(BEGIN) - $(ASC) -r list.as trie.as set.as + $(ASC) -r $^ > $@ @echo $(DONE) -SetDb: - @echo $(MODULE_NAME) $@ +SetDb.out: list.as trie.as set.as setDb.as + @echo $(MODULE_NAME) $(basename $@) @echo $(BEGIN) - $(ASC) -r list.as trie.as set.as setDb.as + $(ASC) -r $^ > $@ @echo $(DONE) -SetDbTest: - @echo $(MODULE_NAME) $@ +SetDbTest.out: list.as trie.as set.as setDb.as setDbTest.as + @echo $(MODULE_NAME) $(basename $@) @echo $(BEGIN) - $(ASC) -r list.as trie.as set.as setDb.as setDbTest.as > $@.out + $(ASC) -r $^ > $@ + @echo $(DONE) + +ProduceExchange.out: list.as trie.as produceExchange.as + @echo $(MODULE_NAME) $(basename $@) + @echo $(BEGIN) + $(ASC) -r $^ > $@ @echo $(DONE) From 97f15dd8962b1f246b213bc3e0e21c489d877fc5 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Thu, 7 Mar 2019 12:28:12 +0000 Subject: [PATCH 43/52] simplify fake module construction exploiting new { } syntax --- samples/collections/list.as | 103 +++++--------- samples/collections/listTest.as | 12 +- samples/collections/set.as | 39 ++---- samples/collections/setDb.as | 2 +- samples/collections/trie.as | 238 +++++++++++++++----------------- 5 files changed, 167 insertions(+), 227 deletions(-) diff --git a/samples/collections/list.as b/samples/collections/list.as index dcaeb083211..25fe15d3a01 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -25,12 +25,14 @@ // polymorphic linked lists type List = ?(T, List); +let List = new { + // empty list -func List__nil() : List = +func nil() : List = null; // test for empty list -func List__isNil(l : List) : Bool { +func isNil(l : List) : Bool { switch l { case null { true }; case _ { false }; @@ -38,20 +40,20 @@ func List__isNil(l : List) : Bool { }; // aka "list cons" -func List__push(x : T, l : List) : List = +func push(x : T, l : List) : List = ?(x, l); // last element, optionally; tail recursive -func List__last(l : List) : ?T = { +func last(l : List) : ?T = { switch l { case null { null }; case (?(x,null)) { ?x }; - case (?(_,t)) { List__last(t) }; + case (?(_,t)) { last(t) }; } }; // treat the list as a stack; combines 'hd' and (non-failing) 'tl' into one operation -func List__pop(l : List) : (?T, List) = { +func pop(l : List) : (?T, List) = { switch l { case null { (null, null) }; case (?(h, t)) { (?h, t) }; @@ -59,7 +61,7 @@ func List__pop(l : List) : (?T, List) = { }; // length; tail recursive -func List__len(l : List) : Nat = { +func len(l : List) : Nat = { func rec(l : List, n : Nat) : Nat { switch l { case null { n }; @@ -70,16 +72,16 @@ func List__len(l : List) : Nat = { }; // array-like list access, but in linear time; tail recursive -func List__nth(l : List, n : Nat) : ?T = { +func nth(l : List, n : Nat) : ?T = { switch (n, l) { case (_, null) { null }; case (0, (?(h,t))) { ?h }; - case (_, (?(_,t))) { List__nth(t, n - 1) }; + case (_, (?(_,t))) { nth(t, n - 1) }; } }; // reverse; tail recursive -func List__rev(l : List) : List = { +func rev(l : List) : List = { func rec(l : List, r : List) : List { switch l { case null { r }; @@ -90,7 +92,7 @@ func List__rev(l : List) : List = { }; // Called "app" in SML Basis, and "iter" in OCaml; tail recursive -func List__iter(l : List, f:T -> ()) : () = { +func iter(l : List, f:T -> ()) : () = { func rec(l : List) : () { switch l { case null { () }; @@ -102,7 +104,7 @@ func List__iter(l : List, f:T -> ()) : () = { // map; non-tail recursive // (Note: need mutable Cons tails for tail-recursive map) -func List__map(l : List, f:T -> S) : List = { +func map(l : List, f:T -> S) : List = { func rec(l : List) : List { switch l { case null { null }; @@ -114,7 +116,7 @@ func List__map(l : List, f:T -> S) : List = { // filter; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func List__filter(l : List, f:T -> Bool) : List = { +func filter(l : List, f:T -> Bool) : List = { func rec(l : List) : List { switch l { case null { null }; @@ -126,7 +128,7 @@ func List__filter(l : List, f:T -> Bool) : List = { // map-and-filter; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func List__mapFilter(l : List, f:T -> ?S) : List = { +func mapFilter(l : List, f:T -> ?S) : List = { func rec(l : List) : List { switch l { case null { null }; @@ -143,7 +145,7 @@ func List__mapFilter(l : List, f:T -> ?S) : List = { // append; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func List__append(l : List, m : List) : List = { +func append(l : List, m : List) : List = { func rec(l : List) : List { switch l { case null { m }; @@ -154,45 +156,45 @@ func List__append(l : List, m : List) : List = { }; // concat (aka "list join"); tail recursive, but requires "two passes" -func List__concat(l : List>) : List = { +func concat(l : List>) : List = { // 1/2: fold from left to right, reverse-appending the sublists... let r = - { let f = func(a:List, b:List) : List { List__revAppend(a,b) }; - List__foldLeft, List>(l, null, f) + { let f = func(a:List, b:List) : List { revAppend(a,b) }; + foldLeft, List>(l, null, f) }; // 2/2: ...re-reverse the elements, to their original order: - List__rev(r) + rev(r) }; // (See SML Basis library); tail recursive -func List__revAppend(l1 : List, l2 : List) : List = { +func revAppend(l1 : List, l2 : List) : List = { switch l1 { case null { l2 }; - case (?(h,t)) { List__revAppend(t, ?(h,l2)) }; + case (?(h,t)) { revAppend(t, ?(h,l2)) }; } }; // take; non-tail recursive // (Note: need mutable Cons tails for tail-recursive version) -func List__take(l : List, n:Nat) : List = { +func take(l : List, n:Nat) : List = { switch (l, n) { case (_, 0) { null }; case (null,_) { null }; - case (?(h, t), m) {?(h, List__take(t, m - 1))}; + case (?(h, t), m) {?(h, take(t, m - 1))}; } }; // drop; tail recursive -func List__drop(l : List, n:Nat) : List = { +func drop(l : List, n:Nat) : List = { switch (l, n) { case (l_, 0) { l_ }; case (null, _) { null }; - case ((?(h,t)), m) { List__drop(t, m - 1) }; + case ((?(h,t)), m) { drop(t, m - 1) }; } }; // fold list left-to-right using f; tail recursive -func List__foldLeft(l : List, a:S, f:(T,S) -> S) : S = { +func foldLeft(l : List, a:S, f:(T,S) -> S) : S = { func rec(l:List, a:S) : S = { switch l { case null { a }; @@ -203,7 +205,7 @@ func List__foldLeft(l : List, a:S, f:(T,S) -> S) : S = { }; // fold list right-to-left using f; non-tail recursive -func List__foldRight(l : List, a:S, f:(T,S) -> S) : S = { +func foldRight(l : List, a:S, f:(T,S) -> S) : S = { func rec(l:List) : S = { switch l { case null { a }; @@ -214,7 +216,7 @@ func List__foldRight(l : List, a:S, f:(T,S) -> S) : S = { }; // test if there exists list element for which given predicate is true -func List__find(l: List, f:T -> Bool) : ?T = { +func find(l: List, f:T -> Bool) : ?T = { func rec(l:List) : ?T { switch l { case null { null }; @@ -225,7 +227,7 @@ func List__find(l: List, f:T -> Bool) : ?T = { }; // test if there exists list element for which given predicate is true -func List__exists(l: List, f:T -> Bool) : Bool = { +func exists(l: List, f:T -> Bool) : Bool = { func rec(l:List) : Bool { switch l { case null { false }; @@ -238,7 +240,7 @@ func List__exists(l: List, f:T -> Bool) : Bool = { }; // test if given predicate is true for all list elements -func List__all(l: List, f:T -> Bool) : Bool = { +func all(l: List, f:T -> Bool) : Bool = { func rec(l:List) : Bool { switch l { case null { true }; @@ -249,7 +251,7 @@ func List__all(l: List, f:T -> Bool) : Bool = { }; // Given two ordered lists, merge them into a single ordered list -func List__merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { +func merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { func rec(l1: List, l2: List) : List { switch (l1, l2) { case (null, _) { l2 }; @@ -269,7 +271,7 @@ func List__merge(l1: List, l2: List, lte:(T,T) -> Bool) : List { // Compare two lists lexicographic` ordering. tail recursive. // XXX: Eventually, follow `collate` design from SML Basis, with real sum types, use 3-valued `order` type here. // -func List__lessThanEq(l1: List, l2: List, lte:(T,T) -> Bool) : Bool { +func lessThanEq(l1: List, l2: List, lte:(T,T) -> Bool) : Bool { func rec(l1: List, l2: List) : Bool { switch (l1, l2) { case (null, _) { true }; @@ -288,7 +290,7 @@ func List__lessThanEq(l1: List, l2: List, lte:(T,T) -> Bool) : Bool { // Compare two lists for equality. tail recursive. // `isEq(l1, l2)` =equiv= `lessThanEq(l1,l2) && lessThanEq(l2,l1)`, but the former is more efficient. -func List__isEq(l1: List, l2: List, eq:(T,T) -> Bool) : Bool { +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 }; @@ -308,7 +310,7 @@ func List__isEq(l1: List, l2: List, eq:(T,T) -> Bool) : Bool { // using a predicate, create two lists from one: the "true" list, and the "false" list. // (See SML basis library); non-tail recursive -func List__partition(l: List, f:T -> Bool) : (List, List) { +func partition(l: List, f:T -> Bool) : (List, List) { func rec(l: List) : (List, List) { switch l { case null { (null, null) }; @@ -327,42 +329,11 @@ func List__partition(l: List, f:T -> Bool) : (List, List) { // generate a list based on a length, and a function from list index to list element; // (See SML basis library); non-tail recursive -func List__tabulate(n:Nat, f:Nat -> T) : List { +func tabulate(n:Nat, f:Nat -> T) : List { func rec(i:Nat) : List { if (i == n) { null } else { ?(f(i), rec(i+1)) } }; rec(0) }; - -// Create a record, -// as a standin until we have "real" modules to create namespaces: -let List = new { - moduleName = "List" - ; nil = List__nil - ; isNil = List__isNil - ; push = List__push - ; last = List__last - ; pop = List__pop - ; len = List__len - ; nth = List__nth - ; rev = List__rev - ; iter = List__iter - ; filter = List__filter - ; mapFilter = List__mapFilter - ; append = List__append - ; concat = List__concat - ; revAppend = List__revAppend - ; take = List__take - ; drop = List__drop - ; foldLeft = List__foldLeft - ; foldRight = List__foldRight - ; find = List__find - ; exists = List__exists - ; all = List__all - ; merge = List__merge - ; lessThanEq = List__lessThanEq - ; partition = List__partition - ; tabulate = List__tabulate }; - diff --git a/samples/collections/listTest.as b/samples/collections/listTest.as index 0d218b5b33f..4d84bd6e6ec 100644 --- a/samples/collections/listTest.as +++ b/samples/collections/listTest.as @@ -19,14 +19,14 @@ func List__tests() { }; // ## Construction - let l1 = List__nil(); - let l2 = List__push(2, l1); - let l3 = List__push(3, l2); + let l1 = List.nil(); + let l2 = List.push(2, l1); + let l3 = List.push(3, l2); // ## Projection -- use nth - assert (opnatEq(List__nth(l3, 0), ?3)); - assert (opnatEq(List__nth(l3, 1), ?2)); - assert (opnatEq(List__nth(l3, 2), null)); + assert (opnatEq(List.nth(l3, 0), ?3)); + assert (opnatEq(List.nth(l3, 1), ?2)); + assert (opnatEq(List.nth(l3, 2), null)); //assert (opnatEq (hd(l3), ?3)); //assert (opnatEq (hd(l2), ?2)); //assert (opnat_isnull(hd(l1))); diff --git a/samples/collections/set.as b/samples/collections/set.as index 995f63988ea..fe7ce35bba2 100644 --- a/samples/collections/set.as +++ b/samples/collections/set.as @@ -19,25 +19,27 @@ type Set = Trie; -func Set__empty():Set = +let Set = new { + +func empty():Set = Trie.empty(); -func Set__insert(s:Set, x:T, xh:Hash):Set = { +func insert(s:Set, x:T, xh:Hash):Set = { let (s2, _) = Trie.insert(s, x, xh, ()); s2 }; -func Set__remove(s:Set, x:T, xh:Hash):Set = { +func remove(s:Set, x:T, xh:Hash):Set = { let (s2, _) = Trie.remove(s, x, xh); s2 }; -func Set__eq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { +func eq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { // XXX: Todo: use a smarter check - Trie.equalStructure(s1, s2, eq, Set__unitEq) + Trie.equalStructure(s1, s2, eq, unitEq) }; -func Set__card(s:Set) : Nat { +func card(s:Set) : Nat { Trie.foldUp (s, func(n:Nat,m:Nat):Nat{n+m}, @@ -45,42 +47,29 @@ func Set__card(s:Set) : Nat { 0) }; -func Set__mem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { +func mem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { switch (Trie.find(s, x, xh, eq)) { case null { false }; case (?_) { true }; } }; -func Set__union(s1:Set, s2:Set):Set { +func union(s1:Set, s2:Set):Set { let s3 = Trie.merge(s1, s2); s3 }; -func Set__diff(s1:Set, s2:Set, eq:(T,T)->Bool):Set { +func diff(s1:Set, s2:Set, eq:(T,T)->Bool):Set { let s3 = Trie.diff(s1, s2, eq); s3 }; -func Set__intersect(s1:Set, s2:Set, eq:(T,T)->Bool):Set { +func intersect(s1:Set, s2:Set, eq:(T,T)->Bool):Set { let noop : ((),())->(()) = func (_:(),_:()):(())=(); let s3 = Trie.conj(s1, s2, eq, noop); s3 }; -func Set__unitEq (_:(),_:()):Bool{ true }; +func unitEq (_:(),_:()):Bool{ true }; -// Create a record, -// as a standin until we have "real" modules to create namespaces: -let Set = new { - moduleName = "Set" - ; empty = Set__empty - ; insert = Set__insert - ; remove = Set__remove - ; mem = Set__mem - ; card = Set__card - ; eq = Set__eq - ; union = Set__union - ; diff = Set__diff - ; intersect = Set__intersect -}; +}; \ No newline at end of file diff --git a/samples/collections/setDb.as b/samples/collections/setDb.as index 8363b8dae78..093c0dd9803 100644 --- a/samples/collections/setDb.as +++ b/samples/collections/setDb.as @@ -83,7 +83,7 @@ func SetDb__union(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = // also: test that merge agrees with disj: let r1 = Set.union(s1, s2); let r2 = Trie.disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); - assert(Trie.equalStructure(r1, r2, natEq, Set__unitEq)); + assert(Trie.equalStructure(r1, r2, natEq, Set.unitEq)); print ";\n"; SetDb__print(r1); print "=========\n"; diff --git a/samples/collections/trie.as b/samples/collections/trie.as index 15c4f2a183f..a667a43a725 100644 --- a/samples/collections/trie.as +++ b/samples/collections/trie.as @@ -88,8 +88,9 @@ type LeafNode = { key:K; val:V }; */ +let Trie = new { // XXX: until AST-42: -func Trie__isNull(x : ?X) : Bool { +func isNull(x : ?X) : Bool { switch x { case null { true }; case (?_) { false }; @@ -97,7 +98,7 @@ func Trie__isNull(x : ?X) : Bool { }; // XXX: until AST-42: -func Trie__assertIsNull(x : ?X) { +func assertIsNull(x : ?X) { switch x { case null { assert(true) }; case (?_) { assert(false) }; @@ -105,7 +106,7 @@ func Trie__assertIsNull(x : ?X) { }; // XXX: until AST-42: -func Trie__makeEmpty() : Trie +func makeEmpty() : Trie = null; // Note: More general version of this operation below, which tests for @@ -122,7 +123,7 @@ func Trie__makeEmpty() : Trie // }; // XXX: until AST-42: -func Trie__assertIsEmpty(t : Trie) { +func assertIsEmpty(t : Trie) { switch t { case null { assert(true) }; case (?_) { assert(false) }; @@ -130,12 +131,12 @@ func Trie__assertIsEmpty(t : Trie) { }; // XXX: until AST-42: -func Trie__makeBin(l:Trie, r:Trie) : Trie { +func makeBin(l:Trie, r:Trie) : Trie { ?(new {left=l; right=r; key=null; val=null }) }; // XXX: until AST-42: -func Trie__isBin(t:Trie) : Bool { +func isBin(t:Trie) : Bool { switch t { case null { false }; case (?t_) { @@ -148,12 +149,12 @@ func Trie__isBin(t:Trie) : Bool { }; // XXX: until AST-42: -func Trie__makeLeaf(k:K, v:V) : Trie { +func makeLeaf(k:K, v:V) : Trie { ?(new {left=null; right=null; key=?k; val=?v }) }; // XXX: until AST-42: -func Trie__matchLeaf(t:Trie) : ?(K,V) { +func matchLeaf(t:Trie) : ?(K,V) { switch t { case null { null }; case (?t_) { @@ -166,7 +167,7 @@ func Trie__matchLeaf(t:Trie) : ?(K,V) { }; // XXX: until AST-42: -func Trie__isLeaf(t:Trie) : Bool { +func isLeaf(t:Trie) : Bool { switch t { case null { false }; case (?t_) { @@ -178,36 +179,36 @@ func Trie__isLeaf(t:Trie) : Bool { } }; // XXX: until AST-42: -func Trie__assertIsBin(t : Trie) { +func assertIsBin(t : Trie) { switch t { case null { assert(false) }; case (?n) { - Trie__assertIsNull(n.key); - Trie__assertIsNull(n.val); + assertIsNull(n.key); + assertIsNull(n.val); }; } }; // XXX: until AST-42: -func Trie__getLeafKey(t : Node) : K { - Trie__assertIsNull>(t.left); - Trie__assertIsNull>(t.right); +func getLeafKey(t : Node) : K { + assertIsNull>(t.left); + assertIsNull>(t.right); switch (t.key) { case (?k) { k }; - case null { Trie__getLeafKey(t) }; + case null { getLeafKey(t) }; } }; // XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: -func Trie__getLeafVal(t : Node) : ?V { - Trie__assertIsNull>(t.left); - Trie__assertIsNull>(t.right); +func getLeafVal(t : Node) : ?V { + assertIsNull>(t.left); + assertIsNull>(t.right); t.val }; // TODO: Replace with bitwise operations on Words, once we have each of those in AS. // For now, we encode hashes as lists of booleans. -func Trie__getHashBit(h:Hash, pos:Nat) : Bool { +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. @@ -215,21 +216,21 @@ func Trie__getHashBit(h:Hash, pos:Nat) : Bool { }; case (?(b, h_)) { if (pos == 0) { b } - else { Trie__getHashBit(h_, pos-1) } + else { getHashBit(h_, pos-1) } }; } }; // part of "public interface": -func Trie__empty() : Trie = Trie__makeEmpty(); +func empty() : Trie = makeEmpty(); // helper function for constructing new paths of uniform length -func Trie__buildNewPath(bitpos:Nat, k:K, k_hash:Hash, ov:?V) : Trie { +func buildNewPath(bitpos:Nat, k:K, k_hash:Hash, 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 = Trie__getHashBit(k_hash, bitpos); + let bit = getHashBit(k_hash, bitpos); if (not bit) { ?(new {left=path; right=null; key=null; val=null}) } @@ -245,15 +246,15 @@ func Trie__buildNewPath(bitpos:Nat, k:K, k_hash:Hash, ov:?V) : Trie { }; // replace the given key's value option with the given one, returning the previous one -func Trie__replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { +func replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) { // 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 { (Trie__buildNewPath(bitpos, k, k_hash, v), null) }; + case null { (buildNewPath(bitpos, k, k_hash, v), null) }; case (?n) { - Trie__assertIsBin(t); - let bit = Trie__getHashBit(k_hash, bitpos); + 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); @@ -268,7 +269,7 @@ func Trie__replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) } else { // No more walking; we should be at a leaf now, by construction invariants. switch t { - case null { (Trie__buildNewPath(bitpos, k, k_hash, v), null) }; + 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) @@ -280,17 +281,17 @@ func Trie__replace(t : Trie, k:K, k_hash:Hash, v:?V) : (Trie, ?V) }; // insert the given key's value in the trie; return the new trie -func Trie__insert(t : Trie, k:K, k_hash:Hash, v:V) : (Trie, ?V) { - Trie__replace(t, k, k_hash, ?v) +func insert(t : Trie, k:K, k_hash:Hash, v:V) : (Trie, ?V) { + replace(t, k, k_hash, ?v) }; // remove the given key's value in the trie; return the new trie -func Trie__remove(t : Trie, k:K, k_hash:Hash) : (Trie, ?V) { - Trie__replace(t, k, k_hash, null) +func remove(t : Trie, k:K, k_hash:Hash) : (Trie, ?V) { + replace(t, k, k_hash, null) }; // find the given key's value in the trie, or return null if nonexistent -func Trie__find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { +func find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { // 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 ) { @@ -300,8 +301,8 @@ func Trie__find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { null }; case (?n) { - Trie__assertIsBin(t); - let bit = Trie__getHashBit(k_hash, bitpos); + assertIsBin(t); + let bit = getHashBit(k_hash, bitpos); if (not bit) { rec(n.left, bitpos+1) } else { rec(n.right, bitpos+1) } }; @@ -312,8 +313,8 @@ func Trie__find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { case null { null }; case (?l) { // TODO: Permit hash collisions by walking a list/array of KV pairs in each leaf: - if (keq(Trie__getLeafKey(l), k)) { - Trie__getLeafVal(l) + if (keq(getLeafKey(l), k)) { + getLeafVal(l) } else { null } @@ -328,17 +329,17 @@ func Trie__find(t : Trie, k:K, k_hash:Hash, keq:(K,K) -> Bool) : ?V { // 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 Trie__merge(tl:Trie, tr:Trie) : Trie { +func merge(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { case (null, _) { return tr }; case (_, null) { return tl }; case (?nl,?nr) { - switch (Trie__isBin(tl), - Trie__isBin(tr)) { + switch (isBin(tl), + isBin(tr)) { case (true, true) { - let t0 = Trie__merge(nl.left, nr.left); - let t1 = Trie__merge(nl.right, nr.right); - Trie__makeBin(t0, t1) + let t0 = merge(nl.left, nr.left); + let t1 = merge(nl.right, nr.right); + makeBin(t0, t1) }; case (false, true) { assert(false); @@ -362,18 +363,18 @@ func Trie__merge(tl:Trie, tr:Trie) : Trie { // 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 Trie__diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { +func diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie { func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { - case (null, _) { return Trie__makeEmpty() }; + case (null, _) { return makeEmpty() }; case (_, null) { return tl }; case (?nl,?nr) { - switch (Trie__isBin(tl), - Trie__isBin(tr)) { + switch (isBin(tl), + isBin(tr)) { case (true, true) { let t0 = rec(nl.left, nr.left); let t1 = rec(nl.right, nr.right); - Trie__makeBin(t0, t1) + makeBin(t0, t1) }; case (false, true) { assert(false); @@ -390,7 +391,7 @@ func Trie__diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie switch (nl.key, nr.key) { case (?kl, ?kr) { if (keq(kl, kr)) { - Trie__makeEmpty(); + makeEmpty(); } else { tl }}; @@ -413,7 +414,7 @@ func Trie__diff(tl:Trie, tr:Trie, keq:(K,K)->Bool) : Trie // situations, the operator accepts optional values, but is never // applied to (null, null). // -func Trie__disj(tl:Trie, tr:Trie, +func disj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(?V,?W)->X) : Trie { @@ -421,9 +422,9 @@ func Trie__disj(tl:Trie, tr:Trie, switch t { case (null) null; case (? n) { - switch (Trie__matchLeaf(t)) { - case (?(k,v)) { Trie__makeLeaf(k, vbin(?v, null)) }; - case _ { Trie__makeBin(recL(n.left), recL(n.right)) } + switch (matchLeaf(t)) { + case (?(k,v)) { makeLeaf(k, vbin(?v, null)) }; + case _ { makeBin(recL(n.left), recL(n.right)) } } }; }}; @@ -431,51 +432,51 @@ func Trie__disj(tl:Trie, tr:Trie, switch t { case (null) null; case (? n) { - switch (Trie__matchLeaf(t)) { - case (?(k,w)) { Trie__makeLeaf(k, vbin(null, ?w)) }; - case _ { Trie__makeBin(recR(n.left), recR(n.right)) } + switch (matchLeaf(t)) { + case (?(k,w)) { makeLeaf(k, vbin(null, ?w)) }; + 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. - case (null, null) { Trie__makeEmpty() }; + case (null, null) { makeEmpty() }; case (null, _ ) { recR(tr) }; case (_, null) { recL(tl) }; case (? nl, ? nr) { - switch (Trie__isBin(tl), - Trie__isBin(tr)) { + switch (isBin(tl), + isBin(tr)) { case (true, true) { let t0 = rec(nl.left, nr.left); let t1 = rec(nl.right, nr.right); - Trie__makeBin(t0, t1) + makeBin(t0, t1) }; case (false, true) { assert(false); // XXX impossible, until we lift uniform depth assumption - Trie__makeEmpty() + makeEmpty() }; case (true, false) { assert(false); // XXX impossible, until we lift uniform depth assumption - Trie__makeEmpty() + makeEmpty() }; case (false, false) { - assert(Trie__isLeaf(tl)); - assert(Trie__isLeaf(tr)); + 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)) { - Trie__makeLeaf(kl, vbin(?vl, ?vr)); + makeLeaf(kl, vbin(?vl, ?vr)); } else { // XXX: handle hash collisions here. - Trie__makeEmpty() + makeEmpty() } }; // XXX impossible, and unnecessary with AST-42. - case _ { Trie__makeEmpty() }; + case _ { makeEmpty() }; } }; } @@ -488,48 +489,48 @@ func Trie__disj(tl:Trie, tr:Trie, // finite maps. Produces a "conjuctive image" of the two tries, 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 Trie__conj(tl:Trie, tr:Trie, +func conj(tl:Trie, tr:Trie, keq:(K,K)->Bool, vbin:(V,W)->X) : Trie { func rec(tl:Trie, tr:Trie) : Trie { switch (tl, tr) { - case (null, null) { return Trie__makeEmpty() }; - case (null, ? nr) { return Trie__makeEmpty() }; - case (? nl, null) { return Trie__makeEmpty() }; + case (null, null) { return makeEmpty() }; + case (null, ? nr) { return makeEmpty() }; + case (? nl, null) { return makeEmpty() }; case (? nl, ? nr) { - switch (Trie__isBin(tl), - Trie__isBin(tr)) { + switch (isBin(tl), + isBin(tr)) { case (true, true) { let t0 = rec(nl.left, nr.left); let t1 = rec(nl.right, nr.right); - Trie__makeBin(t0, t1) + makeBin(t0, t1) }; case (false, true) { assert(false); // XXX impossible, until we lift uniform depth assumption - Trie__makeEmpty() + makeEmpty() }; case (true, false) { assert(false); // XXX impossible, until we lift uniform depth assumption - Trie__makeEmpty() + makeEmpty() }; case (false, false) { - assert(Trie__isLeaf(tl)); - assert(Trie__isLeaf(tr)); + 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)) { - Trie__makeLeaf(kl, vbin(vl, vr)); + makeLeaf(kl, vbin(vl, vr)); } else { // XXX: handle hash collisions here. - Trie__makeEmpty() + makeEmpty() } }; // XXX impossible, and unnecessary with AST-42. - case _ { Trie__makeEmpty() }; + case _ { makeEmpty() }; } }; } @@ -542,12 +543,12 @@ func Trie__conj(tl:Trie, tr:Trie, // tries. Many common operations are instantiations of this function, // either as clients, or as hand-specialized versions (e.g., see map, // mapFilter, exists and forAll below). -func Trie__foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { +func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { func rec(t:Trie) : X { switch t { case (null) { empty }; case (?n) { - switch (Trie__matchLeaf(t)) { + switch (matchLeaf(t)) { case (?(k,v)) { leaf(k,v) }; case null { bin(rec(n.left), rec(n.right)) }; } @@ -558,12 +559,12 @@ func Trie__foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X // Fold over the key-value pairs of the trie, using an accumulator. // The key-value pairs have no reliable or meaningful ordering. -func Trie__fold(t:Trie, f:(K,V,X)->X, x:X) : X { +func fold(t:Trie, f:(K,V,X)->X, x:X) : X { func rec(t:Trie, x:X) : X { switch t { case (null) x; case (?n) { - switch (Trie__matchLeaf(t)) { + switch (matchLeaf(t)) { case (?(k,v)) { f(k,v,x) }; case null { rec(n.left,rec(n.right,x)) }; } @@ -573,12 +574,12 @@ func Trie__fold(t:Trie, f:(K,V,X)->X, x:X) : X { }; // specialized foldUp operation. -func Trie__exists(t:Trie, f:(K,V)->Bool) : Bool { +func exists(t:Trie, f:(K,V)->Bool) : Bool { func rec(t:Trie) : Bool { switch t { case (null) { false }; case (?n) { - switch (Trie__matchLeaf(t)) { + switch (matchLeaf(t)) { case (?(k,v)) { f(k,v) }; case null { rec(n.left) or rec(n.right) }; } @@ -588,12 +589,12 @@ func Trie__exists(t:Trie, f:(K,V)->Bool) : Bool { }; // specialized foldUp operation. -func Trie__forAll(t:Trie, f:(K,V)->Bool) : Bool { +func forAll(t:Trie, f:(K,V)->Bool) : Bool { func rec(t:Trie) : Bool { switch t { case (null) { true }; case (?n) { - switch (Trie__matchLeaf(t)) { + switch (matchLeaf(t)) { case (?(k,v)) { f(k,v) }; case null { rec(n.left) and rec(n.right) }; } @@ -606,12 +607,12 @@ func Trie__forAll(t:Trie, f:(K,V)->Bool) : Bool { // Test for "deep emptiness": subtrees that have branching structure, // but no leaves. These can result from naive filtering operations; // filter uses this function to avoid creating such subtrees. -func Trie__isEmpty(t:Trie) : Bool { +func isEmpty(t:Trie) : Bool { func rec(t:Trie) : Bool { switch t { case (null) { true }; case (?n) { - switch (Trie__matchLeaf(t)) { + switch (matchLeaf(t)) { case (?(k,v)) { false }; case null { rec(n.left) and rec(n.right) }; } @@ -621,19 +622,19 @@ func Trie__isEmpty(t:Trie) : Bool { rec(t) }; -func Trie__filter(t:Trie, f:(K,V)->Bool) : Trie { +func filter(t:Trie, f:(K,V)->Bool) : Trie { func rec(t:Trie) : Trie { switch t { case (null) { null }; case (?n) { - switch (Trie__matchLeaf(t)) { + 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)) { - Trie__makeLeaf(k,v) + makeLeaf(k,v) } else { null } @@ -641,12 +642,12 @@ func Trie__filter(t:Trie, f:(K,V)->Bool) : Trie { case null { let l = rec(n.left); let r = rec(n.right); - switch (Trie__isEmpty(l), - Trie__isEmpty(r)) { + switch (isEmpty(l), + isEmpty(r)) { case (true, true) null; case (false, true) r; case (true, false) l; - case (false, false) Trie__makeBin(l, r); + case (false, false) makeBin(l, r); } }; } @@ -656,26 +657,26 @@ func Trie__filter(t:Trie, f:(K,V)->Bool) : Trie { rec(t) }; -func Trie__mapFilter(t:Trie, f:(K,V)->?(K,W)) : Trie { +func mapFilter(t:Trie, f:(K,V)->?(K,W)) : Trie { func rec(t:Trie) : Trie { switch t { case (null) { null }; case (?n) { - switch (Trie__matchLeaf(t)) { + switch (matchLeaf(t)) { case (?(k,v)) { switch (f(k,v)) { case (null) null; - case (?(k,w)) { Trie__makeLeaf(k,w) }; + case (?(k,w)) { makeLeaf(k,w) }; }}; case null { let l = rec(n.left); let r = rec(n.right); - switch (Trie__isEmpty(l), - Trie__isEmpty(r)) { + switch (isEmpty(l), + isEmpty(r)) { case (true, true) null; case (false, true) r; case (true, false) l; - case (false, false) Trie__makeBin(l, r); + case (false, false) makeBin(l, r); } }; } @@ -692,7 +693,7 @@ func Trie__mapFilter(t:Trie, f:(K,V)->?(K,W)) : Trie { // with // `#empty`. // We do not observe that equality here. -func Trie__equalStructure( +func equalStructure( tl:Trie, tr:Trie, keq:(K,K)->Bool, @@ -704,8 +705,8 @@ func Trie__equalStructure( case (_, null) { false }; case (null, _) { false }; case (?nl, ?nr) { - switch (Trie__matchLeaf(tl), - Trie__matchLeaf(tr)) { + 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) }; @@ -716,25 +717,4 @@ func Trie__equalStructure( rec(tl, tr) }; -// Create a record, -// as a standin until we have "real" modules to create namespaces: -let Trie = new { - moduleName = "Trie" - ; empty = Trie__empty - ; insert = Trie__insert - ; remove = Trie__remove - ; find = Trie__find - ; replace = Trie__replace - ; merge = Trie__merge - ; diff = Trie__diff - ; disj = Trie__disj - ; conj = Trie__conj - ; foldUp = Trie__foldUp - ; fold = Trie__fold - ; exists = Trie__exists - ; forAll = Trie__forAll - ; isEmpty = Trie__isEmpty - ; filter = Trie__filter - ; mapFilter = Trie__mapFilter - ; equalStructure = Trie__equalStructure -}; +}; \ No newline at end of file From 1d4b2730704d425f06fcc5208605ffb6a8d4719b Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Thu, 7 Mar 2019 12:34:04 +0000 Subject: [PATCH 44/52] indent 'module' bodies --- samples/collections/list.as | 516 ++++++++-------- samples/collections/set.as | 100 +-- samples/collections/trie.as | 1159 ++++++++++++++++++----------------- 3 files changed, 888 insertions(+), 887 deletions(-) diff --git a/samples/collections/list.as b/samples/collections/list.as index 25fe15d3a01..d16b2f78902 100644 --- a/samples/collections/list.as +++ b/samples/collections/list.as @@ -27,313 +27,313 @@ type List = ?(T, List); let List = new { -// empty list -func nil() : List = - null; - -// test for empty list -func isNil(l : List) : Bool { - switch l { - case null { true }; - case _ { false }; - } -}; - -// aka "list cons" -func push(x : T, l : List) : List = - ?(x, l); - -// last element, optionally; tail recursive -func last(l : List) : ?T = { - switch l { - case null { null }; - case (?(x,null)) { ?x }; - case (?(_,t)) { last(t) }; - } -}; - -// treat the list as a stack; combines 'hd' and (non-failing) 'tl' into one operation -func pop(l : List) : (?T, List) = { - switch l { - case null { (null, null) }; - case (?(h, t)) { (?h, t) }; - } -}; + // empty list + func nil() : List = + null; -// length; tail recursive -func len(l : List) : Nat = { - func rec(l : List, n : Nat) : Nat { + // test for empty list + func isNil(l : List) : Bool { switch l { - case null { n }; - case (?(_,t)) { rec(t,n+1) }; + case null { true }; + case _ { false }; } }; - rec(l,0) -}; -// array-like list access, but in linear time; tail recursive -func nth(l : List, n : Nat) : ?T = { - switch (n, l) { - case (_, null) { null }; - case (0, (?(h,t))) { ?h }; - case (_, (?(_,t))) { nth(t, n - 1) }; - } -}; + // aka "list cons" + func push(x : T, l : List) : List = + ?(x, l); -// reverse; tail recursive -func rev(l : List) : List = { - func rec(l : List, r : List) : List { + // last element, optionally; tail recursive + func last(l : List) : ?T = { switch l { - case null { r }; - case (?(h,t)) { rec(t,?(h,r)) }; + case null { null }; + case (?(x,null)) { ?x }; + case (?(_,t)) { last(t) }; } }; - rec(l, null) -}; -// Called "app" in SML Basis, and "iter" in OCaml; tail recursive -func iter(l : List, f:T -> ()) : () = { - func rec(l : List) : () { + // treat the list as a stack; combines 'hd' and (non-failing) 'tl' into one operation + func pop(l : List) : (?T, List) = { switch l { - case null { () }; - case (?(h,t)) { f(h) ; rec(t) }; + case null { (null, null) }; + case (?(h, t)) { (?h, t) }; } }; - rec(l) -}; -// map; non-tail recursive -// (Note: need mutable Cons tails for tail-recursive map) -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)) }; - } + // length; tail recursive + func len(l : List) : Nat = { + func rec(l : List, n : Nat) : Nat { + switch l { + case null { n }; + case (?(_,t)) { rec(t,n+1) }; + } + }; + rec(l,0) }; - rec(l) -}; -// filter; non-tail recursive -// (Note: need mutable Cons tails for tail-recursive version) -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) } }; + // array-like list access, but in linear time; tail recursive + func nth(l : List, n : Nat) : ?T = { + switch (n, l) { + case (_, null) { null }; + case (0, (?(h,t))) { ?h }; + case (_, (?(_,t))) { nth(t, n - 1) }; } }; - rec(l) -}; -// map-and-filter; non-tail recursive -// (Note: need mutable Cons tails for tail-recursive version) -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)) }; - } - }; - } + // reverse; tail recursive + func rev(l : List) : List = { + func rec(l : List, r : List) : List { + switch l { + case null { r }; + case (?(h,t)) { rec(t,?(h,r)) }; + } + }; + rec(l, null) }; - rec(l) -}; -// append; non-tail recursive -// (Note: need mutable Cons tails for tail-recursive version) -func append(l : List, m : List) : List = { - func rec(l : List) : List { - switch l { - case null { m }; - case (?(h,t)) {?(h,rec(l))}; - } + // Called "app" in SML Basis, and "iter" in OCaml; tail recursive + func iter(l : List, f:T -> ()) : () = { + func rec(l : List) : () { + switch l { + case null { () }; + case (?(h,t)) { f(h) ; rec(t) }; + } + }; + rec(l) }; - rec(l) -}; -// concat (aka "list join"); tail recursive, but requires "two passes" -func concat(l : List>) : List = { - // 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) + // map; non-tail recursive + // (Note: need mutable Cons tails for tail-recursive map) + 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)) }; + } }; - // 2/2: ...re-reverse the elements, to their original order: - rev(r) -}; + rec(l) + }; -// (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)) }; - } -}; + // filter; non-tail recursive + // (Note: need mutable Cons tails for tail-recursive version) + 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) } }; + } + }; + rec(l) + }; -// take; non-tail recursive -// (Note: need mutable Cons tails for tail-recursive version) -func take(l : List, n:Nat) : List = { - switch (l, n) { - case (_, 0) { null }; - case (null,_) { null }; - case (?(h, t), m) {?(h, take(t, m - 1))}; - } -}; + // map-and-filter; non-tail recursive + // (Note: need mutable Cons tails for tail-recursive version) + 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)) }; + } + }; + } + }; + rec(l) + }; -// drop; tail recursive -func drop(l : List, n:Nat) : List = { - switch (l, n) { - case (l_, 0) { l_ }; - case (null, _) { null }; - case ((?(h,t)), m) { drop(t, m - 1) }; - } -}; + // append; non-tail recursive + // (Note: need mutable Cons tails for tail-recursive version) + func append(l : List, m : List) : List = { + func rec(l : List) : List { + switch l { + case null { m }; + case (?(h,t)) {?(h,rec(l))}; + } + }; + rec(l) + }; -// fold list left-to-right using f; tail recursive -func foldLeft(l : List, a:S, f:(T,S) -> S) : S = { - func rec(l:List, a:S) : S = { - switch l { - case null { a }; - case (?(h,t)) { rec(t, f(h,a)) }; - } + // concat (aka "list join"); tail recursive, but requires "two passes" + func concat(l : List>) : List = { + // 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) + }; + // 2/2: ...re-reverse the elements, to their original order: + rev(r) }; - rec(l,a) -}; -// fold list right-to-left using f; non-tail recursive -func foldRight(l : List, a:S, f:(T,S) -> S) : S = { - func rec(l:List) : S = { - switch l { - case null { a }; - case (?(h,t)) { f(h, rec(t)) }; + // (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)) }; } }; - rec(l) -}; -// test if there exists list element for which given predicate is true -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) } }; + // take; non-tail recursive + // (Note: need mutable Cons tails for tail-recursive version) + func take(l : List, n:Nat) : List = { + switch (l, n) { + case (_, 0) { null }; + case (null,_) { null }; + case (?(h, t), m) {?(h, take(t, m - 1))}; } }; - rec(l) -}; -// test if there exists list element for which given predicate is true -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) } }; + // drop; tail recursive + func drop(l : List, n:Nat) : List = { + switch (l, n) { + case (l_, 0) { l_ }; + case (null, _) { null }; + case ((?(h,t)), m) { drop(t, m - 1) }; } }; - rec(l) -}; -// test if given predicate is true for all list elements -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) } }; - } + // fold list left-to-right using f; tail recursive + func foldLeft(l : List, a:S, f:(T,S) -> S) : S = { + func rec(l:List, a:S) : S = { + switch l { + case null { a }; + case (?(h,t)) { rec(t, f(h,a)) }; + } + }; + rec(l,a) }; - rec(l) -}; -// Given two ordered lists, merge them into a single ordered list -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)) - } - }; - } + // fold list right-to-left using f; non-tail recursive + func foldRight(l : List, a:S, f:(T,S) -> S) : S = { + func rec(l:List) : S = { + switch l { + case null { a }; + case (?(h,t)) { f(h, rec(t)) }; + } + }; + rec(l) }; - rec(l1, l2) -}; -// Compare two lists lexicographic` ordering. tail recursive. -// XXX: Eventually, follow `collate` design from SML Basis, with real sum types, use 3-valued `order` type here. -// -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 - } - }; - } + // test if there exists list element for which given predicate is true + 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) } }; + } + }; + rec(l) }; - rec(l1, l2) -}; -// Compare two lists for equality. tail recursive. -// `isEq(l1, l2)` =equiv= `lessThanEq(l1,l2) && lessThanEq(l2,l1)`, but the former is more efficient. -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 - } - }; - } + // test if there exists list element for which given predicate is true + 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) } }; + } + }; + rec(l) }; - rec(l1, l2) -}; -// using a predicate, create two lists from one: the "true" list, and the "false" list. -// (See SML basis library); non-tail recursive -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)) - } - }; - } + // test if given predicate is true for all list elements + 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) } }; + } + }; + rec(l) }; - rec(l) -}; -// generate a list based on a length, and a function from list index to list element; -// (See SML basis library); non-tail recursive -func tabulate(n:Nat, f:Nat -> T) : List { - func rec(i:Nat) : List { - if (i == n) { null } else { ?(f(i), rec(i+1)) } + // Given two ordered lists, merge them into a single ordered list + 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)) + } + }; + } + }; + rec(l1, l2) + }; + + // Compare two lists lexicographic` ordering. tail recursive. + // XXX: Eventually, follow `collate` design from SML Basis, with real sum types, use 3-valued `order` type here. + // + 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 + } + }; + } + }; + rec(l1, l2) + }; + + // Compare two lists for equality. tail recursive. + // `isEq(l1, l2)` =equiv= `lessThanEq(l1,l2) && lessThanEq(l2,l1)`, but the former is more efficient. + 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 + } + }; + } + }; + rec(l1, l2) + }; + + // using a predicate, create two lists from one: the "true" list, and the "false" list. + // (See SML basis library); non-tail recursive + 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)) + } + }; + } + }; + rec(l) + }; + + // generate a list based on a length, and a function from list index to list element; + // (See SML basis library); non-tail recursive + func tabulate(n:Nat, f:Nat -> T) : List { + func rec(i:Nat) : List { + if (i == n) { null } else { ?(f(i), rec(i+1)) } + }; + rec(0) }; - rec(0) -}; }; diff --git a/samples/collections/set.as b/samples/collections/set.as index fe7ce35bba2..00ebf8e664c 100644 --- a/samples/collections/set.as +++ b/samples/collections/set.as @@ -21,55 +21,55 @@ type Set = Trie; let Set = new { -func empty():Set = - Trie.empty(); - -func insert(s:Set, x:T, xh:Hash):Set = { - let (s2, _) = Trie.insert(s, x, xh, ()); - s2 -}; - -func remove(s:Set, x:T, xh:Hash):Set = { - let (s2, _) = Trie.remove(s, x, xh); - s2 -}; - -func eq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { - // XXX: Todo: use a smarter check - Trie.equalStructure(s1, s2, eq, unitEq) -}; - -func card(s:Set) : Nat { - Trie.foldUp - (s, - func(n:Nat,m:Nat):Nat{n+m}, - func(_:T,_:()):Nat{1}, - 0) -}; - -func mem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { - switch (Trie.find(s, x, xh, eq)) { - case null { false }; - case (?_) { true }; - } -}; - -func union(s1:Set, s2:Set):Set { - let s3 = Trie.merge(s1, s2); - s3 -}; - -func diff(s1:Set, s2:Set, eq:(T,T)->Bool):Set { - let s3 = Trie.diff(s1, s2, eq); - s3 -}; - -func intersect(s1:Set, s2:Set, eq:(T,T)->Bool):Set { - let noop : ((),())->(()) = func (_:(),_:()):(())=(); - let s3 = Trie.conj(s1, s2, eq, noop); - s3 -}; - -func unitEq (_:(),_:()):Bool{ true }; + func empty():Set = + Trie.empty(); + + func insert(s:Set, x:T, xh:Hash):Set = { + let (s2, _) = Trie.insert(s, x, xh, ()); + s2 + }; + + func remove(s:Set, x:T, xh:Hash):Set = { + let (s2, _) = Trie.remove(s, x, xh); + s2 + }; + + func eq(s1:Set, s2:Set, eq:(T,T)->Bool):Bool { + // XXX: Todo: use a smarter check + Trie.equalStructure(s1, s2, eq, unitEq) + }; + + func card(s:Set) : Nat { + Trie.foldUp + (s, + func(n:Nat,m:Nat):Nat{n+m}, + func(_:T,_:()):Nat{1}, + 0) + }; + + func mem(s:Set, x:T, xh:Hash, eq:(T,T)->Bool):Bool { + switch (Trie.find(s, x, xh, eq)) { + case null { false }; + case (?_) { true }; + } + }; + + func union(s1:Set, s2:Set):Set { + let s3 = Trie.merge(s1, s2); + s3 + }; + + func diff(s1:Set, s2:Set, eq:(T,T)->Bool):Set { + let s3 = Trie.diff(s1, s2, eq); + s3 + }; + + func intersect(s1:Set, s2:Set, eq:(T,T)->Bool):Set { + let noop : ((),())->(()) = func (_:(),_:()):(())=(); + let s3 = Trie.conj(s1, s2, eq, noop); + s3 + }; + + func unitEq (_:(),_:()):Bool{ true }; }; \ No newline at end of file diff --git a/samples/collections/trie.as b/samples/collections/trie.as index a667a43a725..aaec442f09c 100644 --- a/samples/collections/trie.as +++ b/samples/collections/trie.as @@ -89,632 +89,633 @@ 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 }; - }; -}; -// XXX: until AST-42: -func assertIsNull(x : ?X) { - switch x { - case null { assert(true) }; - case (?_) { assert(false) }; + // XXX: until AST-42: + func isNull(x : ?X) : Bool { + switch x { + case null { true }; + case (?_) { false }; + }; }; -}; - -// XXX: until AST-42: -func makeEmpty() : Trie - = null; -// Note: More general version of this operation below, which tests for -// "deep emptiness" (subtrees that have branching structure, but no -// leaves; these can result from naive filtering operations, for -// instance). -// -// // XXX: until AST-42: -// func isEmpty(t:Trie) : Bool { -// switch t { -// case null { true }; -// case (?_) { false }; -// }; -// }; - -// XXX: until AST-42: -func assertIsEmpty(t : Trie) { - switch t { - case null { assert(true) }; - case (?_) { assert(false) }; + // XXX: until AST-42: + func assertIsNull(x : ?X) { + switch x { + 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 }) -}; -// XXX: until AST-42: -func isBin(t:Trie) : Bool { - switch t { - case null { false }; - case (?t_) { - switch (t_.key) { - 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 }) -}; - -// XXX: until AST-42: -func matchLeaf(t:Trie) : ?(K,V) { - switch t { - case null { null }; - case (?t_) { - switch (t_.key, t_.val) { - case (?k,?v) ?(k,v); - case (_) null; - } - }; - } -}; - -// XXX: until AST-42: -func isLeaf(t:Trie) : Bool { - switch t { - case null { false }; - case (?t_) { - switch (t_.key) { - 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); - }; - } -}; + // XXX: until AST-42: + func makeEmpty() : Trie + = null; + + // Note: More general version of this operation below, which tests for + // "deep emptiness" (subtrees that have branching structure, but no + // leaves; these can result from naive filtering operations, for + // instance). + // + // // XXX: until AST-42: + // func isEmpty(t:Trie) : Bool { + // switch t { + // case null { true }; + // case (?_) { false }; + // }; + // }; + + // XXX: until AST-42: + func assertIsEmpty(t : Trie) { + switch t { + case null { assert(true) }; + case (?_) { assert(false) }; + }; + }; -// XXX: until AST-42: -func getLeafKey(t : Node) : K { - assertIsNull>(t.left); - assertIsNull>(t.right); - switch (t.key) { - case (?k) { k }; - case null { getLeafKey(t) }; - } -}; + // XXX: until AST-42: + func makeBin(l:Trie, r:Trie) : Trie { + ?(new {left=l; right=r; key=null; val=null }) + }; -// XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: -func getLeafVal(t : Node) : ?V { - assertIsNull>(t.left); - assertIsNull>(t.right); - t.val -}; + // XXX: until AST-42: + func isBin(t:Trie) : Bool { + switch t { + case null { false }; + case (?t_) { + switch (t_.key) { + case null { true }; + case _ { false }; + }; + }; + } + }; -// TODO: Replace with bitwise operations on Words, once we have each of those in AS. -// For now, we encode hashes as lists of booleans. -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 - }; - case (?(b, h_)) { - if (pos == 0) { b } - else { getHashBit(h_, pos-1) } - }; - } -}; + // XXX: until AST-42: + func makeLeaf(k:K, v:V) : Trie { + ?(new {left=null; right=null; key=?k; val=?v }) + }; -// 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 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}) - } - } else { - // create new leaf for (k,v) pair - ?(new {left=null; right=null; key=?k; val=ov }) + // XXX: until AST-42: + func matchLeaf(t:Trie) : ?(K,V) { + switch t { + case null { null }; + case (?t_) { + switch (t_.key, t_.val) { + case (?k,?v) ?(k,v); + case (_) 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) { - // 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_) - } - }; + // XXX: until AST-42: + func isLeaf(t:Trie) : Bool { + switch t { + case null { false }; + case (?t_) { + switch (t_.key) { + case null { false }; + case _ { true }; + } + }; } - } 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) - }; - } + }; + // XXX: until AST-42: + func assertIsBin(t : Trie) { + switch t { + case null { assert(false) }; + case (?n) { + assertIsNull(n.key); + assertIsNull(n.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) -}; + // XXX: until AST-42: + func getLeafKey(t : Node) : K { + assertIsNull>(t.left); + assertIsNull>(t.right); + switch (t.key) { + case (?k) { k }; + case null { getLeafKey(t) }; + } + }; -// 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) -}; + // XXX: this helper is an ugly hack; we need real sum types to avoid it, I think: + func getLeafVal(t : Node) : ?V { + assertIsNull>(t.left); + assertIsNull>(t.right); + t.val + }; -// 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 { - // 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) } - }; - } - } 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 - } - }; - } + // TODO: Replace with bitwise operations on Words, once we have each of those in AS. + // For now, we encode hashes as lists of booleans. + 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 + }; + case (?(b, h_)) { + if (pos == 0) { b } + else { getHashBit(h_, pos-1) } + }; } }; - rec(t, 0) -}; -// merge tries, preferring the right trie where there are collisions -// 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) { - 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 - }; + // 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 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}) + } + } else { + // create new leaf for (k,v) pair + ?(new {left=null; right=null; key=?k; val=ov }) } - }; - } -}; + }; + rec(bitpos) + }; -// 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 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 } - } - }; + // 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) { + // 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_) + } + }; } - }; - }}; - rec(tl, tr) -}; - -// This operation generalizes the notion of "set union" to finite maps. -// Produces a "disjunctive image" of the two tries, 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(tl:Trie, tr:Trie, - keq:(K,K)->Bool, vbin:(?V,?W)->X) - : Trie -{ - 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)) } + } 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) + }; + } } }; - }}; - 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)) } + 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) + }; + + // 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) + }; + + // 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 { + // 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) } + }; + } + } 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 + } + }; + } } }; - }}; - func rec(tl:Trie, tr:Trie) : Trie { - switch (tl, tr) { - // 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() }; - } - }; - } - }; - }}; - rec(tl, tr) -}; + rec(t, 0) + }; -// This operation generalizes the notion of "set intersection" to -// finite maps. Produces a "conjuctive image" of the two tries, 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(tl:Trie, tr:Trie, - keq:(K,K)->Bool, vbin:(V,W)->X) - : Trie -{ - func rec(tl:Trie, tr:Trie) : Trie { + // merge tries, preferring the right trie where there are collisions + // 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) { - case (null, null) { return makeEmpty() }; - case (null, ? nr) { return makeEmpty() }; - case (? nl, null) { return makeEmpty() }; - case (? nl, ? nr) { + case (null, _) { return tr }; + case (_, null) { return tl }; + case (?nl,?nr) { switch (isBin(tl), - isBin(tr)) { + isBin(tr)) { case (true, true) { - let t0 = rec(nl.left, nr.left); - let t1 = rec(nl.right, nr.right); - makeBin(t0, t1) - }; + 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 - makeEmpty() - }; + assert(false); + // XXX impossible, until we lift uniform depth assumption + tr + }; case (true, false) { - assert(false); - // XXX impossible, until we lift uniform depth assumption - makeEmpty() - }; + assert(false); + // XXX impossible, until we lift uniform depth assumption + tr + }; 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() }; - } - }; - } - } - }}; - rec(tl, tr) -}; + /// XXX: handle hash collisions here. + tr + }; + } + }; + } + }; -// This operation gives a recursor for the internal structure of -// tries. Many common operations are instantiations of this function, -// either as clients, or as hand-specialized versions (e.g., see map, -// mapFilter, exists and forAll below). -func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { - func rec(t:Trie) : X { - 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)) }; - } - }; + // 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 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 } + } + }; + } + }; }}; - rec(t) -}; + rec(tl, tr) + }; -// Fold over the key-value pairs of the trie, using an accumulator. -// The key-value pairs have no reliable or meaningful ordering. -func fold(t:Trie, f:(K,V,X)->X, x:X) : X { - func rec(t:Trie, x:X) : X { - 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)) }; - } - }; + // This operation generalizes the notion of "set union" to finite maps. + // Produces a "disjunctive image" of the two tries, 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(tl:Trie, tr:Trie, + keq:(K,K)->Bool, vbin:(?V,?W)->X) + : Trie + { + 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)) } + } + }; }}; - rec(t, x) -}; - -// specialized foldUp operation. -func exists(t:Trie, f:(K,V)->Bool) : Bool { - func rec(t:Trie) : Bool { - 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) }; - } - }; + 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)) } + } + }; }}; - 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) }; - } - }; + func rec(tl:Trie, tr:Trie) : Trie { + switch (tl, tr) { + // 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() }; + } + }; + } + }; }}; - rec(t) -}; + rec(tl, tr) + }; -// specialized foldUp operation. -// Test for "deep emptiness": subtrees that have branching structure, -// but no leaves. These can result from naive filtering operations; -// filter uses this function to avoid creating such subtrees. -func isEmpty(t:Trie) : Bool { - func rec(t:Trie) : Bool { - switch t { - case (null) { true }; - case (?n) { - switch (matchLeaf(t)) { - case (?(k,v)) { false }; - case null { rec(n.left) and rec(n.right) }; - } - }; - } + // This operation generalizes the notion of "set intersection" to + // finite maps. Produces a "conjuctive image" of the two tries, 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(tl:Trie, tr:Trie, + keq:(K,K)->Bool, vbin:(V,W)->X) + : Trie + { + 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() }; + } + }; + } + } + }}; + rec(tl, tr) }; - rec(t) -}; -func filter(t:Trie, f:(K,V)->Bool) : Trie { - func rec(t:Trie) : Trie { - 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); - } - }; - } - }; - } + // This operation gives a recursor for the internal structure of + // tries. Many common operations are instantiations of this function, + // either as clients, or as hand-specialized versions (e.g., see map, + // mapFilter, exists and forAll below). + func foldUp(t:Trie, bin:(X,X)->X, leaf:(K,V)->X, empty:X) : X { + func rec(t:Trie) : X { + 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)) }; + } + }; + }}; + rec(t) }; - rec(t) -}; -func mapFilter(t:Trie, f:(K,V)->?(K,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); - } - }; - } - }; - } + // Fold over the key-value pairs of the trie, using an accumulator. + // The key-value pairs have no reliable or meaningful ordering. + func fold(t:Trie, f:(K,V,X)->X, x:X) : X { + func rec(t:Trie, x:X) : X { + 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)) }; + } + }; + }}; + rec(t, x) }; - rec(t) -}; -// Test for equality, but naively, based on structure. -// Does not attempt to remove "junk" in the tree; -// For instance, a "smarter" approach would equate -// `#bin{left=#empty;right=#empty}` -// with -// `#empty`. -// We do not observe that equality here. -func equalStructure( - tl:Trie, - tr:Trie, - keq:(K,K)->Bool, - veq:(V,V)->Bool -) : Bool { - func rec(tl:Trie, tr:Trie) : Bool { - switch (tl, tr) { - case (null, null) { true }; - 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 } - } + // specialized foldUp operation. + func exists(t:Trie, f:(K,V)->Bool) : Bool { + func rec(t:Trie) : Bool { + 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) }; + } + }; + }}; + 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) }; + } + }; + }}; + rec(t) + }; + + // specialized foldUp operation. + // Test for "deep emptiness": subtrees that have branching structure, + // but no leaves. These can result from naive filtering operations; + // filter uses this function to avoid creating such subtrees. + func isEmpty(t:Trie) : Bool { + func rec(t:Trie) : Bool { + switch t { + case (null) { true }; + case (?n) { + switch (matchLeaf(t)) { + case (?(k,v)) { false }; + case null { rec(n.left) and rec(n.right) }; + } + }; + } }; - }}; - rec(tl, tr) -}; + rec(t) + }; + + func filter(t:Trie, f:(K,V)->Bool) : Trie { + func rec(t:Trie) : Trie { + 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); + } + }; + } + }; + } + }; + rec(t) + }; -}; \ No newline at end of file + func mapFilter(t:Trie, f:(K,V)->?(K,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); + } + }; + } + }; + } + }; + rec(t) + }; + + // Test for equality, but naively, based on structure. + // Does not attempt to remove "junk" in the tree; + // For instance, a "smarter" approach would equate + // `#bin{left=#empty;right=#empty}` + // with + // `#empty`. + // We do not observe that equality here. + func equalStructure( + tl:Trie, + tr:Trie, + keq:(K,K)->Bool, + veq:(V,V)->Bool + ) : Bool { + func rec(tl:Trie, tr:Trie) : Bool { + switch (tl, tr) { + case (null, null) { true }; + 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 } + } + }; + }}; + rec(tl, tr) + }; + +}; From d0b0a8dd707a2e4b50ba62b1e892aee63b8c84b0 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Thu, 7 Mar 2019 12:50:17 +0000 Subject: [PATCH 45/52] forgot to fix SetDb.as --- samples/collections/setDb.as | 193 +++++++++++++++++------------------ 1 file changed, 92 insertions(+), 101 deletions(-) diff --git a/samples/collections/setDb.as b/samples/collections/setDb.as index 093c0dd9803..96a7d9a99cc 100644 --- a/samples/collections/setDb.as +++ b/samples/collections/setDb.as @@ -1,116 +1,107 @@ // import Set //////////////////////////////////////////////////////////////////// +let SetDb = new { -func SetDb__print(s:Set) { - func rec(s:Set, ind:Nat, bits:Hash) { - func indPrint(i:Nat) { - 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." } - } + 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) } + }; + func bitsPrintRev(bits:Bits) { + switch bits { + case null { print "" }; + case (?(bit,bits_)) { + bitsPrintRev(bits_); + if bit { print "1R." } + else { print "0L." } + } + } + }; + switch s { + case null { + //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 s { - case null { - //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"; - }; - } - }; - } + rec(s, 0, null); }; - rec(s, 0, null); -}; -//////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// -func natEq(n:Nat,m:Nat):Bool{ n == m}; + private func natEq(n:Nat,m:Nat):Bool{ n == m}; -func SetDb__insert(s:Set, x:Nat, xh:Hash):Set = { - print " setInsert("; - printInt x; - print ")"; - let r = Set.insert(s,x,xh); - print ";\n"; - SetDb__print(r); - r -}; - -func SetDb__mem(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { - print " setMem("; - print sname; - print ", "; - printInt x; - print ")"; - let b = Set.mem(s,x,xh,natEq); - if b { print " = true" } else { print " = false" }; - print ";\n"; - b -}; + func insert(s:Set, x:Nat, xh:Hash):Set = { + print " setInsert("; + printInt x; + print ")"; + let r = Set.insert(s,x,xh); + print ";\n"; + setDbPrint(r); + r + }; -func SetDb__union(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { - print " setUnion("; - print s1name; - print ", "; - print s2name; - print ")"; - // also: test that merge agrees with disj: - let r1 = Set.union(s1, s2); - let r2 = Trie.disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); - assert(Trie.equalStructure(r1, r2, natEq, Set.unitEq)); - print ";\n"; - SetDb__print(r1); - print "=========\n"; - SetDb__print(r2); - r1 -}; + func mem(s:Set, sname:Text, x:Nat, xh:Hash):Bool = { + print " setMem("; + print sname; + print ", "; + printInt x; + print ")"; + let b = Set.mem(s,x,xh,natEq); + if b { print " = true" } else { print " = false" }; + print ";\n"; + b + }; -func SetDb__intersect(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { - print " setIntersect("; - print s1name; - print ", "; - print s2name; - print ")"; - let r = Set.intersect(s1, s2, natEq); - print ";\n"; - SetDb__print(r); - r -}; + func union(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setUnion("; + print s1name; + print ", "; + print s2name; + print ")"; + // also: test that merge agrees with disj: + let r1 = Set.union(s1, s2); + let r2 = Trie.disj(s1, s2, natEq, func (_:?(),_:?()):(())=()); + assert(Trie.equalStructure(r1, r2, natEq, Set.unitEq)); + print ";\n"; + setDbPrint(r1); + print "=========\n"; + setDbPrint(r2); + r1 + }; -///////////////////////////////////////////////////////////////////////////////// + func intersect(s1:Set, s1name:Text, s2:Set, s2name:Text):Set = { + print " setIntersect("; + print s1name; + print ", "; + print s2name; + print ")"; + let r = Set.intersect(s1, s2, natEq); + print ";\n"; + setDbPrint(r); + r + }; -// Create a record, -// as a standin until we have "real" modules to create namespaces: -let SetDb = new { - moduleName = "SetDb" - ; insert = SetDb__insert - ; mem = SetDb__mem - ; union = SetDb__union - ; intersect = SetDb__intersect }; \ No newline at end of file From 6e9f054bd7e854c2f5ac7a9e8ef96d4ee4edc4e9 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Mar 2019 06:56:00 -0700 Subject: [PATCH 46/52] Makefile: module output files in a separate directory --- samples/collections/Makefile | 71 +++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/samples/collections/Makefile b/samples/collections/Makefile index 50dc01b8c23..834b6f3178a 100644 --- a/samples/collections/Makefile +++ b/samples/collections/Makefile @@ -1,62 +1,73 @@ ASC=../../src/asc +OUTDIR=out MODULE_NAME="\x1b[1;32mModule:\x1b[1;34m" BEGIN="\x1b[0;1mBegin...\x1b[0m" DONE="\x1b[1mDone.\n---------------------------------------------------\x1b[0m" +# Add new module targets here: +MODULES=\ + List \ + ListTest \ + Trie \ + Set \ + SetDb \ + SetDbTest \ + ProduceExchange \ + +OUTFILES=$(addsuffix .out, $(MODULES)) + +OUTPATHS=$(addprefix $(OUTDIR)/, $(OUTFILES)) + .PHONY: default all clean default: all -all: \ - List.out \ - ListTest.out \ - Trie.out \ - Set.out \ - SetDb.out \ - SetDbTest.out \ - ProduceExchange.out \ +all: $(OUTPATHS) clean: - rm -f *.out + rm -rf $(OUTDIR) + +$(OUTDIR): + mkdir $(OUTDIR) -List.out: list.as - @echo $(MODULE_NAME) $(basename $@) +$(OUTDIR)/List.out: $(OUTDIR) list.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) - $(ASC) -r $^ > $@ + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -ListTest.out: list.as listTest.as - @echo $(MODULE_NAME) $(basename $@) +$(OUTDIR)/ListTest.out: list.as listTest.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) - $(ASC) -r $^ > $@ + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -Trie.out: list.as trie.as - @echo $(MODULE_NAME) $(basename $@) +$(OUTDIR)/Trie.out: list.as trie.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) - $(ASC) -r $^ > $@ + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -Set.out: list.as trie.as set.as - @echo $(MODULE_NAME) $(basename $@) +$(OUTDIR)/Set.out: list.as trie.as set.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) - $(ASC) -r $^ > $@ + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -SetDb.out: list.as trie.as set.as setDb.as - @echo $(MODULE_NAME) $(basename $@) +$(OUTDIR)/SetDb.out: list.as trie.as set.as setDb.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) - $(ASC) -r $^ > $@ + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -SetDbTest.out: list.as trie.as set.as setDb.as setDbTest.as - @echo $(MODULE_NAME) $(basename $@) +$(OUTDIR)/SetDbTest.out: list.as trie.as set.as setDb.as setDbTest.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) - $(ASC) -r $^ > $@ + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -ProduceExchange.out: list.as trie.as produceExchange.as - @echo $(MODULE_NAME) $(basename $@) +$(OUTDIR)/ProduceExchange.out: list.as trie.as produceExchange.as + @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) - $(ASC) -r $^ > $@ + $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) From 4ec6fc4766cc806be5f0df9c6a9dacf1e28036fb Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Mar 2019 07:46:01 -0700 Subject: [PATCH 47/52] Makefile nits; more futzing for later --- samples/collections/Makefile | 45 ++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/samples/collections/Makefile b/samples/collections/Makefile index 834b6f3178a..dba43f011e6 100644 --- a/samples/collections/Makefile +++ b/samples/collections/Makefile @@ -1,8 +1,13 @@ ASC=../../src/asc OUTDIR=out + +## VT100 stuff +HRULE="\x1b[2;34m----------------------------------------------------------------\x1b[0m" MODULE_NAME="\x1b[1;32mModule:\x1b[1;34m" BEGIN="\x1b[0;1mBegin...\x1b[0m" -DONE="\x1b[1mDone.\n---------------------------------------------------\x1b[0m" +DONE="\x1b[1mDone.\n"$(HRULE) +MODULE_NAME_COLOR="\x1b[0;1;34m" +NO_COLOR="\x1b[0m" # Add new module targets here: MODULES=\ @@ -14,21 +19,28 @@ MODULES=\ SetDbTest \ ProduceExchange \ + OUTFILES=$(addsuffix .out, $(MODULES)) OUTPATHS=$(addprefix $(OUTDIR)/, $(OUTFILES)) -.PHONY: default all clean +.PHONY: default all clean startmsg default: all -all: $(OUTPATHS) +startmsg: + @echo Begin build: $(MODULE_NAME_COLOR)$(MODULES)$(NO_COLOR)... + @echo $(HRULE) + +all: $(OUTDIR) startmsg $(OUTPATHS) + @echo Build done : $(MODULE_NAME_COLOR)$(MODULES)$(NO_COLOR) clean: rm -rf $(OUTDIR) $(OUTDIR): - mkdir $(OUTDIR) + @mkdir $(OUTDIR) + $(OUTDIR)/List.out: $(OUTDIR) list.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @@ -71,3 +83,28 @@ $(OUTDIR)/ProduceExchange.out: list.as trie.as produceExchange.as @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) + + +######################################################################################### +# TODO(Matthew): Figure out why this "compressed" version of the rules doesn't work. + +# $(OUTDIR)/List.out: $(OUTDIR) list.as ; @$(doModule) + +# $(OUTDIR)/ListTest.out: $(OUTDIR) list.as listTest.as ; @$(doModule) + +# $(OUTDIR)/Trie.out: $(OUTDIR) list.as trie.as ; @$(doModule) + +# $(OUTDIR)/Set.out: $(OUTDIR) list.as trie.as set.as ; @$(doModule) + +# $(OUTDIR)/SetDb.out: $(OUTDIR) list.as trie.as set.as setDb.as ; @$(doModule) + +# $(OUTDIR)/SetDbTest.out: $(OUTDIR) list.as trie.as set.as setDb.as setDbTest.as ; @$(doModule) + +# $(OUTDIR)/ProduceExchange.out: $(OUTDIR) list.as trie.as produceExchange.as ; @$(doModule) + +# define doModule = +# @echo $(MODULE_NAME) $(basename $(notdir $@)) +# @echo $(BEGIN) +# $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ +# @echo $(DONE) +# endef From 8953b59d4ab23e6c367bf1dadc42771b08ac1be4 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Mar 2019 07:47:35 -0700 Subject: [PATCH 48/52] move collections in source tree --- {samples/collections => stdlib}/Makefile | 0 {samples/collections => stdlib}/README.md | 0 {samples/collections => stdlib}/list.as | 0 {samples/collections => stdlib}/listTest.as | 0 {samples/collections => stdlib}/nonCriticalPath/stream.as | 0 {samples/collections => stdlib}/nonCriticalPath/thunk.as | 0 {samples/collections => stdlib}/produceExchange.as | 0 {samples/collections => stdlib}/set.as | 0 {samples/collections => stdlib}/setDb.as | 0 {samples/collections => stdlib}/setDbTest.as | 0 {samples/collections => stdlib}/trie.as | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {samples/collections => stdlib}/Makefile (100%) rename {samples/collections => stdlib}/README.md (100%) rename {samples/collections => stdlib}/list.as (100%) rename {samples/collections => stdlib}/listTest.as (100%) rename {samples/collections => stdlib}/nonCriticalPath/stream.as (100%) rename {samples/collections => stdlib}/nonCriticalPath/thunk.as (100%) rename {samples/collections => stdlib}/produceExchange.as (100%) rename {samples/collections => stdlib}/set.as (100%) rename {samples/collections => stdlib}/setDb.as (100%) rename {samples/collections => stdlib}/setDbTest.as (100%) rename {samples/collections => stdlib}/trie.as (100%) diff --git a/samples/collections/Makefile b/stdlib/Makefile similarity index 100% rename from samples/collections/Makefile rename to stdlib/Makefile diff --git a/samples/collections/README.md b/stdlib/README.md similarity index 100% rename from samples/collections/README.md rename to stdlib/README.md diff --git a/samples/collections/list.as b/stdlib/list.as similarity index 100% rename from samples/collections/list.as rename to stdlib/list.as diff --git a/samples/collections/listTest.as b/stdlib/listTest.as similarity index 100% rename from samples/collections/listTest.as rename to stdlib/listTest.as diff --git a/samples/collections/nonCriticalPath/stream.as b/stdlib/nonCriticalPath/stream.as similarity index 100% rename from samples/collections/nonCriticalPath/stream.as rename to stdlib/nonCriticalPath/stream.as diff --git a/samples/collections/nonCriticalPath/thunk.as b/stdlib/nonCriticalPath/thunk.as similarity index 100% rename from samples/collections/nonCriticalPath/thunk.as rename to stdlib/nonCriticalPath/thunk.as diff --git a/samples/collections/produceExchange.as b/stdlib/produceExchange.as similarity index 100% rename from samples/collections/produceExchange.as rename to stdlib/produceExchange.as diff --git a/samples/collections/set.as b/stdlib/set.as similarity index 100% rename from samples/collections/set.as rename to stdlib/set.as diff --git a/samples/collections/setDb.as b/stdlib/setDb.as similarity index 100% rename from samples/collections/setDb.as rename to stdlib/setDb.as diff --git a/samples/collections/setDbTest.as b/stdlib/setDbTest.as similarity index 100% rename from samples/collections/setDbTest.as rename to stdlib/setDbTest.as diff --git a/samples/collections/trie.as b/stdlib/trie.as similarity index 100% rename from samples/collections/trie.as rename to stdlib/trie.as From 165d0884e431460b8daf83d5fd6a714f9dcc1a03 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Mar 2019 07:48:54 -0700 Subject: [PATCH 49/52] fix Makefile, post move --- stdlib/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/Makefile b/stdlib/Makefile index dba43f011e6..2afdc49d003 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -1,4 +1,4 @@ -ASC=../../src/asc +ASC=../src/asc OUTDIR=out ## VT100 stuff From 5274e4c023eccac0315de14c3f81c10d9d4f59a9 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Mar 2019 07:52:44 -0700 Subject: [PATCH 50/52] move produce exchange to 'examples' subdir --- stdlib/Makefile | 12 ++++++------ stdlib/{ => examples}/produceExchange.as | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename stdlib/{ => examples}/produceExchange.as (100%) diff --git a/stdlib/Makefile b/stdlib/Makefile index 2afdc49d003..53640be3f45 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -48,37 +48,37 @@ $(OUTDIR)/List.out: $(OUTDIR) list.as $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/ListTest.out: list.as listTest.as +$(OUTDIR)/ListTest.out: $(OUTDIR) list.as listTest.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/Trie.out: list.as trie.as +$(OUTDIR)/Trie.out: $(OUTDIR) list.as trie.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/Set.out: list.as trie.as set.as +$(OUTDIR)/Set.out: $(OUTDIR) list.as trie.as set.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/SetDb.out: list.as trie.as set.as setDb.as +$(OUTDIR)/SetDb.out: $(OUTDIR) list.as trie.as set.as setDb.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ @echo $(DONE) -$(OUTDIR)/SetDbTest.out: list.as trie.as set.as setDb.as setDbTest.as +$(OUTDIR)/SetDbTest.out: $(OUTDIR) list.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: list.as trie.as produceExchange.as +$(OUTDIR)/ProduceExchange.out: $(OUTDIR) list.as trie.as examples/produceExchange.as @echo $(MODULE_NAME) $(basename $(notdir $@)) @echo $(BEGIN) $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ diff --git a/stdlib/produceExchange.as b/stdlib/examples/produceExchange.as similarity index 100% rename from stdlib/produceExchange.as rename to stdlib/examples/produceExchange.as From 7049305776f639ba392207299d919f038f0b3eec Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Mar 2019 09:17:58 -0700 Subject: [PATCH 51/52] Makefile nits --- stdlib/.gitignore | 1 + stdlib/Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 stdlib/.gitignore diff --git a/stdlib/.gitignore b/stdlib/.gitignore new file mode 100644 index 00000000000..c1d18d8a8b2 --- /dev/null +++ b/stdlib/.gitignore @@ -0,0 +1 @@ +_out diff --git a/stdlib/Makefile b/stdlib/Makefile index 53640be3f45..7f51ebb7cfb 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -1,5 +1,5 @@ ASC=../src/asc -OUTDIR=out +OUTDIR=_out ## VT100 stuff HRULE="\x1b[2;34m----------------------------------------------------------------\x1b[0m" From f580a970ee65ec334ec2458315cffb785f1cd406 Mon Sep 17 00:00:00 2001 From: Matthew Hammer Date: Thu, 7 Mar 2019 09:24:45 -0700 Subject: [PATCH 52/52] add stdlib dir to other Makefile, and nix derivation --- default.nix | 6 ++++++ src/Makefile | 1 + 2 files changed, 7 insertions(+) diff --git a/default.nix b/default.nix index 4bad62c8ac3..ac38a76f615 100644 --- a/default.nix +++ b/default.nix @@ -98,6 +98,11 @@ rec { "test/.*.sh" "samples/" "samples/.*" + "stdlib/" + "stdlib/.*Makefile.*" + "stdlib/.*.as" + "stdlib/examples/" + "stdlib/examples/.*.as" ]; buildInputs = @@ -112,6 +117,7 @@ rec { buildPhase = '' patchShebangs . asc --version + make -C stdlib ASC=asc all make -C samples ASC=asc all make -C test/run VERBOSE=1 ASC=asc all make -C test/fail VERBOSE=1 ASC=asc all diff --git a/src/Makefile b/src/Makefile index fd811340b00..bfcd4a47372 100644 --- a/src/Makefile +++ b/src/Makefile @@ -49,6 +49,7 @@ clean: $(MAKE) -C ../test clean test: $(NAME) + $(MAKE) -C ../stdlib ASC=$(ASC) all $(MAKE) -C ../test ASC=$(ASC) all $(MAKE) -C ../samples ASC=$(ASC) all