packages feed

swarm-0.7.0.0: example/recursive-containers.sw

/*******************************************************************/
/*                           CONTAINERS                            */
/*******************************************************************/
// This file has an example implementation of lists, sets and maps
// (a.k.a dictionaries) using recursive types.
//
// The implementation uses a Red-Black tree balanced binary tree
// with projection (k -> a) to get sets and dictionaries (compare
// only first element of tuple). It is based on Haskell code in:
// https://abhiroop.github.io/Haskell-Red-Black-Tree/

// Flipped versions of match and case come in handy for
// writing functions where we want to immediately match or case on
// the argument.  e.g. instead of  `\p. match p \x. \y. ...` we can
// write `λmatch \x. \y. ...`

def λmatch : (a -> b -> c) -> (a * b) -> c =
  \f. \p. match p f
end

def λcase : (a -> c) -> (b -> c) -> (a + b) -> c =
  \f. \g. \s. case s f g
end

/*******************************************************************/
/*                    (TYPE) DECLARATIONS                          */
/*******************************************************************/

tydef Maybe a = Unit + a end

tydef List a = rec l. Maybe (a * l) end

tydef RBTree k = rec b. Maybe [red: Bool, k: k, l: b, r: b] end

tydef ISet s a =
  [ empty: s
  , insert: a -> s -> s
  , delete: a -> s -> s
  , contains: a -> s -> Bool
  , from_list: List a -> s
  , pretty: s -> Text
  ]
end

def undefined_set : any -> ISet any s =\empty.
    [empty=empty, insert=\x.undefined, delete=\x.undefined, contains=\x.undefined, from_list=\x.undefined, pretty=\x.undefined]
end

tydef IDict d k v =
  [ empty: d
  , insert: k -> v -> d -> d
  , delete: k -> d -> d
  , get: k -> d -> Maybe v
  , contains: k -> d -> Bool
  , pretty: d -> Text
  ]
end

def undefined_dict : any -> IDict any k v =\empty.
    [empty=empty, insert=\x.undefined, delete=\x.undefined, get=\x.undefined, contains=\x.undefined, pretty=\x.undefined]
end

tydef FlatSet a = List a end
def flat_set : ISet (FlatSet a) a = undefined_set (inl ()) end

tydef FlatDict k v = List (k * v) end
def flat_dict : IDict (FlatDict k v) k v = undefined_dict (inl ()) end

tydef Set k = RBTree k end
def tree_set : ISet (Set a) a = undefined_set (inl ()) end

tydef Dict k v = RBTree (k * v) end
def tree_dict : IDict (Dict k v) k v = undefined_dict (inl ()) end

/*******************************************************************/
/*                              MAYBE                              */
/*******************************************************************/

def mmap : (a -> b) -> Maybe a -> Maybe b = \f. λcase (\_. inl ()) (\a. inr (f a)) end

/*******************************************************************/
/*                              LISTS                              */
/*******************************************************************/

def emptyL : List a = inl () end
def cons : a -> List a -> List a = \a.\l. inr (a, l) end
def pureL : a -> List a = \a. cons a emptyL end

def foldr : (a -> b -> b) -> b -> List a -> b = \f. \z. λcase
  (\_. z)
  (λmatch \hd. \tl. f hd (foldr f z tl))
end

def foldl : (b -> a -> b) -> b -> List a -> b = \f. \z. λcase
  (\_. z)
  (λmatch \hd. \tl. (foldl f (f z hd) tl))
end

def flip : (a -> b -> c) -> (b -> a -> c) = \f. (\b.\a.f a b) end

def map : (a -> b) -> List a -> List b = \f.
  foldr (\y. cons (f y)) emptyL
end

def appendL : List a -> List a -> List a = \xs. \ys.
  foldr cons ys xs
end

def lengthL : List a -> Int = foldl (\acc.\_. acc + 1) 0 end

def safeGetL : Int -> List a -> Maybe a = \i. λcase
  (\_. inl ())
  (λmatch \hd. \tl. if (i <= 0) {inr hd} {safeGetL (i - 1) tl})
end

def getL : Int -> List a -> a = \i.\l.
  case (safeGetL i l) (\_. fail $ "INDEX " ++ format i ++ " OUT OF BOUNDS!") (\a. a)
end

def formatL : List a -> Text = \l.
 let f : List a -> Text = λcase (\_. "]") (λmatch \hd. \tl. ", " ++ format hd ++ f tl)
 in case l (\_. "[]") (λmatch \hd. \tl. "[" ++ format hd ++ f tl)
end

/*******************************************************************/
/*                          ORDERED LISTS                          */
/*******************************************************************/

// get from ordered list
def getKeyL : (a -> k) -> k -> List a -> Maybe a = \p. \x. λcase
  (\_. inl ())
  (λmatch \hd. \tl.
    let nx = p hd in
    if (nx < x) {
      getKeyL p x tl
    } {
      if (nx > x) {
        inl ()
      } {
        inr hd
      }
    }
  )
end

def containsKeyL : (a -> k) -> k -> List a -> Bool = \p.\x.\l.
  case (getKeyL p x l) (\_. false) (\_. true)
end

// insert into ordered list - replaces elements, like set
def insertKeyL : (a -> k) -> a -> List a -> List a = \p.\y.\l. case l
  (\_. pureL y)
  (λmatch \hd. \tl.
    let nx = p hd in
    let x = p y in
    if (nx == x) {
      inr (y, tl)
    } {
      if (nx > x) {
        cons y l
      } {
        cons hd (insertKeyL p y tl)
      }
    }
  )
end

// delete in ordered list
def deleteKeyL : (a -> k) -> k -> List a -> List a = \p.\x. \l. case l
  (\_. emptyL)
  (λmatch \hd. \tl.
    let nx = p hd in
    if (nx == x) {
      tl
    } {
      if (nx > x) {
        l
      } {
        cons hd (deleteKeyL p x tl)
      }
    }
  )
end

tydef FlatSet a = List a end

def flat_set : ISet (FlatSet a) a =
  [ empty=emptyL
  , insert=insertKeyL (\x.x)
  , delete=deleteKeyL (\x.x)
  , contains=containsKeyL (\x.x)
  , from_list=foldl (flip $ insertKeyL (\x.x)) emptyL
  , pretty=formatL
  ]
end

tydef FlatDict k v = List (k * v) end

def fst = λmatch \a. \_. a end
def snd = λmatch \_. \b. b end

def flat_dict : IDict (FlatDict k v) k v =
  [ empty=emptyL
  , insert=\k.\v. insertKeyL fst (k,v)
  , delete=deleteKeyL fst
  , get=\k.\d. mmap snd (getKeyL fst k d)
  , contains=containsKeyL fst
  , pretty=\d.
    let p : (k * v) -> Text = λmatch \k. \v. format k ++ ": " ++ format v in
    let f : List (k * v) -> Text = λcase (\_. "}") (λmatch \hd. \tl. ", " ++ p hd ++ f tl)
    in case d (\_. "{}") (λmatch \hd. \tl. "{" ++ p hd ++ f tl)
  ]
end

/*******************************************************************/
/*                         RED BLACK TREE                          */
/*******************************************************************/


// equirecursive types allow us to get the node type without mutual recursion
tydef RBNode k = rec n. [red: Bool, k: k, l: Maybe n, r: Maybe n] end

def emptyT : RBTree k = inl () end

def getT : (k -> a) -> a -> RBTree k -> Maybe k = \p. \x. \t. case t
  (\_. inl ())
  (\n .
    let nx = p n.k in
    if (x < nx) {
        getT p x n.l
    } {
      if (x > nx) {
          getT p x n.r
      } {
        inr n.k
      }
    }
  )
end

def containsT : (k -> a) -> a -> RBTree k -> Bool = \p.\x.\t. getT p x t != inl () end

def inorder : RBTree k -> List k = \t. case t
  (\_. inl ())
  (\n. appendL (inorder n.l) (appendL (pureL n.k) $ inorder n.r))
end

def formatT : RBTree k -> Text = \t. case t
  (\_. "N")
  (\n.
    "(T "
    ++ if n.red {"R "} {"B "}
    ++ formatT n.l ++ " " ++ format n.k ++ " " ++ formatT n.r ++ ")"
  )
end

def indent_ = \c.\i. if (i <= 0) {" "} {c ++ indent_ c (i - 1)} end
def indent = indent_ " " end

def debugT : RBTree k -> Cmd Unit =
  let d : Text -> Text -> Text -> RBTree k -> Cmd Unit = \i.\li.\ri.\t. case t
    (\_. log $ i ++ "+ N")
    (\n.
      d (i ++ li) "  " "| " n.l;
      log $ i ++ "+ " ++ if n.red {"R "} {"B "} ++ format n.k;
      d (i ++ ri) "| " "  " n.r;
    )
  in d "" "" ""
end

/*
balance B (T R (T R a x0 b) x1 c) x2 d = T R (T B a x0 b) x1 (T B c x2 d) -- case 1: LL red
balance B (T R a x0 (T R b x1 c)) x2 d = T R (T B a x0 b) x1 (T B c x2 d) -- case 2: LR red
balance B a x0 (T R (T R b x1 c) x2 d) = T R (T B a x0 b) x1 (T B c x2 d) -- case 3: RL red
balance B a x0 (T R b x1 (T R c x2 d)) = T R (T B a x0 b) x1 (T B c x2 d) -- case 4: RR red
balance color a x b = T color a x b -- case 5
*/
def balanceT : RBNode k -> RBTree k = \t.
  let balanced : [ a: RBTree k, x0: k, b: RBTree k,   x1: k,   c: RBTree k, x2: k, d: RBTree k] -> RBTree k
    = \v.
    inr [red=true, l=inr [red=false, l=v.a, k=v.x0, r=v.b], k=v.x1, r=inr [red=false, l=v.c, k=v.x2, r=v.d]]
  in let case5 : RBNode k -> RBTree k =
    inr  // just id - TODO: inline?
  in let balanceRR : RBNode k -> RBNode k -> RBTree k = \t.\rn.
    case rn.r (\_. case5 t) (\rrn.
      if rrn.red {
        balanced [a=t.l, x0=t.k, b=rn.l, x1=rn.k, c=rrn.l, x2=rrn.k, d=rrn.r]
      } {
        case5 t
      }
    )
  in let balanceRL : RBNode k -> RBNode k -> RBTree k = \t.\rn.
    case rn.l (\_. balanceRR t rn) (\rln.
      if rln.red {
        balanced [a=t.l, x0=t.k, b=rln.l, x1=rln.k, c=rln.r, x2=rn.k, d=rn.r]
      } {
        balanceRR t rn
      }
    )
  in let balanceR : RBNode k -> RBTree k = \t.
    case t.r (\_.
      case5 t
    ) (\lr.
      if lr.red {
        balanceRL t lr
      } {
        case5 t
      }
    )
  in let balanceLR : RBNode k -> RBNode k -> RBTree k = \t.\ln.
    case ln.r (\_. balanceR t) (\lrn.
      if lrn.red {
        balanced [a=ln.l, x0=ln.k, b=lrn.l, x1=lrn.k, c=lrn.r, x2=t.k, d=t.r]
      } {
        balanceR t
      }
    )
  in let balanceLL : RBNode k -> RBNode k -> RBTree k = \t.\ln.
    case ln.l (\_. balanceLR t ln) (\lln.
      if lln.red {
        balanced [a=lln.l, x0=lln.k, b=lln.r, x1=ln.k, c=ln.r, x2=t.k, d=t.r]
      } {
        balanceLR t ln
      }
    )
  in let balanceL : RBNode k -> RBTree k = \t.
    case t.l (\_.
      balanceR t
    ) (\ln.
      if ln.red {
        balanceLL t ln
      } {
        balanceR t
      }
    )
  in if t.red {
    case5 t
  } {
    balanceL t
  }
end

def insertT : (k -> a) -> k -> RBTree k -> RBTree k = \p.\x.\t.
  let ins : (k -> a) -> k -> RBTree k -> RBTree k =\p.\x.\t. case t
    (\_.
      inr [red=true, k=x, l=inl (), r=inl ()]
    )
    (\n.
      if (p x == p n.k) { inr [red=n.red, k=x, l=n.l, r=n.r] } {
        if (x < n.k) {
            balanceT [red=n.red, k=n.k, l=ins p x n.l, r=n.r]
        } {
            balanceT [red=n.red, k=n.k, l=n.l, r=ins p x n.r]
        }
      }
    )
  in let makeBlack : RBTree k -> RBTree k = \t. case t
    (\_. fail "makeBlack will always be called on nonempty")
    (\n. inr [red=false, k=n.k, l=n.l, r=n.r] )
  in makeBlack $ ins p x t
end

def deleteT : (k -> a) -> a -> RBTree k -> RBTree k = \p.\x.\t.
  // ------------------------------------------------------------------------------------------------------------
  tydef Del k a = (k -> a) -> a -> RBTree k -> RBTree k end
  // ------------------------------------------------------------------------------------------------------------
  let makeBlack : RBTree k -> RBTree k = \t.
    case t (\_. t) (\n. inr [red=false, k=n.k, l=n.l, r=n.r])
  in let makeRed : RBNode k -> RBTree k = \n.
    inr [red=true, k=n.k, l=n.l, r=n.r]
  // ------------------------------------------------------------------------------------------------------------
  /*
  balL (T B (T R t1 x t2) y t3) = T R (T B t1 x t2) y t3
  balL (T B t1 y (T B t2 z t3)) = balance' (T B t1 y (T R t2 z t3))
  balL (T B t1 y (T R (T B t2 u t3) z t4@(T B l value r)))
    = T R
      (T B t1 y t2)
      u
      (balance' (T B t3 z
        (T R l value r)))
  */
  in let balL : RBNode k -> RBTree k = \n.
    let balL2 : RBNode k -> RBNode k -> RBTree k = \n.\rn.
      case rn.l (\_. inr n) (\rln.
        if rln.red {inr n} {
          case rn.r (\_. inr n) (\rrn.
            if rrn.red {inr n} {
              inr [red=true,
                   l=inr [red=false, l=n.l, k=n.k, r=rln.l],
                   k=rln.k,
                   r=balanceT [red=false, l=rln.r, k=rn.k, r=inr [red=true, l=rrn.l, k=rrn.k, r=rrn.r]]]
            }
          )
        }
      )
    in let balL1 : RBNode k -> RBTree k = \n.
      case n.r (\_. inr n) (\rn.
        if rn.red {balL2 n rn} {balanceT [red=false, k=n.k, l=n.l, r=makeRed rn]}
      )
    in case n.l (\_. balL1 n) (\ln. if ln.red {inr [red=true, k=n.k, l=makeBlack n.l, r=n.r]} {balL1 n})
  // ------------------------------------------------------------------------------------------------------------
  /*
  balR (T B t1 y (T R t2 x t3)) = T R t1 y (T B t2 x t3)
  balR (T B (T B t1 z t2) y t3) = balance' (T B (T R t1 z t2) y t3)
  balR (T B
    ln@(T R
      lln@(T B lll llk llr)
      lk
      lrn@(T B lrl lrk lrr))
    k
    rn)
    =
    T R
      (balance' (T B (T R lll llk llr) lk lrl))
      lrk
      (T B lrr k rn)
  */
  in let balR : RBNode k -> RBTree k = \n.
    let balR2 : RBNode k -> RBNode k -> RBTree k = \n.\ln.
      case ln.l (\_. inr n) (\lln.
        if lln.red {inr n} {
          case ln.r (\_. inr n) (\lrn.
            if lrn.red {inr n} {
              inr [red=true,
                   l=balanceT [red=false, l=inr [red=true, l=lln.l, k=lln.k, r=lln.r], k=ln.k, r=lrn.l],
                   k=lrn.k,
                   r=inr [red=false, l=lrn.r, k=n.k, r=n.r]]
            }
          )
        }
      )
    in let balR1 : RBNode k -> RBTree k = \n.
      case n.l (\_. inr n) (\ln.
        if ln.red {balR2 n ln} {balanceT [red=false, k=n.k, l=makeRed ln, r=n.r]}
      )
    in case n.r (\_. balR1 n) (\rn. if rn.red {inr [red=true, k=n.k, l=n.l, r=makeBlack n.r]} {balR1 n})
  // ------------------------------------------------------------------------------------------------------------
  in let delL : Del k a -> (k -> a) -> a -> RBNode k -> RBTree k = \del.\p.\x.\n.
    let dl = [red=n.red, k=n.k, l=del p x n.l, r=n.r] in
    if n.red { inr dl } { balL dl }
  // ------------------------------------------------------------------------------------------------------------
  in let delR : Del k a -> (k -> a) -> a -> RBNode k -> RBTree k = \del.\p.\x.\n.
    let dr = [red=n.red, k=n.k, l=n.l, r=del p x n.r] in
    if n.red { inr dr } { balR dr }
  // ------------------------------------------------------------------------------------------------------------
  /*
  fuse E t = t
  fuse t E = t
  fuse t1@(T B _ _ _) (T R t3 y t4) = T R (fuse t1 t3) y t4
  fuse (T R t1 x t2) t3@(T B _ _ _) = T R t1 x (fuse t2 t3)
  fuse (T R t1 x t2) (T R t3 y t4)  =
    let s = fuse t2 t3
    in case s of
        (T R s1 z s2) -> (T R (T R t1 x s1) z (T R s2 y t4))
        (T B _ _ _)   -> (T R t1 x (T R s y t4))
  fuse (T B t1 x t2) (T B t3 y t4)  =
    let s = fuse t2 t3
    in case s of
        (T R s1 z s2) -> (T R (T B t1 x s1) z (T B s2 y t4))
        (T B s1 z s2) -> balL (T B t1 x (T B s y t4))
  */
  in let fuse: RBTree k -> RBTree k -> RBTree k = \l.\r.
    case l (\_. r) (\ln. case r (\_. l) (\rn.
      if ln.red {
        if rn.red {
          /* RED RED */
          let s = fuse ln.r rn.l in
          let b_case = {inr [red=true, l=ln.l, k=ln.k, r=inr [red=true, l=s, k=rn.k, r=rn.r]]} in
          case s (\_. force b_case) (\sn.
            if sn.red {
              inr [red=true, l=inr [red=true, l=ln.l, k=ln.k, r=sn.l], k=sn.k, r=inr [red=true, l=sn.r, k=rn.k, r=rn.r]]
            } {
              force b_case
            }
          )
        } {
          /* RED BLACK */
          inr [red=true, l=ln.l, k=ln.k, r=fuse ln.r (inr rn)]
        }
      } {
        if rn.red {
          /* BLACK RED */
          inr [red=true, l=fuse (inr ln) rn.l, k=rn.k, r=rn.r]
        } {
          /* BLACK BLACK */
          let s = fuse ln.r rn.l in
          let b_case = {balL [red=false, l=ln.l, k=ln.k, r=inr [red=false, l=s, k=rn.k, r=rn.r]]} in
          case s (\_. force b_case) (\sn.
            if sn.red {
              inr [red=true, l=inr [red=false, l=ln.l, k=ln.k, r=sn.l], k=sn.k, r=inr [red=false, l=sn.r, k=rn.k, r=rn.r]]
            } {
              force b_case
            }
          )
        }
      }
    ))
  // ------------------------------------------------------------------------------------------------------------
  in let del : (k -> a) -> a -> RBTree k -> RBTree k = \p.\x.\t.
    case t (\_.t) (\n.
      if (p n.k == x) {fuse n.l n.r} {
        if (x < p n.k) {delL del p x n} {delR del p x n}
      }
    )
  // ------------------------------------------------------------------------------------------------------------
  in makeBlack $ del p x t
end

/*******************************************************************/
/*                           DICTIONARY                            */
/*******************************************************************/

tydef Dict k v = RBTree (k * v) end

def tree_dict : IDict (Dict k v) k v =
 [ empty = emptyT
 , get  = \k.\d. mmap snd (getT fst k d)
 , contains = containsT fst
 , insert = \k.\v. insertT fst (k, v)
 , delete = \k. deleteT fst k
 , pretty = \d. flat_dict.pretty (inorder d)
 ]
end

/*******************************************************************/
/*                              SET                                */
/*******************************************************************/

tydef Set k = RBTree k end

def tree_set : ISet (Set a) a =
  [ empty=emptyT
  , insert=insertT (\x.x)
  , delete=deleteT (\x.x)
  , contains=containsT (\x.x)
  , from_list=foldl (flip $ insertT (\x.x)) emptyT
  , pretty=\s. formatL $ inorder s
  ]
end

/*******************************************************************/
/*                           UNIT TESTS                            */
/*******************************************************************/

def assert = \i. \b. \m. if b {log $ indent i ++ "OK: " ++ m} {log "FAIL:"; fail m} end

def assert_eq
  = \i. \exp. \act. \m.
  if (exp == act) {log $ indent i ++ "OK: " ++ m} {
    log (indent i ++ "FAIL: expected " ++ format exp ++ " actual " ++ format act);
    fail m
  }
end

def group = \i. \m. \tests.
  log $ indent i ++ "START " ++ m ++ ":";
  tests $ i + 1;
  pure ()
end

def test_empty: Int -> Cmd Unit = \i.
  let d = tree_dict in
  group i "EMPTY TESTS" (\i.
    assert_eq i (inl ()) (d.get 0 $ d.empty) "no element should be present in an empty tree";
    assert i (not $ d.contains 0 $ d.empty) "empty tree should not contain any elements";
  )
end

def test_insert_1235: ISet s Int -> Int -> Cmd Unit =\s.\i.
    let expected = cons 1 $ cons 2 $ cons 3 $ cons 5 emptyL in
    let actual = s.insert 2 $ s.insert 5 $ s.insert 1 $ s.insert 3 s.empty in
    assert_eq i (formatL expected) (formatL actual) "insertKeyL should insert in order";
    assert i (not $ s.contains 0 actual) "not contains 0 [1,2,3,5]";
    assert i (s.contains 1 actual) "contains 1 [1,2,3,5]";
    assert i (s.contains 2 actual) "contains 2 [1,2,3,5]";
    assert i (s.contains 3 actual) "contains 3 [1,2,3,5]";
    assert i (not $ s.contains 4 actual) "not contains 4 [1,2,3,5]";
    assert i (s.contains 5 actual) "contains 5 [1,2,3,5]";
    assert i (not $ s.contains 6 actual) "not contains 6 [1,2,3,5]";
end

def test_ordered_list : Int -> Cmd Unit = \i.
  let s = flat_set in
  group i "ORDERED LIST TESTS" (test_insert_1235 flat_set)
end

def test_insert: Int -> Cmd Unit = \i.
  let d = tree_dict in
  let s = tree_set in
  group i "INSERT TESTS" (\i.
    group i "test {0: 1}" (\i.
      let tree0 = d.insert 0 1 d.empty in
      assert i (d.empty != tree0) "nonempty tree should not be empty";
      assert_eq i (inr 1) (d.get 0 $ tree0) "one element should be present in a one element tree";
      assert i (d.contains 0 $ tree0) "one element tree should contain that elements";
      assert i (not $ d.contains 1 $ tree0) "one element tree should contain only that elements";
    );
    group i "test {0: 1, 2: 3}" (\i.
      let tree02 = d.insert 2 3 $ d.insert 0 1 d.empty in
      assert_eq i (inr 1) (d.get 0 $ tree02) "get 0 {0: 1, 2: 3} == 1";
      assert_eq i (inr 3) (d.get 2 $ tree02) "get 2 {0: 1, 2: 3} == 3";
    );
    group i "test {1,2,3,5}" (test_insert_1235 tree_set)
  );
end

def randomTestS: Int -> Set Int -> (Set Int -> Cmd a) -> Cmd (Set Int) = \i.\s.\test.
  if (i <= 0) {pure s} {
    x <- random 20;
    let ns = tree_set.insert x s in
    test ns;
    randomTestS (i - 1) ns test
  }
end

def delete_insert_prop: Int -> Set Int -> Cmd Unit = \i.\t.
  let s = tree_set in
  x <- random 20;
  let i_t = inorder t in
  let f_t = s.pretty t in
  if (not $ s.contains x t) {
    // log $ format x;
    assert_eq i
      (formatL i_t)
      (s.pretty $ s.delete x $ s.insert x t)
      (f_t ++ " == delete " ++ format x ++ " $ insert " ++ format x ++ " " ++ f_t)
  } { delete_insert_prop i t /* reroll */}
end

def delete_delete_prop: Int -> Set a -> Cmd Unit = \i.\t.
  let s = tree_set in
  let i_t = inorder t in
  ix <- random (lengthL i_t);
  let x = getL ix i_t in
  let ds = s.delete x t in
  let dds = s.delete x ds in
  let f_t = s.pretty t in
  let f_dx = "delete " ++ format x in
  assert_eq i (s.pretty ds) (s.pretty dds)
    (f_dx ++ f_t ++ " == " ++ f_dx ++ " $ " ++ f_dx ++ " " ++ f_t)
end

def test_delete: Int -> Cmd Unit = \i.
  group i "DELETE TESTS" (\i.
    randomTestS 15 tree_set.empty (\s.
      group i (tree_set.pretty s) (\i.
        delete_insert_prop i s;
        delete_delete_prop i s;
      )
    )
  )
end

def test_tree: Cmd Unit =
  group 0 "TREE TESTS" (\i.
    test_empty i;
    test_ordered_list i;
    test_insert i;
    test_delete i;
  );
  log "ALL TREE TESTS PASSED!"
end

/*******************************************************************/
/*                           BENCHMARKS                            */
/*******************************************************************/

def benchmark: Int -> s -> (s -> s) -> Cmd (Int * (Int * Int)) = \times.\s.\act.
  let min = \x.\y. if (x > y) {y} {x} in
  let max = \x.\y. if (x > y) {x} {y} in
  let runM: (Int * Maybe (Int * Int)) -> s -> Int -> Cmd (Int * Maybe (Int * Int)) = \acc.\s.\n.
    if (n <= 0) {
      pure acc
    } {
      t0 <- time;
      //log $ "START " ++ format t0;
      let ns = act s in
      t1 <- time;
      //log $ "END " ++ format t1;
      let t = t1 - t0 in
      //log $ format s ++ " " ++ format t ++ " ticks";
      match acc \acc1. \acc2.
      let lim = case acc2 (\_. (t, t)) (λmatch \l1. \l2. (min t l1, max t l2)) in
      runM ((acc1 + t), inr lim) ns (n - 1)
    } in
  //log "start run";
  res <- runM (0, inl ()) s times;
  //log "end run";
  match res \res1. \res2.
  let avg = res1 / times in
  let lim = case res2 (\_. fail "BENCHMARK NOT RUN") (\l.l) in
  pure (avg, lim)
end

def cmp_bench : Int -> Text -> (Int * (Int * Int)) -> Text -> (Int * (Int * Int)) -> Cmd Unit
 = \i.\base_name.\base_res.\new_name.\new_res.
  let formatLim = λmatch \a. \b. "(min " ++ format a ++ ", max " ++ format b ++ ")" in
  log $ indent i ++ "* " ++ base_name ++ ": "
    ++ format (fst base_res) ++ " ticks " ++ formatLim (snd base_res);

  let d = (fst new_res * 100) / fst base_res in
  let cmp = if (d > 100) {
     format (d - 100) ++ "% slower"
    } {
      format (100 - d) ++ "% faster"
    } in
  match new_res \res1. \res2.
  log $ indent i ++ "* " ++ new_name ++ ": "
    ++ format res1 ++ " ticks " ++ formatLim res2
    ++ " <- " ++ cmp;
end

// Get a list of random integers of given length and maximum element number
def gen_random_list: Int -> Int -> Cmd (List Int) =
  let gen = \acc.\n.\rlim.
    if (n <= 0) { pure acc } {
      x <- random rlim;
      gen (cons x acc) (n - 1) rlim
    }
  in gen emptyL
end

// Get a number of lists of random integers of given length and maximum element number
def gen_random_lists: Int -> Int -> Int -> Cmd (List (List Int)) = \m.\n.\rlim.
  let gen = \acc.\m.
    if (m <= 0) { pure acc } {
      l <- gen_random_list n rlim;
      gen (cons l acc) (m - 1)
    }
  in gen emptyL m
end


def bench_insert = \i.
  // Use the given function to construct (Flat)Set from the head of provided list of lists and return the tail
  let set_from_first_list : (List a -> s) -> List (List a) -> List (List a) =\from_list.\lls.
    case lls
      (\_. lls)
      // The 'ns = from_list ls' binding is unused, but intentional:
      // we want to benchmark how long it takes.
      (λmatch \ls. \nlls. let ns = from_list ls in nlls)
  in

  group i "INSERT BENCHMARK" (\i.
    group i "INSERT 10" (\i.
      let n = 10 in
      let m = 5 in
      lls10 <- gen_random_lists m n (3 * n);
      flat_res <- benchmark m lls10 (set_from_first_list flat_set.from_list);
      tree_res <- benchmark m lls10 (set_from_first_list tree_set.from_list);
      cmp_bench i "flat set" flat_res "tree set" tree_res
    );
    group i "INSERT 100" (\i.
      let n = 100 in
      let m = 3 in
      lls100 <- gen_random_lists m n (3 * n);
      flat_res <- benchmark m lls100 (set_from_first_list flat_set.from_list);
      tree_res <- benchmark m lls100 (set_from_first_list tree_set.from_list);
      cmp_bench i "flat set" flat_res "tree set" tree_res
    )
  )
end

def bench_contains = \i.
  let contains_int : s -> (Int -> s -> Bool) -> Int -> Int = \s.\contains.\n.
    let _ = contains n s in n + 1
  in
  group i "CONTAINS BENCHMARK" (\i.
    group i "CONTAINS 10" (\i.
      let n = 10 in
      let m = 3 * n in
      ls10 <- gen_random_list n m;
      let fls = flat_set.from_list ls10 in
      flat_res <- benchmark m 0 (contains_int fls flat_set.contains);
      let tls = tree_set.from_list ls10 in
      tree_res <- benchmark m 0 (contains_int tls tree_set.contains);
      cmp_bench i "flat set" flat_res "tree set" tree_res
    );
    group i "CONTAINS 100" (\i.
      let n = 100 in
      let m = 3 * n in
      ls100 <- gen_random_list n m;
      //log $ formatL ls100;
      let fls = flat_set.from_list ls100 in
      //log $ formatL fls;
      flat_res <- benchmark m 0 (contains_int fls flat_set.contains);
      let tls = tree_set.from_list ls100 in
      //log $ formatS tls;
      tree_res <- benchmark m 0 (contains_int tls tree_set.contains);
      cmp_bench i "flat set" flat_res "tree set" tree_res
    )
  )
end

def benchmark_tree: Cmd Unit =
  group 0 "TREE BENCHMARKS" (\i.
    bench_insert i;
    bench_contains i;
  );
  log "ALL BENCHMARKS DONE!"
end