simple prototype language with:
- s-expressions based syntax
- hindley-milner-based type system
- row polymorphism (scoped labels)
- (open) sum types + pattern matching
- first-class polymorphism with type inference
- higher-kinded types
- minimal package imports
- cryptic error messages :-/
- minimal interpreter
the main parts:
- vanilla hindley-milner
- row polymorphism à la Leijen, with both records and sums
- first-class polymorphism à la Jones i.e. function variables introduce FCP + give up first-class data constructors. the only data constructors are module constructors
- parametrized signatures also à la Jones, with universal quantifiers only
- type/module/value language à la Rossberg, but trying hard to stick to rank-1 types (and use FCP when necessary)
1, 2 are pretty much standard. 3 is implemented using modules as the only data constructors, with associated parametrized signatures similar to 4. for instance:
(new functor
(map list-map))
this boxes the record type forall a, b, f. {map: (a -> b) -> (f a) -> (f b)}
as module type forall f. functor f
. module signatures are associated with
module type constructors (here functor
) and are used to unbox module values.
in this case the signature is:
forall a, b, f. (functor f) -> {map: (a -> b) -> (f a) -> (f b)}
5 is used because of me being too lazy to implement a separate language for types and terms. so I tried to use the term language as much as possible.
(def (list-size (list x))
(match x
(nil _ 0)
(cons self (+ 1 (list-size self.tail)))))
the the above example, list
is actually a value:
list
;; list : (type 'a) -> type (list 'a) = #<fun>
the right-most list
is the type constructor list
, which is here reified as
the value list
. the value is used for function parameter type annotations
as shown above. the type constructor list
is associated with the following
signature:
forall a. (list a) -> <cons: {head: a; tail: list a}; nil: list a>
which makes it possible to automatically unbox values of type list a
at
function applications (automatic coercion), in this case for
pattern matching.
-
to discover/understand how functional languages work
-
to have a simple basis for trying out advanced features like lifetime polymorphism, linear type systems, etc
-
for the fun!!1
;; no "hello word (yet) sorry :/"
;; simple arithmetic
(+ 1 2)
;; : integer = 3
;; empty list
nil
;; nil : list 'a = ()
;; list size
(def (list-size (list x))
(match x
(nil _ 0)
(cons self (+ 1 (list-size self.tail)))))
list-size
;; list-size : (list 'a) -> integer = #<fun>
;; list concatenation, pattern matching
(def (concat lhs rhs)
(match lhs
(cons self (cons self.head (concat self.tail rhs)))
(nil _ rhs)))
concat
;; concat : <cons: {head: 'a; ...b}; nil: 'c> -> (list 'a) -> list 'a = #<fun>
;; build a list containing an integer range
(def (range start end)
(if (= start end) nil
(cons start (range (+ start 1) end))))
range
;; range : integer -> integer -> list integer = #<fun>
;; some test list
(def data (concat (range 0 10) (range 0 3)))
data
;; data : list integer = (0 1 2 3 4 5 6 7 8 9 0 1 2)
;; list map
(def (list-map f (list x))
(match x
(nil _ nil)
(cons self (cons (f self.head) (list-map f self.tail)))))
list-map
;; list-map : ('a -> 'b) -> (list 'a) -> list 'b = #<fun>
;; doubling our test list
(list-map (fn (x) (* 2 x)) data)
;; functor module definition
(struct (functor (ctor f))
(map (fn (a b) ((a -> b) -> (f a) -> (f b)))))
functor
;; functor : (ctor ''a) -> type (functor ''a) = ()
;; list functor instance
(def list-functor
(new functor
(map list-map)))
list-functor
;; list-functor : functor list = {map: #<fun>}
;; sum types: maybe
(union (maybe a)
(none unit)
(some a))
maybe
;; maybe : (type 'a) -> type (maybe 'a) = ()
;; convenience
(def none (new maybe
(none ())))
none
;; none : maybe 'a = {none: ()}
;; convenience
(def (just a)
(new maybe (some a)))
just
;; just : 'a -> maybe 'a = #<fun>
;; maybe map
(def (maybe-map f (maybe a))
(match a
(none _ none)
(some self (just (f self)))))
maybe-map
;; maybe-map : ('a -> 'b) -> (maybe 'a) -> maybe 'b = #<fun>
;; maybe functor instance
(def maybe-functor
(new functor
(map maybe-map)))
maybe-functor
;; maybe-functor : functor maybe = {map: #<fun>}
;; omg monads!!11
(struct (monad (ctor m))
(pure (fn (a) (a -> (m a))))
(>>= (fn (a b) ((m a) -> (a -> (m b)) -> (m b)))))
monad
;; monad : (ctor ''a) -> type (monad ''a) = ()
;; maybe bind
(def (maybe-bind (maybe a) f)
(match a
(none _ none)
(some a (f a))))
maybe-bind
;; maybe-bind : (maybe 'a) -> ('a -> maybe 'b) -> maybe 'b = #<fun>
;; maybe monad instance
(def maybe-monad
(new monad
(pure just)
(>>= maybe-bind)))
maybe-monad
;; maybe-monad : monad maybe = {>>=: #<fun>; pure: #<fun>}
;; reader pure
(def (reader-pure x)
(fn (y) x))
reader-pure
;; reader-pure : 'a -> 'b -> 'a = #<fun>
;; reader bind
(def (reader-bind a f)
(fn (y)
(f (a y) y)))
reader-bind
;; reader-bind : ('a -> 'b) -> ('b -> 'a -> 'c) -> 'a -> 'c = #<fun>
;; reader monad
(def reader-monad
(new monad
(pure reader-pure)
(>>= reader-bind)))
reader-monad
;; reader-monad : monad 'a -> = {>>=: #<fun>; pure: #<fun>}
- improve error messages
- do notation
- state monad, mutable refs, etc