gdritter repos documents / master scraps / picoml.md
master

Tree @master (Download .tar.gz)

picoml.md @master

1ac99fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b4f134
 
 
1ac99fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
PicoML
======

PicoML is an ML- and Haskell-inspired language that is designed to be
easy to implement, straightforward, consistent, and small.

~~~~
fact : Int -> Int
  0 = 1
  n = n * fact (n - 1)

main () = putln ("fact 5 = " <> fact 5)
~~~~

Type declarations are done either with the `data` or the `record` keywords:

~~~~
data List a =
  [ Nil
  , Cons a (List a)
  ]

record Stream a =
  { .head a
  , .tail (Stream a)
  }
~~~~

Types created with `data` are always eager and types created with `record`
are always lazy, so you can easily create infinite streams:

~~~~
streamZip : (a -> b -> c) -> Stream a -> Stream b -> Stream c
streamZip f s1 s2 =
  { .head = f s1.head s2.head
  , .tail = streamZip f s1.tail s2.tail
  }

fibs : Stream Int =
  { .head = 1
  , .tail = streamZip _+_ fibs fibs.tail
  }
~~~~

Recursive bindings are allowed in a type-directed way, so a binding whose
type is a function or a record will allow recursive bindings, but a binding
whose type is data will not allow a recursive binding.

~~~~
listZip : (a -> b -> c) -> List a -> List b -> List c
   f (Cons x xs) (Cons y ys) = Cons (f x y) (listZip f xs ys)
   _ _           _           = Nil

tail : List a -> List a
tail (Cons x xs) = xs
tail Nil         = error "tail of empty list"

-- this definition will fail, because one cannot recursively refer to
-- value bindings
badFibs : List Int
  = Cons 1 (zip _+_ badFibs (tail badFibs))
~~~~

The language is not pure, but all bindings are immutable, so reference cells
are provided in the stdlib:

~~~~
impFact : Int -> Int
  n = let acc = ref 0
    ; let num = ref n
    ; while { !num > 0 }
	    { acc := !acc * !num
	    ; num := !num - 1
		}
	; !acc
~~~~

This also demonstrates the other usage of curly braces: they are sugar for
an anonymous function that takes a `()` argument, i.e. the above `impFact`
definition is equivalent to

~~~~
impFact : Int -> Int
  n = let acc = ref 0
    ; let num = ref n
    ; while (() -> !num > 0) (() ->
	    ( () ->
            acc := !acc * !num
 	      ; num := !num - 1
		)
	; !acc
~~~~

This means that `while` is a function included in the stdlib, as well:

~~~~
while : Thunk Bool -> Thunk () -> ()
  cond body = if | cond () -> (body () ; while cond body)
                 | else    -> ()

-- because elsewhere
type Thunk a = () -> a
~~~~

Finally, PicoML has a mechanism for supplying values implicitly. The
`implicit` keyword can be used on value bindings and on function
arguments. The language will then infer, based on the type, whether or
not the user supplied the argument, or whether it needs to reach for
the implicit argument:

~~~
implicit n : Int = 5

shout : implicit Int -> ()
  x = println (show x)

main =
  { shout 8 -- prints 8
  ; shout   -- prints 5
  }
~~~

If there are _multiple_ implicit values in scope whose types match,
then PicoML will raise a compile-time error:

~~~~
implicit n : Int = 5
implicit m : Int = 6

main = { shout } -- will not compile, because of the ambiguity
                 -- as to which implicit to choose
~~~~

This, combined with manual dictionary-passing, is how typeclass-like
functionality is implemented in PicoML

~~~~
record Monoid m =
  { .mempty m
  , .mappend (m -> m -> m)
  }

implicit additiveMonoid : Monoid Int =
  { .mempty  = 0
  , .mappend = _+_
  }

multiplicativeMonoid : Monoid Int =
  { .mempty  = 1
  , .mappend = _*_
  }

mconcat : implicit (Monoid a) -> List a -> a
  m (Cons x xs) = m.mappend x (mconcat m xs)
  m Nil         = m.mempty

main =
  { print (mconcat [2,3,4])                      -- prints 9
  ; print (mconcat multiplicativeMonoid [2,3,4]) -- prints 24
  }
~~~~