blah | blahblah
takes the output of blah
and feeds it as input to blahblah
cat blah.tar.gz | gunzip | tar tf - | cat | grep .md
|>
# (|>);;
- : 'a -> ('a -> 'b) -> 'b = <fun>
# 5 |> (fun x -> x - 20);; (* it is really just new syntax for function application *)
- : int = -15
# [] |> (List.cons 5) |> (List.cons 7) |> (fun x -> x) |> List.length;;
- : int = 2
cat
in *nix is like fun x -> x
in OCamlWe will roughly follow the Cornell notes
List.fold_right
in OCaml “folds” a list to a single element via an operation.List.fold_right ( * ) [2; 5; 7] 1
is 2*(5*(7*(1)))
, i.e. 70
. The “right” refers to the zero (1
here) being on the right, not the left (it doesn’t matter in the case of multiplication as it is commutative and associative).
Here is roughly how List.fold_right
is implemented:
let rec fold_right op lst init = match lst with
| [] -> init
| h::t -> op h (fold_right op t init)
let summate lst = List.fold_right (+) lst 0
let concatenate lst = List.fold_right (^) lst ""
summate
as a recursive function isn’t going to be much longer, but it gets the code on a “higher level” so is often good practice.List.fold_left ( * ) 1 [2; 5; 7] (* note the zero and the list swapped in arg list vs folds_right *)
is (((1)*2)*5)*7
, i.e. 70
.
let rec fold_left op accum lst = match lst with
| [] -> accum
| h::t -> fold_left op (op accum h) t
let summate lst = List.fold_left (+) 0 lst
let concatenate lst = List.fold_left (^) "" lst
List.fold_left
by default, it is more efficientSome non-assoc/commut operator:
List.fold_right (-) [2; 5; 7] 0 (* 2-(5-(7-0))) = 4; not very useful *)
List.fold_left (-) 0 [2; 5; 7] (* ((0-2)-5)-7 = -14 *)
Here are some pleasant examples mostly from the Cornell notes illustrating the power of fold.
let length l = List.fold_left (fun a _ -> a+1) 0 l
let rev l = List.fold_left (fun a x -> x::a) [] l (* e.g. rev [1;2;3] = (3::(2::(1::[]))) *)
let map f l = List.fold_right (fun x a -> (f x)::a) l []
let map_rev f l = List.fold_left (fun a x -> (f x)::a) [] l (* to contrast left and right fold *)
let filter f l = List.fold_right (fun x a -> if f x then x::a else a) l []
type 'a btree =
| Leaf
| Node of 'a * 'a btree * 'a btree
let rec treefold init op = function
| Leaf -> init
| Node (v,l,r) -> op v (treefold init op l) (treefold init op r)
let size t = treefold 0 (fun _ l r -> 1 + l + r) t
let depth t = treefold 0 (fun _ l r -> 1 + max l r) t
let preorder t = treefold [] (fun x l r -> [x] @ l @ r) t
let tex = Node (4, Node (5, Node (6, Leaf, Leaf), Leaf), Node (4, Leaf, Leaf))
size tex
type function btreefun('mytype)('a) = Leaf | Node of 'a * 'mytype('a) * 'mytype('a) (* takes self-type as arg *)
type 'a btree = btreefun btreefun 'a (* type fixed point via self-type passing *)
By feeding in the “accumulator type” for 'mytype
here we can obtain the type of one layer of the fold
type summer = btreefun int int (* assume we are summating over an integer-node tree *)
Then we can write a function over this type which does that one layer:
let (treesummer : summer -> int) = function
| Leaf -> 0
| Node (v,laccum,raccum) -> v + laccum + raccum
And the folder can be generic over any such type – it takes this non-recursive code to perform the fold operation.
Here is a kind of struct that we can take as a parameter
type comparison = LessThan | EqualTo | GreaterThan
module type ORDERED_TYPE =
sig
type t
val compare: t -> t -> comparison
end
Here is a functor version of a set, you feed in a struct with the set element ordering defined on it
module FSetFunctor =
functor (Elt: ORDERED_TYPE) ->
struct
type element = Elt.t (* import the type of elements from the module *)
type set = element list
let empty = []
let rec add x s =
match s with
[] -> [x]
| hd::tl ->
match Elt.compare x hd with
EqualTo -> s
| LessThan -> x :: s
| GreaterThan -> hd :: add x tl
let rec contains x s =
match s with
[] -> false
| hd::tl ->
match Elt.compare x hd with
EqualTo -> true
| LessThan -> false
| GreaterThan -> contains x tl
end
Here is a concrete ordering we can feed in, one over ints
module OrderedInt =
struct
type t = int
let compare x y =
if x = y then
EqualTo
else
if x < y then
LessThan
else
GreaterThan
end
Here is how we feed it in, instantiating the functor to give a module
module OrderedIntSet = FSetFunctor(OrderedInt)
Example of using the resulting module
let myOrderedIntSet = OrderedIntSet.add 5 OrderedIntSet.empty
OrderedIntSet.contains 3 myOrderedIntSet
We can do the same thing for a string comparison
module OrderedString =
struct
type t = string
let compare x y =
if x = y then EqualTo
else if x < y then LessThan
else GreaterThan
end
module OrderedStringSet = FSetFunctor(OrderedString) (* a DIFFERENT instantiation of same *)
let myOrderedStringSet = OrderedStringSet.add "abc" OrderedStringSet.empty
Functors also have signatures; there can also be type abstraction in a functor signature
module type SETFUNCTOR = (* below is the syntax for a signature of a functor *)
functor (Elt: ORDERED_TYPE) ->
sig
type element = Elt.t (* concrete *)
type set (* abstract *)
val empty : set
val add : element -> set -> set
val contains : element -> set -> bool
end
module AbstractSet = (FSetFunctor : SETFUNCTOR) (* slap that sig on a functor *)
module AbstractIntSet = AbstractSet(OrderedInt)
AbstractIntSet.add 5 AbstractIntSet.empty
Stdlib.Set
uses functors similar to how our simplistic set works, let us review it – manpage
Usage example
module Ints =
struct
type t = int
let compare x0 x1 =
Stdlib.compare x0 x1
end
module IntsSet = Set.Make(Ints) (* Set.Make is a functor *)
let m = IntsSet.(empty |> add 3 |> add 22 |> add 76)
Real World OCaml has more involved examples
fbdk/src/application.ml
in FbDK is yet another example; e.g. Fb/fb.ml
uses a functor.
Idea: Lift modules to be true first-class data; supported in more recent OCaml versions.
Generalized Abstract Data Types (GADT’s) allow several more flexible uses of OCaml data types. See The Manual for the details.
Review: roll-your-own lists using existing OCaml types
type 'a oldlist = Nil | Cons of 'a * 'a oldlist
Equivalent way to do list type using new generic type syntax (but using it to do old thing)
type _ newlist = Nil : 'a newlist | Cons : 'a * 'a newlist -> 'a newlist
oldlist
type.Lets now show some added power of this new notation (toy example).
type _ newlist = Nil : 'a newlist
| IntNil : int newlist
| Cons : 'a * 'a newlist -> 'a newlist
We now have a new IntNil empty list, which is only consable on to integers:
# Cons(5,IntNil);;
- : int newlist = Cons (5, IntNil)
Note however that “IntNil” is just a name, the int newlist type could have been something else:
type _ newlist = Nil : 'a newlist
| IntNil : string newlist (* yes this is perfectly legal *)
| Cons : 'a * 'a newlist -> 'a newlist
# Cons(5,IntNil);;
Error: This expression has type string newlist
but an expression was expected of type int newlist
Type string is not compatible with type int
In other words, the “IntNil” is just a name. It is axiomatically (by fiat) making certain constructors have certain newlist types. Think of it as something like making an operational semantics rule set. Here is an arbitrary example to illustrate.
type _ dough =
Coin : 'a dough (* base case (axiom) *)
| Add : 'a dough -> 'a dough (* rule: have dough, can make more same type of dough *)
| Bump : int dough -> float dough (* rule: can turn int dough to float dough *)
| Morph : 'a dough -> 'b dough (* rule: can turn any kind of dough to any other *)
| Intbux : int -> int dough (* rule: with a number some int dough can be made *)
| Intcomb : int * int dough -> int dough (* rule: use int and int dough to make int dough *)
You can still do whatever combinations will type, e.g.
# Bump(Add(Coin));;
- : float dough = Bump (Add Coin)
# Bump(Morph(Bump(Add(Intcomb(4,Coin)))));;
- : float dough = Bump (Morph (Bump (Add (Intcomb (4, Coin)))))
Along with the new syntax for type declarations is new syntax for polymorphic (for all / generic) types.
let id: type t. t -> t = fun x -> x;;
val id : 't -> 't = <fun>
Here is an example from the manual showing how some useful work can be done.
type _ typ =
| Int : int typ (* axiom: make an int type *)
| String : string typ (* axiom: can make a string typ *)
| Pair : 'a typ * 'b typ -> ('a * 'b) typ (* rule: from any two typ's can make their product *)
Observe first that this GADT typ
is not the same as the “old OCaml” equivalent,
type atype = OInt | OString | OPair of atype * atype
– the old types are much simpler, there is no type parameter; compare:
# OPair(OInt,OString);; (* old *)
- : atype = OPair (OInt, OString) (* no parameter on atype *)
# Pair(Int,String);; (* new *)
- : (int * string) typ = Pair (Int, String) (* observe parameter on typ *)
The interesting and useful bit about this seemingly-useless difference is this type parameter on typ
can be used to type otherwise untypeable code:
let rec to_string: type t. t typ -> t -> string = (* notice need to declare forall types! *)
fun tv x ->
match tv with
| Int -> string_of_int x
| String -> Printf.sprintf "%S" x
| Pair(t1,t2) ->
let (x1, x2) = x in
Printf.sprintf "(%s,%s)" (to_string t1 x1) (to_string t2 x2);;
val to_string : 't typ -> 't -> string = <fun>
typ
is giving a run-time name, Int
which in the type of to_string
adds an implicit constraint that the second argument must be an integer (cool, eh?).match
above cannot be typed at a fixed type for tv
like int typ
# to_string Int 5;; (* Int has type int typ and 5 is of type int -- a match *)
- : string = "5"
# to_string Int "oops";; (* Int has type int typ and "oops" is of type string -- mismatch *)
Error: This expression has type string but an expression was expected of type
int
If the type of to_string
were the more standard OCaml 'a typ -> 'a -> string
this path-sensitivity would not be allowed:
let rec too_string: 'a typ -> 'a -> string =
fun tv x ->
match tv with
| Int -> string_of_int x
| String -> Printf.sprintf "%S" x
| Pair(t1,t2) ->
let (x1, x2) = x in
Printf.sprintf "(%s,%s)" (to_string t1 x1) (to_string t2 x2);;
Error: This pattern matches values of type string typ
but a pattern was expected which matches values of type int typ
Type string is not compatible with type int
'a
must be an integer, but that conflicts with the second case. GADTs allow each branch to have it’s own type.let int_to_string: int typ -> int -> string =
fun t x ->
match t with
| Int -> string_of_int x
Here is another simple example of suspended application.
type _ wait = Wait : 'arg * ('arg -> 'result) -> 'result wait
let foo (x : bool) = if x then 0 else 1
let suspended_value : int wait = Wait(true, foo)
let other_suspended_value : int wait = Wait("5", int_of_string)
let run_it w = match w with Wait(arg,func) -> func arg
let doit = run_it suspended_value
'arg
is not an exposed parameter in the wait type like 'result
iswait
type.Wait
type but we could not make a list of the two int wait
values above as their 'arg
types differtype ('a, 'b) badwait = BadWait of 'a * ('a -> 'b);; (* notice how BOTH 'a and 'b exposed here *)
A practical library example: Gmap