This is part of a series of examples of the Amulet programming language. If you do not yet have an Amulet compiler, please refer to the installation instructions.
open import "foo.ml"
imports foo
from the library path.
open import "prelude.ml"
Let bindings introduce variables. These variables can not be mutated.
let foo = 1
let bar = ""
Type inference can figure out the type of a variable without the programmer having to supply it.
let foo x = x + x
(+)
operates on ints, (+.)
on floats.
let bar x = x +. x
The type
construct can be used to define a new data type. Types are defined by listing their constructors
type foo =
| Foo_str of string
| Foo_int of int
Values of algebraic data types can be taken apart with pattern matching.
let get_foo_str x =
match x with
| Foo_str x -> x
| Foo_int _ -> "it wasn't a string!"
The function
keyword abbreviates a pattern matching function.
let get_foo_str' = function
| Foo_str x -> x
| Foo_int _ -> "not a string again?"
Pattern matching on bool
can be done with if .. then .. else ..
.
let is_gte x y =
if x >= y then
"it is"
else
"it's not"
User-defined data types can be recursive.
type chain =
| Chain_end
| Chain_add of string * chain
Recursive functions need a rec
keyword, as in OCaml.
let rec str_of_chain = function
| Chain_end -> "end"
Concatenation is done using the (^)
function.
| Chain_add (x, rest) ->
x ^ " + " ^ str_of_chain rest
Types can have parameters. These stand for another type, and can be inferred.
type li 'a =
| Nil_
| Cons_ of 'a * li 'a
val map : ('a -> 'b) -> li 'a -> li 'b
let rec map f = function
| Nil_ -> Nil_
| Cons_ (x, xs) -> Cons_ (f x, map f xs)
Amulet has a built-in type of lists, list 'a
.
let foo : list int = [1, 2, 3, 4, 5]
Lists can be manipulated using list comprehensions.
let greater_than_3 =
[ x
| with x <- foo
, x > 3
]
List literal syntax can also be used in patterns.
let [a, b, c] = [1, 2, 3]
let _ = a + b + c
Type classes are used for principled overloading.
class to_string 'a begin
val to_str : 'a -> string
end
instance to_string int begin
let to_str _ = "int"
end
instance to_string bool begin
let to_str _ = "bool"
end
Methods can be used with any type that has a corresponding instance
.
let "int" = to_str 123
let "bool" = to_str true
Type class methods can be polymorphic in their return types.
type a_box 'a = Box of 'a
The class into_box
is parametrised by a type constructor.
class into_box 'f begin
val into : 'a -> 'f 'a
end
instance into_box list begin
let into x = [x]
end
instance into_box a_box begin
let into = Box
end
Amulet selects the instance to use automatically, based on the context into
is used in.
let Box 123 = into 123
let [123] = into 123
into_box
is almost the standard class applicative
. Amulet has special syntax for using applicative
functors, idiom brackets:
let cartesian (xs : list _) ys =
| (,) xs ys |) (
Idiom brackets desugar into uses of pure
and <*>
. These definitions of cartesian
are identical.
let cartesian (xs : list _) ys =
pure (,) <*> xs <*> ys
Amulet has special syntax for use of the monad
class.
let might_fail x =
if x >= 10 then
None
else
Some x
The monad option
instance aborts the computation when it encounters a None
.
let None =
let! x = might_fail 1
let! y = might_fail 11
pure (x + y)
Computations without None
are simply chained.
let Some 2 =
let! x = might_fail 1
let! y = might_fail 1
pure (x + y)