Skip to content

Examples

Quicksort

Tree.coal

module Tree {

  type Tree<a>
    = Node(a, Tree<a>, Tree<a>)
    | Leaf

  fun flatten(tree) = 
    fold(tree) {
      | Node(y, @lhs, @rhs) =>
          lhs ++ y :: rhs
      | Leaf =>
          []
    }

}

Qsort.coal

module Qsort(sort) {

  import Tree(type Tree, flatten)
  import Coal.Combinators(always)
  import Number(_INT32_MAX)

  fun binary_search_tree(list : List<int32>) : Tree<int32> =
    fold(list, { min = 0, max = _INT32_MAX }) {
      | (p :: @g) =>
          fn(range) =>
            if (p > range.min && p <= range.max)
              then 
                Node
                  ( p 
                  , g({ min = range.min, max = p })
                  , g({ min = p, max = range.max })
                  )
              else
                g(range)
      | [] =>
          always(Leaf)
    }

  let sort = flatten << binary_search_tree

}

Main.coal

module Main {

  import Qsort(sort)
  import Coal.Monad(trait Monad, and_eval)
  import IO(return)

  import namespace IO

  fun print_all(ints : List<int32>) : IO<unit> =
    fold(ints) {
      | [] => 
          return()
      | m :: @next =>
          IO.println_int32(m) |. and_eval(next)
    }

  fun main() = 
    let xs = [105, 103, 234, 109, 107, 55, 102, 999, 101, 8, 106, 104]
    in
    print_all(sort(xs))

}

Basic I/O

This program demonstrates how to combine parsing and monadic pipelining to build a small interactive console application. Using IO operations composed through monadic combinators, the program asks the user for their name and then runs a simple number-guessing game

module Main {

  import Char(digit_to_int32)
  import Coal.Functor(trait Functor(map))
  import Coal.Monad(trait Monad, and_then)
  import IO(readln, println_string, random, return)
  import List(is_empty)
  import Number(double_to_int32)
  import String(to_list, remove_whitespace, int32_to_string)

  fun digits_to_unsigned(chars) : Option<int32> =
    if (is_empty(chars)) 
      then
        None
      else
        fold(chars, Some(0)) {
          | ch :: @go =>
              fn(t) =>
                match(digit_to_int32(ch)) {
                  | Some(n) =>
                      go(map(fn(s) => n + 10 * s, t))
                  | None =>
                      None
                }
          | [] =>
              fn(t) => t
        }

  fun parse_int32(str : string) : Option<int32> =
    digits_to_unsigned(to_list(remove_whitespace(str)))

  fun random_int32() : IO<int32> =
    random()
      |. and_then(fn(n) => return(double_to_int32(n * 10)))

  fun main() = 
    println_string("Enter your name")
      |. and_then(readln)
      |. and_then(fn(s) => println_string("Hello, " <> s <> "!"))
      |. and_then(random_int32)
      |. and_then(fn(rand) => 
           println_string("Guess what number I am thinking of")
             |. and_then(readln)
             |. and_then(fn(s) => 
                  match(parse_int32(s)) {
                    | Some(n) =>
                        if (n == rand) 
                          then println_string("You guessed right!")
                          else println_string("Sorry, the number I was thinking of was " <> int32_to_string(rand) <> ".")
                    | None =>
                        println_string("Not a number")
                  }
                )
         )

}

Do-notation

Using do-notation, we can refactor main in the above program, so that it instead becomes:

  fun main() = do {
    println_string("Enter your name");
    name <- readln();
    println_string("Hello, " <> name <> "!");

    rand <- random_int32();
    println_string("Guess what number I am thinking of");
    guess <- readln();

    println_string(
      match(parse_int32(guess)) {
        | Some(n) =>
            if (n == rand) 
              then "You guessed right!"
              else "Sorry, the number I was thinking of was " <> int32_to_string(rand) <> "."
        | None =>
            "Not a number"
      });
  }

Monads

These examples reimplement Haskell’s Reader, Writer, and State monads.

Reader

module Main {

  import namespace IO
  import Coal.Monad(trait Monad, and_then)
  import Coal.Functor(trait Functor)
  import Coal.Applicative(trait Applicative)

  type Reader<r, a> = Reader(r -> a)

  fun run_reader(reader, env) =
    match(reader) {
      | Reader(f) => f(env)
    }

  fun ask() = 
    Reader(fn(env) => env)

  instance Functor<Reader<r>> {
    fun map(f, Reader(g)) =
      Reader(fn(r) => f(g(r)))
  }

  instance Applicative<Reader<r>> {
    fun pure(r) = 
      Reader(fn(_) => r)

    fun ap(Reader(f), Reader(g)) =
      Reader(fn(r) =>
        let func = f(r) in
        let a    = g(r) in
        func(a)
      )
  }

  instance Monad<Reader<r>> {
    fun bind(reader : Reader<r, a>, next : a -> Reader<r, b>) : Reader<r, b> = 
      match(reader) {
        | Reader(f) =>
            Reader(fn(env) =>
              let a = 
                f(env) 
              in
              match(next(a)) {
                | Reader(g) => g(env)
              }
            )
      }
  }

  fun local(transform, Reader(f)) = 
    Reader(fn(env) => f(transform(env)))

  // Example use:

  fun test_reader(val : int32) : Reader<int32, int32> =
    ask()
      |. and_then(fn(r) => pure(val + r))

  fun main() =
    IO.println_int32(run_reader(test_reader(5), 5))

}

Writer

module Main {

  import namespace IO
  import namespace List
  import Coal.Monad(trait Monad, and_then, and_eval)
  import Coal.Functor(trait Functor)
  import Coal.Applicative(trait Applicative)
  import Coal.Monoid(trait Monoid)
  import IO(return)

  type Writer<w, a> = Writer(a, w)

  fun run_writer(writer) =
    match(writer) {
      | Writer(a, w) => (a, w)
    }

  instance Functor<Writer<w>> {
    fun map(f, Writer(a, w)) =
      Writer(f(a), w)
  }

  instance Applicative<Writer<w>> {
    fun pure(w) = 
      Writer(w, id)

    fun ap(Writer(f, w1), Writer(a, w2)) =
      Writer(f(a), w1 <> w2)
  }

  instance Monad<Writer<w>> {
    fun bind(writer, k) : Writer<w, b> =
      match(writer) {
        | Writer(a, w1) =>
            match(k(a)) {
              | Writer(b, w2) => 
                  Writer(b, w1 <> w2)
            }
      }
  }

  fun tell(w) : Writer<w, unit> =
    Writer((), w)

  fun listen(m) : Writer<w,(a, w)> =
    match(m) {
      | Writer(a, w) => Writer((a, w), w)
    }

  // Example use:

  fun test_writer() : Writer<List<string>, int32> =
    tell(["one"])
      |. and_eval(tell(["two"]))
      |. and_eval(tell(["three"]))
      |. and_eval(pure(100))

  fun print_msgs(msgs : List<string>) =
    fold(msgs) {
      | [] => 
          return()
      | m :: @next =>
          IO.println_string(m) |. and_eval(next)
    }

  fun main() =
    match(run_writer(test_writer())) {
      | ((_, w)) => print_msgs(w)
    }

}

State

module Main {

  import namespace IO
  import Coal.Functor(trait Functor)
  import Coal.Monad(trait Monad, and_then)
  import Coal.Applicative(trait Applicative)
  import Coal.Combinators(fst)

  type State<s, a> = State(s -> (a, s))

  fun run_state(State(f), s) = 
    f(s)

  fun eval_state(st, s0) =
    fst(run_state(st, s0))

  instance Functor<State<s>> {
    fun map(f, State(g)) =
      State(fn(s) =>
        let (a, s1) = g(s) 
        in
        (f(a), s1)
      )
  }

  instance Applicative<State<s>> {
    fun pure(x) =
      State(fn(s) => (x, s))

    fun ap(State(sf), State(sa)) =
      State(fn(s0) =>
        let (f, s1) = sf(s0) in
        let (a, s2) = sa(s1) in
        (f(a), s2)
      )
  }

  instance Monad<State<s>> {
    fun bind(m, k) =
      State(fn(s0) =>
        let (a, s1) = run_state(m, s0) 
        in
        run_state(k(a), s1)
      )
  }

  fun get() = 
    State(fn(s) => (s, s))

  fun put(s) = 
    State(fn(_) => ((), s))

  fun modify(f) = 
    State(fn(s) => ((), f(s)))

  // Example use:

  fun authenticate
    | "password123" = put(true)
    | _             = put(false)

   fun msg(success : bool) =
     if (success) then "Logged in" else "Authentication failed"

  fun state_example(pw : string) =
    get()
      |. and_then(fn(logged_in) => 
           if (logged_in) 
             then pure("Already logged in")
             else 
               authenticate(pw) 
                 |. and_then(get) 
                 |. and_then(pure << msg)
      )

  fun main() =
    IO.println_string(eval_state(state_example("abc123"), false))

}

Data structures

AVL-Tree Map

module Map {

  import Number(max)

  type alias MapFields<key, val> =
    { key    : key
    , value  : val
    , height : int32
    , left   : Map<key, val>
    , right  : Map<key, val> 
    }

  type Map<key, val> = Empty | Map(MapFields<key, val>)

  // Helpers

  fun height(map) =
    match(map) {
      | Empty => 0
      | Map({ height = h | _ }) => h
    }

  fun make_node(key, val, left, right) =
    Map({ key    = key
        , value  = val
        , height = 1 + max(height(left), height(right))
        , left   = left
        , right  = right
        })

  fun balance_factor
    | Empty  = 0
    | Map(m) = height(m.left) - height(m.right)

  // Constructors

  let empty = Empty

  fun singleton(key, val) = 
    make_node(key, val, Empty, Empty)

  // Rotations

  //
  //       y              x
  //      / \            / \
  //     x   t3   ==>   t1  y
  //    / \                / \
  //   t1 t2              t2 t3
  //

  fun rotate_right(map) = 
    match(map) {
      | Map({ key = yk, value = yv, left = Map(x), right = t3 | _ }) => 
          let t1 = x.left;
              t2 = x.right
          in
          make_node(x.key, x.value, t1, make_node(yk, yv, t2, t3))
      | _ => 
          map
    }

  //
  //     x                 y
  //    / \               / \
  //   t1  y     ==>     x  t3
  //      / \           / \
  //     t2 t3         t1 t2
  //

  fun rotate_left(map) = 
    match(map) {
      | Map({ key = xk, value = xv, left = t1, right = Map(y) | _ }) => 
          let t2 = y.left;
              t3 = y.right
          in
          make_node(y.key, y.value, make_node(xk, xv, t1, t2), t3)
      | _ => 
          map
    }

  // Rebalancing

  fun rebalance(map) =
    match(map) {
      | Empty =>
          Empty

      | Map(m) =>
          let bf = balance_factor(map) in
          if (bf > 1) then
            if (balance_factor(m.left) < 0) then
              rotate_right(
                make_node(m.key, m.value,
                  rotate_left(m.left),
                  m.right
                )
              )
            else
              rotate_right(map)

          else if (bf < -1) then
            if (balance_factor(m.right) > 0) then
              rotate_left(
                make_node(m.key, m.value,
                  m.left,
                  rotate_right(m.right)
                )
              )
            else
              rotate_left(map)

          else
            map
    }

  // Lookup

  fun lookup(key, map) =
    fold(map) {
      | Empty =>
          None

      | Map({ key = key_, value = value, left = @left, right = @right | _ }) =>
          if (key == key_) then
            Some(value)
          else 
            if (key < key_) then
              left
            else
              right
    }

  // Insertion

  fun insert(key, val, map) =
    fold(map) {
      | Empty =>
          singleton(key, val)

      | Map({ left = @left, right = @right | _ } as m) =>
          if (key == m.key) then
            make_node(key, val, m.left, m.right)

          else if (key < m.key) then
            rebalance(make_node(m.key, m.value, left, m.right))

          else
            rebalance(make_node(m.key, m.value, m.left, right))
    }

  // Deletion

  fold delete_min_node : Map<k, v> -> (Option<k>, Option<v>, Map<k, v>) {
    | Empty =>
        (None, None, Empty)

    | Map({ left = Empty | _ } as m) =>
        (Some(m.key), Some(m.value), m.right)

    | Map({ left = delete_min_node(@left) | _ } as m) =>
        let (k, v, new_left) = left 
        in
        (Some(k), Some(v), rebalance(make_node(m.key, m.value, new_left, m.right)))
  }

  fun delete(key, map) =
    fold(map) {
      | Empty =>
          Empty

      | Map({ left = @left, right = @right as rhs | _ } as m) =>
          if (key < m.key) then
            rebalance(make_node(m.key, m.value, left, m.right))

          else if (key > m.key) then
            rebalance(make_node(m.key, m.value, m.left, right))

          else
            match(delete_min_node(m.right)) {
              | (Some(k), Some(v), new_right) =>
                  rebalance(make_node(k, v, m.left, new_right))
              | _ =>
                  m.left
            }
    }
}