From: Roberto Di Cosmo <roberto@dicosmo.org>
To: caml-list@yquem.inria.fr
Subject: GADT examples: composable functions list (Was: Re: [Caml-list] Wanted: GADT examples: string length, counting module x)
Date: Thu, 22 Mar 2012 10:28:06 +0100 [thread overview]
Message-ID: <20120322092806.GA29219@voyager> (raw)
GADT come in really handy is when you have data structures that need existential
type variables.
A nice example is the case of lists of composable functions: say you want to
build a list containing functions f_i : A_i -> A_{i+1}
Without GADT
------------
One can get away cheating the type system and declaring the type
type ('a,'b) cfl = ('a -> 'b) list;;
which is really incorrect: 'a is the first input type, 'b is the last output
type, and that's ok, but it is really not true that the list will contain
functions of type 'a -> 'b ...
This shows up as soon as one tries to do something useful with this list, like
adding one element at the bebinning: to keep the type checker happy, we call
Obj.magic in for help
let add (f: 'a -> 'b) (fl : ('b,'c) cfl) : ('a,'c) cfl =
(Obj.magic f):: (Obj.magic fl);;
And you will need Obj.magic's help in writing map, fold, compute, whatever...
You may argue that if all the hectic primitives are well hidden behind a module
signature, and the module programmer is very smart, all will be well, but
that's ugly, isn't it?
Here is the elegant way of doing it using GADT
----------------------------------------------
Declare the type cfl of a composable function list as follows
type ('a,'b) cfl =
Nilf: ('a,'a) cfl
|Consf: ('a -> 'b) * ('b,'c) cfl -> ('a,'c) cfl;;
Now you can write useful functions which are well typed
let rec compute : type a b. a -> (a,b) cfl -> b = fun x ->
function
| Nilf -> x (* here 'a = 'b *)
| Consf (f,rl) -> compute (f x) rl;;
Try it... it works!
let cl = Consf ((fun x -> Printf.sprintf "%d" x), Nilf);;
let cl' = Consf ((fun x -> truncate x), cl);;
compute 3.5 cl';;
Notice that the type of Consf contains a variable 'b which is
not used in the result type: one can check that
('a -> 'b) * ('b,'c) cfl -> ('a,'c) cfl
can be seen as
\forall 'a 'c. (\exists 'b.('a -> 'b) * ('b,'c) cfl) -> ('a,'c) cfl
so, when deconstructing a cfl, one gets of course a function and the
rest of the list, but now we know that their type is
\exists 'b.('a -> 'b) * ('b,'c) cfl
Well, isn't this a contrived example?
-------------------------------------
Actually, not at all... back in 1999, when developing a parallel
programming library named ocamlp3l, we implemented high-level
parallelism combinators that allowed to write expressions like this
(hey, isn't this map/reduce? well, yes... indeed that was an ooold idea)
(seq(intervals 10) ||| mapvector(seq(seq_integr f),5) ||| reducevector(seq(sum),2))
These combinators could be interpreted sequentially or graphically quite
easily, but turning them into a distributed program required a lot of
work, and the first step was to build an AST from these expressions:
here is a snippet of the actual type declaration from the old code in parp3l.ml
(* the type of the p3l cap *)
type ('a,'b) p3ltree = Farm of (('a,'b) p3ltree * int)
| Pipe of ('a,'b) p3ltree list
| Map of (('a,'b) p3ltree * int)
| Reduce of (('a,'b) p3ltree * int)
| Seq of ('a -> 'b)
;;
And here is one of the simplification steps we had to perform on the AST
let (|||) (t1 : ('a,'b) p3ltree) (t2 : ('b,'c) p3ltree) =
match ((Obj.magic t1 : ('a,'c) p3ltree), (Obj.magic t2 : ('a,'c) p3ltree)) with
(Pipe l1, Pipe l2) -> Pipe(l1 @ l2)
| (s1, Pipe l2) -> Pipe(s1 :: l2)
| (Pipe l1, s2) -> Pipe(l1 @ [s2])
| (s1, s2) -> Pipe [s1; s2];;
I am sure you see the analogy with the composable function list: a series
of functions in a paralle pipeline have exactly the same type structure.
With GADTs, onw can can finally write this 1999 code in a clean way in OCaml,
so many thanks to the OCaml team, and keep up the good work!
--Roberto
------------------------------------------------------------------
Professeur En delegation a l'INRIA
PPS E-mail: roberto@dicosmo.org
Universite Paris Diderot WWW : http://www.dicosmo.org
Case 7014 Tel : ++33-(0)1-57 27 92 20
5, Rue Thomas Mann
F-75205 Paris Cedex 13 Identica: http://identi.ca/rdicosmo
FRANCE. Twitter: http://twitter.com/rdicosmo
------------------------------------------------------------------
Attachments:
MIME accepted, Word deprecated
http://www.gnu.org/philosophy/no-word-attachments.html
------------------------------------------------------------------
Office location:
Bureau 6C08 (6th floor)
175, rue du Chevaleret, XIII
Metro Chevaleret, ligne 6
------------------------------------------------------------------
next reply other threads:[~2012-03-22 9:24 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-03-22 9:28 Roberto Di Cosmo [this message]
2012-03-22 9:46 ` Daniel Bünzli
2012-03-22 10:54 ` Roberto Di Cosmo
2012-03-22 14:31 ` Markus Mottl
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20120322092806.GA29219@voyager \
--to=roberto@dicosmo.org \
--cc=caml-list@yquem.inria.fr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).