#
" prompt.
;;
" signifies the end of input to the top
loop, which the system will immediately process.
blue typewriter
font is typed by the user, and
maroon typewriter
font is the computer reply.)
% ocaml Objective Caml version 3.06 # let x = 3+4;; val x : int = 7
let x = 3+4
is compiled, loaded, run,
and the result printed.
val x : int = 7
means "the return value of the
expression is 7", and int
is the (automatically
inferred) type of the return value.
# x+4;; - : int = 11
x
was declared to be
7
, from the previous entry into the top loop.
-
indicates this anonymous variable). Variables
must be lower-case in Caml.
let
syntax allows for local variable declarations in Caml:
# let x = 4 in x+3;; - : int = 7Declarations typed in at the top level are like an open-ended
let
:
# let x=4;; val x : int = 4 # let y = x+3;; val y : int = 7 # x*x;; - : int = 16Notice how the types are being inferred for us (pretty simple to do here but harder for more complex programs).
int, float, string, char, bool
types:
4, 4.3, "hi", 'c', true
These types have all the standard kinds of operations on them, in the libraries.
+ - mod abs ** sqrt && || <>
etc
Pervasives
which is
always loaded. We will cover OCaml modules later.
Array
Stream List
, etc.
Char.code
for the char -> int
ASCII code function.
# Char.code 'a';; - : int = 97
# 2.3 * 5.7;; Characters 6-9: This expression has type float but is here used with type int # 2.3 *. 5.7;; - : float = 13.110000Caml also never performs implicit coercions, all coercions must be explicit.
# 2.3 *. 5;; Characters 7-8: This expression has type int but is here used with type float
Why is Caml doing this?
# (if (2=3) then 5 else 6) + 1;; - : int = 7One sometimes annoying consequence of the above is the two branches of the
if
need to return the same type.
if (2=3) then 5 else 6.5;; Characters 21-24: This expression has type float but is here used with type int
let
allows local declarations to be defined.
let x = ... in ....
is somewhat analogous to local variable definition
{ int x = ...; ... }in C, but the Caml variable
x
is given a value that is
immutable (can never change).The top-loop is analogous to an open-ended let: each entry defines a new nested let-scope with a new declaration, and the scope can never be closed.
malloc/free
them. A Garbage collector automatically
collects them when they are unreachable and thus no longer used.
# [2;1+2;4];; - : int list = [2; 3; 4]
int list
in this case, is
inferred automatically.
# ["e"; String.concat "" ["f";"g"] ;"h"];; - : string list = ["e"; "fg"; "h"]Notice how the function call
String.concat
["f";"g"]
does not
require ( ... )
around the function's arguments, and how
they are space- and not comma-separated. Thats because
sin 0.3
max 3 4
.
# [3;"e"];; Characters 3-6: This expression has type string but is here used with type intList operations are numerous.
# let x = [2;3];; val x : int list = [2; 3] # let y = 1::x;; (* this is called "consing" an element on to a list *) val y : int list = [1; 2; 3] # x;; val x : int list = [2; 3] (* y is a NEW list; the list x is IMMUTABLE and didn't change *) # x @ y;; (* appending lists *) - : int list = [2; 3; 1; 2; 3] # List.hd x;; (* head: first element of a list *) - : int = 2 # List.tl x;; (* the tail or rest of a list *) - : int list = [3]The hd/tl operations are not in the core library (the module name must be used when referring to them) because you should generally not use them. (And, we will penalize any use of them in homeworks). Instead, use pattern matching:
# match x with h::t -> h;; Characters 0-22: Warning: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: [] - : int = 2
match
forces x to match pattern h::t
, a
list with head and tail, and then we grab the head h
.
List.hd
to blow up (raise an excaption).
List.hd
/List.tl
are bad because you
really should deal with all possible patterns (more later on that).
# match x with h::t -> t;; Characters 0-22: Warning: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: [] - : int list = [3]
# let x = (2,"hi");; val x : int * string = 2, "hi" # match x with (y,z) -> y;; - : int = 2Note tuple type syntax:
int * string
, etc.
All the data mentioned so far is immutable - it is impossible
to change
an entry in an existing list, tuple, or record!
Also, all variables are immutable. Thus the language described so far
is a pure functional subset of ML.
Mutable features will be discussed later.
#let rec fib n = if n < 2 then 1 else fib(n-1) + fib(n-2);; val fib : int -> int = <fun> # fib 33;; - : int = 5702887
fib
and the parameter is n
.
rec
keyword must be added to a recursive
function. If there is no rec
, the function can't call
itself. n
here is the argument, which also doesn't need to
be in parens.
( .. )
around the argument: sin 0.34;;
return
statement; instead, the value of
the whole body-expression is implicitly what gets returned.
domain -> range
" Their values (yes, functions are
expressions with values, too!) are always printed as
<fun>
.
# ((function x -> x+1) 4) + 7;; - : int = 12
# let f = (function x -> x+1;; (* identical to "let f x = x+1;;" *) val f : int -> int = <fun>These anonymos functions (they aren't given names) end up being very useful as we will see.
# let rec comb n m = (* assumes 0 <= m <= n *) if m=0 or m=n then 1 else comb (n-1) m + comb (n-1) (m-1);; val comb : int -> int -> int = <fun> # comb 10 4;; - : int = 210Look at the type of comb:
int -> int -> int
which is
int -> (int -> int)
: it takes an integer and returns a
function which expects another integer! Lets test this:
# let comb10 = comb 10;; val comb10 : int -> int = <fun> # comb10 4;; - : int = 210 # comb10 3;; - : int = 120Indeed, we can give
comb
only one argument, in which
case it returns a function that we can later use. More on
Currying below.
Mutually recursive functions must be defined simultaneously:
let rec take(l) = match l with [] -> [] | hd :: tl -> hd::skip tl and skip(l) = match l with [] -> [] | hd :: tl -> take tl;; val take : 'a list -> 'a list = <fun> val skip : 'a list -> 'a list = <fun> # take [1;2;3;4;5;6;7;8;9;10];; - : int list = [1; 3; 5; 7; 9] # skip [1;2;3;4;5;6;7;8;9;10];; - : int list = [2; 4; 6; 8; 10]This example also shows a pattern match with multiple cases, either empty list or nonempty list. More on patterns now.
let rec rev l = match l with [] -> [] | x::xs -> rev xs @ [x];;The pattern matching process
[]
and x::xs
are patterns against which the value passed to the function
is matched.
[]
matches any empty
list argument. x::xs
matches any list, and binds x
to the head and
xs
to the tail.
(x,4),
3::y, {e = 4, f = x}
).
x::y::z
.
|
" separates the different possible patterns.
# match [1;2;3] with x::y -> true | x::y::z -> false | [] -> true;; # match [1;2;3] with x::y -> true | x::y::z -> false ^^^^^^^ | [] -> true;; Warning: this match case is unused. - : bool = true
hd/tl
implementations. # let myhd x = match x with x::y -> x;; Toplevel input: # let myhd x = match x with x::y -> x;; ^^^^^^^^^^^^^^^^^^^^^^ Warning: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: [] val myhd : 'a list -> 'a = <fun>
# myhd [];; Exception: Match_failure ("", 11, 33).
_
is
an anonymous pattern that matches anything; for something thrown away.
# let x = [1;2];; val x : int list = [1; 2] # match x with x::y::z::w -> 5 | _ -> 7;; - : int = 7
let
to attribute values to
multiple variables:
let l = [1;2;3;4;5;6;7;8;9;10];; val l : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] # let (evens,odds) = (skip l, take l);; (* skip and take defined above *) val evens : int list = [2; 4; 6; 8; 10] val odds : int list = [1; 3; 5; 7; 9]Similarly patterns can be used in function definitions.
# let add (x,y) = x + y;; val add : int * int -> int = <fun> # add (2,3);; - : int = 5This looks like a function of two arguments, but its a function of one argument which matches a pair pattern. Note, in Caml it is better to use Curried function definitions for multiple-argument functions, not tuples.
let
-defined variable values in Caml:
they cannot change their value later.
const
and Java's final
on fields has a similar effect.
let
is immutable; its bound to
screw you up at some point.
let x = 5 in let f y = x + 1 in let x = 7 in f 0 ;; - : int = 6 (* old value of x is what f refers to *)Here's the one that will mess with your mind: the same thing as above but with the declarations typed into the top loop (the top loop is conceptually an open-ended series of
let
s which never close).
# let x = 5;; val x : int = 5 # let f y = x + 1;; val f : 'a -> int = <fun> # f 0;; - : int = 6 # let x = 7;; (* not an assignment to above x -- a new declaration *) val x : int = 7 # f 0;; - : int = 6Programming moral: When interactively editing a group of functions that call each other, re-submit all of the functions to the top loop when you change any one of them.
let hundredthPower x = let four = x*.x*.x*.x in let twenty = four*.four*.four*.four*.four in twenty*.twenty*.twenty*.twenty*.twenty;; val hundredthPower : float -> float = <fun> # hundredthPower(2.0);; - : float = 1.26765060023e+30
Higher-order functions are an important component of a programmer's toolkit.
map
function on lists. It
takes a list and a function and applies the function to every element
of the list.
let rec map f l = match l with [] -> [] | x::xs -> f(x) :: map f xs;; val map : ('a -> 'b) -> 'a list -> 'b list = <fun>The
'a/'b
types are polymorphic ("any") type, more on them below.
# map (function x -> x*10) [4;2;7];; - : int list = [40; 20; 70]
map
is so common it is built into Caml as
List.map
.
Perhaps the simplest higher-order function is the composer, in mathematics expressed as g o f. it takes two functions and returns a new function which is their composition:
let circle g f = (function x -> g(f(x)));; val circle : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>Here is an example of
circle
in action.
# let plus3times2 = circle (function x -> x*2) (function x -> x+3);; val plus3times2 : int -> int = <fun> # plus3times2 10;; - : int = 26As we have seen before, functions are just expressions so can also be immediately applied after being defined:
# circle (function x -> x*2) (function x -> x+3) 10;; - : int = 26Oddly enough, this looks like application of a three-argument function
circle
, but circle
only takes two arguments! All three arguments
to circle are in fact Curried: one is applied, the result is
a function, and then that function is immediately applied to the next
argument. Let us focus on this topic now.
Here is a two-argument function defined in our usual manner.
# let myadd x y = x + y;; val myadd : int -> int -> int = <fun> # myadd 3 4;; - : int = 7Here is another completely equivalent way to define the same function:
# let myadd x = function y -> x + y;; val myadd : int -> int -> int = <fun> (* the -> type constructor associates to the RIGHT: int -> (int -> int) *) # myadd 3 4;; (* parenthesized as (myadd 3) 4 *) - : int = 7 # let inc3 = myadd 3;; val inc3 : int -> int = <fun> # inc3 4;; - : int = 7 (* same result as myadd 3 4 in the end *)The main observation is
myadd
is a function returning a
function, so the way we supply two arguments is
(myadd 3) 4
is an inlined version of this where
the function returned by myadd 3
is not put in any variable
Here is a third equivalent way to define myadd
, as an
anonymous function returning another anonymous function.
#let myadd = function x -> function y -> x + y;; val myadd : int -> int -> int = <fun>
map
application above showed.
# List.map;; - : ('a -> 'b) -> 'a list -> 'b list = <fun>
Functions can also take pairs as arguments to achieve the effect of a two-argument function:
# let mypairadd (x,y) = x+y;; val mypairadd : int * int -> int = <fun> # mypairadd (2,3);; - : int = 5So, either we can Curry or we can pass a pair. We can also write higher-order functions to switch back and forth between the two forms.
# let curry f = function x -> function y -> f (x,y);; val curry : ('a * 'b -> 'c) -> 'a -> 'b -> 'c = <fun> # let uncurry f = function (x,y) -> f x y;; val uncurry : ('a -> 'b -> 'c) -> 'a * 'b -> 'c = <fun> # uncurry myadd;; - : int * int -> int = <fun> # curry mypairadd;; - : int -> int -> int = <fun> # uncurry map;; (* map defined above *) - : ('_a -> '_b) -> '_a list -> '_b list = <fun> # curry(uncurry myadd);; (* a no-op *) - : int -> int -> int = <fun>Look at the types: these mappings in both directions in some sense "implement" the well-known isomorphism on sets: A * B -> C = A -> B -> C
# let rec foldr f l y = match l with [] -> y | x::xs -> f x (foldr f xs y);; val foldr : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun> # let prod = foldr (function a -> function x -> a * x);; val prod : int list -> int -> int = <fun> # let prod0 = prod [1;2;3;4];; val prod0 : int -> int = <fun> # (prod0 1, prod0 2);; - : int * int = 24, 48Here is an analysis of this recursive function.
[x1;x2]
, the
call foldr f [x1; x2] ycomputes to
f x1 (foldr f [x2] y)which in turn computes to
f x1 (f x2 (foldr f [] y)))which computes to
f x1 (f x2 y)From this we can assert that the general result returned from
foldr f [x1;x2;...;xn] y
is
f x1 (f x2 f ...(f xn y)...))))Currying allows us to specialize foldr to a particular function f, as with
prod
above.
Lemma. foldr f [xn;...;x1] y
computes to f xn (f xn-1 f ...(f x1 y)...)))
for n
greater than 0.
Proof. Proceed by induction on the length of the
list [xn;..;x1].
Base Case n=1, i.e. the list is [x1]
.
The function computes to f x1 (foldr f [] y)
which computes to f x1 y
as hypothesized.
Induction Step. Assume
foldr f [xn;...;x1] ycomputes to
f xn (f xn-1 f ...(f x1 y)...)))and show
foldr f [xn+1;xn;...;x1] ycomputes to
f xn+1 (f xn f ...(f x1 y)...))))Computing
foldr f [x1;x2;...;xn;xn+1] y, it matches the pattern with x being xn+1 and xs being [xn;...;x1].
foldr f [xn;...;x1] ywhich by our inductive assumption computes to
f xn (f xn-1 f ...(f x1 y)...)))And, given this result for the recursive call, the whole function then returns
f xn+1 (...result of recursive call...)which is
f xn+1 (f xn (f xn-1 f ...(f x1 y)...)))which is what we needed to show.
The above implementation is inefficient in that f is explicitly passed to every recursive call. Here is a more efficient version with identical functionality.
let efficientfoldr f = let rec localfun l y = match l with [] -> y | x::xs -> f x (localfun xs y) in localfun ;;This function also illustrates how functions may be defined in a local scope. Observe
localfun
is defined locally but then
exported since it is the return value of f
.
Question: How does the return value localfun
know
where to look for f
when its called??
# let summate = efficientfoldr (function a -> function x -> a+x);; val summate : int list -> int -> int = <fun> # summate [1;2;3;4] 0;; - : int = 10--summate is just localfun, but somehow it "knows" that
f
is (function a -> function x -> a+x)
, even though f
is undefined at the top level:
# f;; Characters 0-1: Unbound value f
localfun
in fact knew the right f to call, so it must
have been kept somewhere: in a closure.
At function definition point, the current values of variables not local to the function definition are remembered in a closure.Function values in ML are thus really a pair consisting of the function (pointer) and the closure.
Without making a closure, higher-order functions will do unexpected things. Java, C++, C can pass and return function (pointers), but all functions are defined at the top level so they have no closures.
# #use "myfunctions.ml";;-- this will submit everything in the file to the top loop. Note its
#use
, not just use
.
C-c C-e caml-eval-phrase C-c C-r caml-eval-region C-c C-s caml-show-subshell C-c ` caml-goto-phrase-errorcommands of caml mode to submit the current definition (phrase) to caml, the selected region to caml, to show the actual shell window, and to find your errors respectively. When you are playing with little examples, just type a small bit in any emacs buffer and click on it and use the caml-eval-phrase command to send it to caml. caml-eval-phrase at the end of a file submits all the expressions in the file.
print_string("hi\n");; hi - : unit = ()Caml has a print_x function for the atomic types x. Again there is no overloading of meaning here.
let myadd (x:int) (y:int) = x + y;; val myadd : int -> int -> int = <fun>You can in fact put type assertions on any variable in an expression to clarify what type the variable has:
let myadd (x:int) (y:int) = (x:int) + y;; val myadd : int -> int -> int = <fun>
# type intpair = int*int;; type intpair = int * int # let f (p : intpair) = match p with (l,r) -> l+r;; val f : intpair -> int = <fun> # f (2,3);; - : int = 5
# let id x = x;; val id : 'a -> 'a = <fun> # id 3;; - : int = 3 # id true;; - : bool = true
id
was not used as any type in particular, the type
of the function is polymorphic ("many forms").
'a
is a type variable, meaning some arbitrary
type 'a
.
int -> int
would not be
completely general.
'a
:
what comes out is the same type as what came in. Generics is
another term for parametric polymorphism.
Object
, but you have to cast
when you get it out which requires a run-time check.
'a
,
'b
, etc, but then the
operations infer constraints that "this thing has the same type as
that thing". Use of type-specific atomic operators obviously restricts polymorphism:
let doublenegate x = not (not x);; val doublenegate = function : bool -> boolWhen a function is defined via
let
to have polymorphic
type, every use can be at a different type:
# let id = (function x -> x) in match id(true) with true -> id(3) | false -> id(4);; - : int = 3Note that if the function is not declared via let, polymorphism is not allowed. The following code would run just like the code above (if it would typecheck):
# (function id -> match id(true) with true -> id(3) | false -> id(4))(function x -> x);; ^ This expression has type int but is here used with type bool
ML variant types must be declared.
Why? Variant Types are often
recursive, and recursive
types cannot be inferred in ML.
Here is a really simple variant type declaration to get warmed up:
type height = Tall | Medium | Short type height = Tall | Medium | ShortThree constructors have been defined. These are now official constants. Constructors must be capitalized, and variables must be lower-case in Caml.
Tall;; - : height = TallThe previous type is only an enumerated type. Much more interesting variant types can be defined. Lists could have been predefined as a variant type:
# type 'a mylist = Nil | Cons of 'a * 'a mylist;; type 'a mylist = Nil | Cons of 'a * 'a mylist # Cons (3, Cons (3+1,Nil));; - : int mylist = Cons (3, Cons (4, Nil))This form of type has several new features:
'a
here is a type
argument.
Cons
is actually
defined as a so-called constructor function.
Trees:
type 'a tree = EmptyTree | Node of 'a * 'a tree * 'a treePatterns are also variant type-constructor-friendly:
let rec myappend p = match p with (Nil,l2) -> l2 | (Cons(x,xs),l2) -> Cons (x, append(xs,l2)) val myappend : 'a mylist * 'a mylist -> 'a mylist = <fun> # let rec myreverse l = match l with Nil -> Nil | Cons(x,xs) -> myappend(myreverse xs , Cons (x,Nil));; val myreverse : 'a mylist -> 'a mylist = <fun> # myreverse (Cons (3, Cons(4, Cons(2, Nil))));; - : int mylist = Cons (2, Cons (4, Cons (3, Nil)))
Variant types are extremely happy with polymorphism; observe how
myreverse above is polymorphic over 'a mylist
s.
strtuct
s of C/C++.
type onetwo = {one : int; two : string};; type onetwo = { one : int; two : string; } # let x = {one = 2; two = "hi"};; val x : onetwo = {one=2; two="hi"} # x.one;; - : int = 2 # match x with {one = x; two = s} -> x;; - : int = 2
References are the simplest unit of mutability.
# let x = ref 4;; val x : int ref = {contents=4}This allocates a fresh mutable location named
x
. Records
can have mutable fields, and so a reference is in fact implemented in
Caml as a little record
with one mutable field, contents
:
#type 'a ref = { mutable contents: 'a };; (* a type abbreviation; its how ref is defined in OCaml *) type 'a ref = { mutable contents : 'a; }references are records so you can't directly operate on them.
# x+1;; Characters 0-1: This expression has type int ref but is here used with type int # x.contents + 1;; (* x is a record *) - : int = 5 # !x + 1;; (* shorthand notation for the above *) - : int = 5ref variables may be modified by assignment:
# x.contents <- 6;; (* mutate the contents record field *) - : unit = () # x := 6;; (* equivalent shorthand notation for the above *) - : unit = () (* (), the empty tuple of type unit, is the result *) # !x + 1;; - : int = 7Only ref typed variables or mutable records may be assigned to. The notion of immutable variables is one of the great strengths of ML.
Note, let
doesn't turn into a mutation operator with
refs, either:
let x = ref 5;; val x : int ref = {contents = 5} let f () = !x;; val f : unit -> int =let x = ref 6;; (* not an assignment to x *) val x : int ref = {contents = 6} f ();; - : int = 5
Mutable records of general form can be created by putting
mutable
in front of mutable fields:
#type mutable_point = { mutable x: float; mutable y: float };; type mutable_point = { mutable x : float; mutable y : float; } #let translate p dx dy = p.x <- p.x +. dx; p.y <- p.y +. dy;; val translate : mutable_point -> float -> float -> unit = <fun> #let mypoint = { x = 0.0; y = 0.0 };; val mypoint : mutable_point = {x=0.000000; y=0.000000} #translate mypoint 1.0 2.0;; - : unit = () #mypoint;; - : mutable_point = {x=1.000000; y=2.000000}Now that we have references, the while loop construct becomes useful (without references, a while loop would either never execute or loop infinitely -# pretty useless!):
# (x := 1; (while !x < 10 do x := !x + 1 done); !x);; - : int = 10
# Array.make 10 "hi";; (* size and initial value are the params here *) - : string array = [|"hi"; "hi"; "hi"; "hi"; "hi"; "hi"; "hi"; "hi"; "hi"; "hi"|] # let arr = [| 4; 3; 2 |];; val arr : int array = [|4; 3; 2|] # arr.(2);; - : int = 2 # arr.(2) <- 55;; - : unit = () # arr;; - : int array = [|4; 3; 55|]
exception Foo;; exception Foo # let f _ = raise Foo;; val f : 'a -> 'b = <fun> # f ();; Uncaught exception: Foo. -As you can see, exceptions are top-level definable units. Exceptions can be handled. This means even though something bad happened, the program can detect this fact, recover, and continue:
# let g _ = try f () with Foo -> print_string("exception raised; returning 5\n"); 5 ;; val g : 'a -> int = <fun> # g();; exception raised; returning 5 - : int = 5The call
f()
followed by handle is syntax for handling errors
that may arise when f()
is called (including errors that
may arise if
f
in turn calls some other function etc).
Exception foo
is only handled when it happens in
the call to f()
in g
.
Exception handling thus always has a scope.
Exceptions that pass up an argument.
Useful both for print
diagnostics and error recovery.
exception Goo of string;; exception Goo -let f _ = raise (Goo "bad stuff");; val f : 'a -> 'b = <fun> # f ();; Uncaught exception: Goo "bad stuff". # let g () = try f () with Goo s -> (print_string("exception raised: ");print_string(s);print_string("\n"));; val g : unit -> unit = <fun> # g();; exception raised: bad stuff - : unit = ()
#include
s that
.h
file
extern
in the file using them, no need to
explicitly export
Basic entites of Caml modules: structures and functors
module Mapping = struct exception NotFound (* create the empty mapping *) let create = [] (* lookup(d,M) finds the range value r such that (d,r) is a pair in mapping M *) let rec lookup pr = match pr with (d,[]) -> raise NotFound | (d,(e,r)::es) -> if d=e then r else lookup(d,es) (* insert(d,r,M) puts (d,r) in mapping M and removes any other pair (d,s) that was present in M *) let rec insert triple = match triple with (d,r,[]) -> [(d,r)] | (d,r,(e,s)::es) -> if d = e then (d,r)::es else (e,s)::insert(d,r,es) end;;(Note this example should probably use curried functions, not tuple arguments)
module Mapping : sig exception NotFound val create : 'a list val lookup : 'a * ('a * 'b) list -> 'b val insert : 'a * 'b * ('a * 'b) list -> ('a * 'b) list endObserve the syntactic details.
struct
... end
, and the declarations are pretty much what one can type into
the top loop (and, may even include other modules).
module
" (you cant say "let S = struct ..."
--structures aren't expressions)
sig ... end
.
signature
".
Structure internals may be referenced by qualified names
Mapping.insert
, etc:
# Mapping.insert(4,"tru",Mapping.create);; - : (int * string) list = [4, "tru"] # let m1 = Mapping.insert(4,"tru",Mapping.create);; val m1 : (int * string) list = [4, "tru"] # let m2 = Mapping.insert(44,"ru",m1);; val m2 : (int * string) list = [4, "tru"; 44, "ru"] #The whole structure may be made available at the "top level" of the namespace by the declaration
open Mapping
:
# open Mapping;; # insert;; - : 'a * 'b * ('a * 'b) list -> ('a * 'b) list = <fun>Built-in Caml modules: the standard library.
Array
,
Int32
, Char
, List
.
Stack
, Map
,
Set
, Queue
, Hashtbl
, Sort
Filename
, Sys
(system interface), etc.
# module type INSERTMAPPING = sig exception NotFound val create : 'a list val insert : 'a * 'b * ('a * 'b) list -> ('a * 'b) list end;; module type INSERTMAPPING = sig exception NotFound val create : 'a list val insert : 'a * 'b * ('a * 'b) list -> ('a * 'b) list endThis (stupid) signature leaves out the
lookup
operation
-- this is how internal functions can be hidden. You probably
wouldn't want to hide lookup
but you get the idea.Once you have declared a signature, you can restrict a structure to that signature as follows:
# module InsertOnlyMapping = (Mapping : INSERTMAPPING);; module InsertOnlyMapping : INSERTMAPPING # InsertOnlyMapping.insert;; - : 'a * 'b * ('a * 'b) list -> ('a * 'b) list = <fun> # InsertOnlyMapping.lookup;; (* this guy is hidden *) Characters 0-24: Unbound value InsertOnlyMapping.lookupThe above module includes some functions, a value, and an exception declared. Modules will often also include type declarations, as we will see later.
let lt(x,y) = String.lowercase x < String.lowercase y module StringBST = struct type 'label btree = Empty | Node of 'label * 'label btree * 'label btree let rec lookup(x, tree) = match tree with Empty -> false | Node(y,left,right) -> if lt(x,y) then lookup(x, left) else if lt(y,x) then lookup(x, right) else (* x=y *) true let rec insert(x, tree) = match tree with Empty -> Node(x,Empty,Empty) | Node(y,left,right) as t -> if lt(x,y) then Node(y,insert(x,left),right) else if lt(y,x) then Node(y,left,insert(x,right)) else (* x=y *) t (* do nothing; x was already there *) exception EmptyTree (* deletemin(T) returns a pair consisting of the least element y in tree T and the tree that results if we delete y from T. It is an error if T is empty *) let rec deletemin l = match l with Empty -> raise EmptyTree | Node(y,Empty,right) -> (y,right) (* The critical case. If the left subtree is empty, then the element at current node is min. *) | Node(w,left,right) -> let (y,l) = deletemin(left) in (y, Node(w,l,right)) let rec delete(x, tree) = match tree with Empty -> Empty | Node(y,left,right) -> if lt(x,y) then Node(y,delete(x,left),right) else if lt(y,x) then Node(y,left,delete(x,right)) else (* x=y *) match (left,right) with (Empty,r) -> r | (l,Empty) -> l | (l,r) -> let (z,r1) = deletemin(r) in Node(z,l,r1) end;; val lt : string * string -> bool = <fun> module StringBST : sig type 'a btree = Empty | Node of 'a * 'a btree * 'a btree val lookup : string * string btree -> bool val insert : string * string btree -> string btree exception EmptyTree val deletemin : 'a btree -> 'a * 'a btree val delete : string * string btree -> string btree end
StringBST.Empty : 'a StringBST.btree
.
lt
had
better be defined already. So, the structure isn't explicitly
declaring its imports: BAD.
StringLess
which defines lt
.
# module type STRINGLESS = sig val lt : string * string -> bool end;; module type STRINGLESS = sig val lt : string * string -> bool end # module StringLess : STRINGLESS = (* can give signature at declaration time *) struct let lt(x,y) = String.lowercase x < String.lowercase y end;; module StringLess : STRINGLESS # module StringBSTFunctor= functor (StringLt : STRINGLESS) -> struct type 'label btree = Empty | Node of 'label * 'label btree * 'label btree let rec lookup(x, tree) = match tree with Empty -> false | Node(y,left,right) -> if StringLt.lt(x,y) then lookup(x, left) else if StringLt.lt(y,x) then lookup(x, right) else (* x=y *) true let rec insert(x, tree) = match tree with Empty -> Node(x,Empty,Empty) | Node(y,left,right) as t -> if StringLt.lt(x,y) then Node(y,insert(x,left),right) else if StringLt.lt(y,x) then Node(y,left,insert(x,right)) else (* x=y *) t (* do nothing; x was already there *) exception EmptyTree (* deletemin(T) returns a pair consisting of the least element y in tree T and the tree that results if we delete y from T. It is an error if T is empty *) let rec deletemin l = match l with Empty -> raise EmptyTree | Node(y,Empty,right) -> (y,right) (* The critical case. If the left subtree is empty, then the element at current node is min. *) | Node(w,left,right) -> let (y,l) = deletemin(left) in (y, Node(w,l,right)) let rec delete(x, tree) = match tree with Empty -> Empty | Node(y,left,right) -> if StringLt.lt(x,y) then Node(y,delete(x,left),right) else if StringLt.lt(y,x) then Node(y,left,delete(x,right)) else (* x=y *) match (left,right) with (Empty,r) -> r | (l,Empty) -> l | (l,r) -> let (z,r1) = deletemin(r) in Node(z,l,r1) end;; module StringBSTFunctor : functor (StringLt : STRINGLESS) -> sig type 'a btree = Empty | Node of 'a * 'a btree * 'a btree val lookup : string * string btree -> bool val insert : string * string btree -> string btree exception EmptyTree val deletemin : 'a btree -> 'a * 'a btree val delete : string * string btree -> string btree endObservations about functor declarations:
functor .. end
instead of struct ... end
# module StringBST = StringBSTFunctor (StringLess) (* apply functor to make structure *) ;; module StringBST : sig type 'a btree = 'a StringBSTFunctor(StringLess).btree = Empty | Node of 'a * 'a btree * 'a btree val lookup : string * string btree -> bool val insert : string * string btree -> string btree exception EmptyTree val deletemin : 'a btree -> 'a * 'a btree val delete : string * string btree -> string btree end
Type declarations are particularly useful in signatures: you can hide the details of a type by declaring
type 'a btreealone in the signature, hiding what
'a btree
really is (this makes
btree abstract)
:
# module type STRINGBST = sig type 'a btree (* hide the details of the btree type in the signature *) val lookup : string * string btree -> bool val insert : string * string btree -> string btree exception EmptyTree val deletemin : 'a btree -> 'a * 'a btree val delete : string * string btree -> string btree end;; module type STRINGBST = sig type 'a btree val lookup : string * string btree -> bool val insert : string * string btree -> string btree exception EmptyTree val deletemin : 'a btree -> 'a * 'a btree val delete : string * string btree -> string btree end # module AbstractStringBST = (StringBST : STRINGBST);; module AbstractStringBST : STRINGBST #In the above example, we applied the functor
StringBSTFunctor
and then hid some elements of the
result. What you probably want to do is fix
StringBSTFunctor
to always produce the abstract type in
the result. That is done as follows.
# module AbstractStringBSTFunctor = (StringBSTFunctor : functor(Lt:STRINGLESS) -> STRINGBST);; module AbstractStringBSTFunctor : functor (Lt : STRINGLESS) -> STRINGBST
lt
operator above.
.ml
file, everything you type is
implicitly wrapped in a struct .. end: it defines a struct. File
snork.ml
always makes a structure named
Snork
, so the naming is implicit.
.mli
file, its the analogous concept for a
signature: everything you type is implicitly wrapped in a sig
.. end
. (You
don't need an .mli
file for each .ml
file: its inferred if its not there)
.ml
files are analogous to
.c/.cc/.java
files, and .mli
files,
analogous to .h
files.
.ml
files compile to .cmo
object files,
analogous to how .c/.java
files produce .o/.class
files. .mli
files are also compiled
(unlike .h
files) and produce .cmi
files.
stack.ml
implementing
stacks in the ocaml library:
type 'a t = { mutable c : 'a list } exception Empty let create () = { c = [] } let clear s = s.c <- [] let copy s = { c = s.c } let push x s = s.c <- x :: s.c let pop s = match s.c with hd::tl -> s.c <- tl; hd | [] -> raise Empty let top s = match s.c with hd::_ -> hd | [] -> raise Empty let is_empty s = (s.c = []) let length s = List.length s.c let iter f s = List.iter f s.cAnd, here is the interface file,
stack.mli
:
type 'a t exception Empty val create : unit -> 'a t val push : 'a -> 'a t -> unit val pop : 'a t -> 'a val top : 'a t -> 'a val clear : 'a t -> unit val copy : 'a t -> 'a t val is_empty : 'a t -> bool val length : 'a t -> int val iter : ('a -> unit) -> 'a t -> unitNotice the interface hides the type
'a t
and so outsiders will not be
able to directly manipulate the stack data structure.
ocamlc
% ocamlc stack.mlwill make file stack.cmo, and
% ocamlc stack.mliwill make file stack.cmi. If there is no .mli file, compiling the
.ml
file will make both the
.cmi
and .cmo
files.
main
defined in it. Then,
% ocamlc myprog.ml -o myprogwill make files myprog.cmi, myprog.cmo, and myprog. Then,
./myprog
will execute main
.
open MyStruct
at the top of a
.ml
file will open that structure, so you won't
need to write
MyStruct.myfunction
(similar to use
of Java).
ocamlc
produces bytecode which is interpreted.
There is also ocamlopt
which is an optimizing
compiler that produces much faster binary code.