Things to expect from this talk.
OCaml functors ain't Haskell functors.
Tim McGilchrist (@lambda_foo)
Things to expect from this talk.
OCaml functors ain't Haskell functors.
The ML module system comes up in conversations around Haskell needing a module system, why building a Haskell module system is hard and just how do I relate ML Modules to things in other languages like Traits in Scala or Type Classes in Haskell. Rather than having a bunch of handwaving about modules can do this and type classes can do that, it'd be better if everyone had a basic understanding of what ML modules are. So the discussion can come from a place of knowledge
Anecdote about why I chose to learn OCaml rather than Haskell. be a contrarian
Lets dive into some terminology
The key parts of OCaml's Module System are:
OCaml has 2 levels or languages;
'a
(Int, Int) -- Haskellint * int -- OCaml[Bool] -- Haskellbool list -- OCamlfoldl :: (b -> a -> b) -> b -> [a] -> bfold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'adata Tree a = Node a (Tree a) (Tree a) | Leaftype 'a tree = Node of 'a * 'a tree * 'a tree | Leaf
Explain the code examples.
Structures provide units of organistion within OCaml
module IntSet = struct type t = int type set = t list let empty = [] let member i s = List.exists (fun x -> x = i) s let insert i s = if member i s then s else (i::s)end
struct
end
keyword pair.Called via dot notation
IntSet.tIntSet.emptyIntSet.member
Pretty much as you'd expect coming from another language
module IntSet = struct module Compare = struct type t = int let eql x y = x = y endendIntSet.Compare.eql 1 1
Nested structures can be nested.
open
structure at top of the fileopen IntSetCompare.eql 1 1(* or *)IntSet.Compare.(eq 1 1)(* or *)let module S = IntSet.Comparein S.eql 1 1
Opening the module at the top of a file. Using open
then the name of the
module, everything that the module exports will be available in the opening
module. This is convenient, no need to pre-pend anything with module names,
the downside however is you pollute the namespace. Might not want to have
everything in this namespace, eg name clashes. Good idea to keep it small.
Convenient option if you're only using the module in a few places within a file. Especially if you're using the same module repeatedly in a a statement.
Helpful to keep what the short name of a module is close to where it's being used.
Defines an interface for a structure.
module type Set = sig type 't set val empty : 't set val member : 't set -> 't -> bool val insert : 't set -> 't -> 't set optionend
sig
end
keywordsstruct
in one file say set.ml
and the
signature defined in another file called set.mli
.module type Monad = sig type t val bind : 'a t -> ('a -> 'b t) -> 'b t val return : 'a -> 'a t val (>>=): 'a t -> ('a -> 'b t) -> 'b t val (>>|): 'a t -> ('a -> 'b) -> 'b tend
Lets introduce a signature for a Monad.
struct
to struct
Functors in OCaml aren't like Haskell functors, they don't provide an fmap
across a particular structure. Though they do perform a similar purpose, just
it's at the module level. Think of functors as functions
from struct
to
struct
.
Another way, to think of it is lifting functions into the module language
Let's look at an example!
module type ORDERING = sig type t val compare : t -> t -> intendmodule type Set = sig type 't set val empty : 't set val member : 't set -> 't -> bool val insert : 't set -> 't -> 't set optionend
ORDERING
module MakeSet (O : ORDERING) = struct type t = O.t let empty = [] let rec member s x = match s with [] -> false | hd::tl -> match O.compare x hd with 0 -> true | 1 -> false | -1 -> member tl x let rec insert x s = ...end
module IntOrdering = struct type t = int let compare = Int.compareendmodule IntSet = MakeSet(IntOrdering)let a = IntSet.insert 1 IntSet.emptyIntSet.member 1 a(* Using local opens *)let a = IntSet.(insert 1 empty)
struct
to struct
So let's take a moment to review what we've covered so far. If nothing else, I'd be great if you can take these defintions away with you.
Module system explicit configuration of program components and the use of data abstraction
Different purposes but significant overlap
Type classes focus on bringing implict program construction and adhoc polymorphism to Haskell. By implicit construction I mean in using type classes you don't need to say which instance of a type class to use. Haskell works that out for you. Providing some sort of guarantee that there is only 1 possible implementation (Though there are a snumber of ways around this if you're sufficiently devious)
Modules on the other hand emphasise explicit configuration of program components and the use of data abstraction. Namely a means for decomposing large programs into smaller pieces.
I'm probably oversimplifying the motivations on both sides, the point is they're not trying to solve the same problem. However there is some overlap in functionality.
So lets see some code showing how to translate Haskell Type Classes into OCaml
Haskell
class Show a where show :: a -> String
OCaml
module type SHOW = sig type t val show : t -> stringend
So type classes in Haskell become signatures in OCaml. Lets look at the show type class as a simple example, though this applies to any type class.
Haskell
instance Show Int where show = ... -- provided in ghc
OCaml
module ShowInt = struct type t = int let show = string_of_intend
struct
that has the right interfaceHaskell
csv :: Show a => [a] -> Stringcsv [] = ""csv [x] = show xcsv h:t = show h ++ "," ++ csv tcsv [1,2,3] => "1,2,3"
Functions that use type classes do not need to do anything special
OCaml
module Csv(S:SHOW) = struct let rec to_csv : S.t list -> string = function [] -> "" | [x] -> S.show x | h::t -> S.show h ^ "," ^ to_csv tendmodule X = Csv(ShowInt)X.to_csv [1;2;3] => "1,2,3"
A few things that aren't quite possible in Haskell:
Using the include
keyword we can extend an existing struct
or signature.
module Listy = struct include List let intersperse t sep = match t with | [] | [_] -> t | x :: List.fold_right (fun y acc -> sep :: y :: acc) xs []endmodule List = Listyinclude (module type of List)
intersperse
Cohttp
is a library for creating HTTP daemons.Mirage
UnikernelIrmin
, a distributed databaseOCamlgraph
, a generic graph library for OCamlApplications in MirageOS are functorized across all the operating system components that they depend on.
In Haskell you're forced to resort to CPP to do this sort of thing. While CPP is comparitively portable, it's untyped, has a different syntax than Haskell and is just kind of gross.
What are we savages???
module type DEVICE = sig type errorendmodule type CONSOLE = sig type error = [ ‘Invalid_console of string ] include DEVICE with type error := error val log_s : t -> string -> unit ioendmodule Main (C: V1_LWT.CONSOLE) = struct let start c = let rec print () = C.log_s c "hello" >>= fun () -> OS.Time.sleep 1.0 >>= fun () -> C.log_s c "world" >>= print in print ()end
module Main (C: V1_LWT.CONSOLE) (FS: V1_LWT.KV_RO) (TMPL: V1_LWT.KV_RO) (S: Cohttp_lwt.Server) = struct ... end
module type Basic = sig type 'a t val bind : 'a t -> ('a -> 'b t) -> 'b t val return : 'a -> 'a tendmodule type S = sig .... endmodule Make (M : Basic) : S with type 'a t := 'a M.t = struct let bind = M.bind let return = M.return let join t = t >>= fun t' -> t' let ignore t = map t ~f:(fun _ -> ()) ...end
Proposal to improve ad-hoc polymorphism in OCaml
module type Show = sig type t val show : t -> unitendlet print (implicit S : Show) x = S.show ximplicit module Show_int = struct type t = int let show x = Printf.printf "Show_int: %i\n" xendprint 3
Haskell records-as-modules style
data Index f = Index { record :: Descriptor -> f () , lookup :: LogKey -> EitherT IndexError f (Maybe Descriptor)}makeS3Index root = Index { record = record' root , lookup = lookup' root}record' :: Address -> Descriptor -> S3Action ()record root d = ...
struct
to struct
Tim McGilchrist @lambda_foo
Software Engineer @ Ambiata
Thanks!
Things to expect from this talk.
OCaml functors ain't Haskell functors.
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |