tags : Functional Programming, Programming Languages
In the early ’80s, there was a schism in the ML community with the French on one side and the British and US on another. The French went on to develop CAML and later Objective CAML (OCaml) while the Brits and Americans developed Standard ML. The two dialects are quite similar. Microsoft introduced its own variant of OCaml called F# in 2005.
Intro
- Objective Categorical Abstract Machine language
- ML: Meta Language
Mutability
- OCaml is primarily an immutable language, like most functional languages.
- It does support imperative programming with mutable state, but we can probably not use it
Types
- Infer (Type checker will already do this)
- Annotate (Additional constraint for the type checker)
- Eg.
(e:t)
: Parentheses are required for type annotation
- Eg.
Building blocks
Syntax gotchas
- A design choice is made not to overload operators, eg.
*
and*.
are different operators for ints and floats. - If statements explicitly want boolean guards and branches need to be of same type
- use
camelCase
for identifiers - In ocaml we say, we
apply
a function thancall
it. - Equality
=
and<>
examine structural equality whereas (This is what we want to use)==
and!=
examine physical equality.
begin..end
are equivalent to()
Functions
- Functions are
values
and the body of the function is not evaluated unless applied.- Functions can be used as
arguments
- Functions can be returned as
result
-
Anonymous Function (Lambda expressions)
- syntax:
fun x1 ... xn -> e
- Eg. :
fun x -> 42
- syntax:
-
Named
let poop x = x+1;
let poop = fun x -> x+1;
-
Recursive
let rec poop = poop;
- Mutually recursive functions can be written using
and
. 3110 book has a funny odd even example in Chapter2.4
-
No multi argument functions
- It might seem we can pass multiple arguments to ocaml, but that’s not what actually happens
fun x y -> e
is syntactic sugar forfun x -> (fun y -> e)
let add x y = x +y
is syntactic sugar forlet add = fun x -> fun y -> x +y
-
Polymporphic Functions
- Eg.
let id x = x;;
'a
: alpha (A type variable)
- Eg.
Operators
- Defining
infix
binary operators, we must wrap the operator in parens.- Eg.
let ( <^> ) x y = max x y;;
, whilelet <^> x y = max x y;;
will give you errors. btw not using simply^
because that’s the concat operator in ocaml.
- Eg.
- To define
infix
operators, you must surround them in parens
Semantic gotchas
Others
Unit
values: Eg.assert e
evaluates to aunit
value and nothing happens ife
istrue
- For the
Unit
type, there’s only one value:()
- It’s usually using code that has side effects. Printing is an example of a side effect: it changes the world and can’t be undone.
- Special syntax that can be used to chain together multiple functions that return unit.
- The expression
e1; e2
first evaluatese1
, which should evaluate to()
, then discards that value, and evaluatese2
. - This can be used to chain multiple print statements together which usually evaluate to
Unit
.
- The expression
- For the
Value and Expression
Values
Do not need any further evaluation
Expressions
- Syntax rules
- Semantics
- Type checking rules
- Evaluation checking rules
-
let
expression- This give a notion of scope
- Eg.
let x = 2 in x
(let expressions, we can use this inside other expressions) - Here we’re binding a value to
x
- Outside of this expression it’ll not be bound to
x
whereas in case oflet
definition, it’ll be bound to.
-
nested
let
expression w same variable namelet x = 5 in (let x = 6 in x)
- What this expression is evaluate to is the last
x
. - In ocaml, things are not mutable. So,
x
will not be redefined. - Order is left to right
let x = 5 in ...
: Alright, x is assigned 5 for that scopelet x = 6 in x
: A new x is assigned here, and evaluated tox
x
is returned. we get 6.(from theinnermost scope
). i.elet x = 5 in 6
- But we never used the older
x
. So the olderx
remain unused. - In other words, ocaml will not let us substitute the older value of
x
in the nested expression because it’ll violate Principle of Name Irrelevance
- It’s usually good not to mix the same variable name like this. You cannot get to the top level from the inner most level anyway for that
x
.
Definitions
let
definition
- These are like variable
- These are NOT expressions, it does not have a value itself. (
let
expression andlet
definition are different things) - Eg.
let x = 2
(let definition)- This implicitly means,
let x = 2 in "rest of what i type"
- When we do
x=4;;x=7
etc. we’re not mutatingx
, we’re defining newx
in a new scope. (So if you do this in ocaml toplevel/utop, you won’t be able to access previously definedx
but in files and programs we’ll be do so accordingly)
- This implicitly means,
Builtin Data Types
Lists
- can be
nil
or wecons
- These are recursive and parameterized(takes any type) list
#show list
- Operations
- Prepend
- Constant time
O(1)
'a -> 'a list -> 'a list
- []~
::
is used for construct objects in memory (cons
)::
is a binary constructor, takes'a
and'a list
::
is an infix constructor(the only one)- 1st element is
head
, rest of the elements aretail
- []~
- Constant time
- Appending
- Linear time
O(n)
'a list -> 'a list -> 'a list
[88] @ [77]
, this is not recommended in the hot path because lists are implemented as a singly linked list.- The
@
is the append operator, not related to the apply operator
- Linear time
- Prepend
Records
with
: Allows record copy. eg.{poop with name = "something else"}
. Does not allow you to add new fields as that would change the type of the record.
Tuple
type time = int * int * string
let t = (10, 10, "am")
- Order is relevant
fst
andsnd
let you access tuple which are pairs
Features
Pattern Matching
match e with
| 2 -> e0
| p1 -> e1
| p2 -> e2
| pi -> ei
| _ -> 1
(* - p1, p2, p3 are patern variables so will be bound to e1, e2, e3 *)
(* - _ will not be bound *)
- Can be used for matching with
types, ints, strings, lists, records, tuples
- Pattern variables: Things that go after
|
afterwith
- What it allows
- Match is the shape of the data
- Extract/Transform parts of the data
- Typechecking
e
andpi
need to have same type- Whole match would have type of
ei
. Allei
need to be of same type
Syntax Sugars
- For pattern matching with the
last argument
, we can use thefunction
keyword.
Pipeline operator
Pipeline (Reverse Application operator)
succ (square (square (succ 3)));;
- v/s
5 |> succ |> square |> square |> succ;;
Custom Types
type primary = Red | Green | Blue
type 'a tree = Lf | Br of 'a * 'a tree * 'a tree;; (* here, 'a is type param/variable, tree is the name of the type *)
Variants
- See Type Systems
- Value that could be one of several possibilities
- Each value is called a
constructor
and is separated with|
(the use of word constructor is diff. from languages like c++)constructor
name must begin with Uppercaseconstructor
is avalue
contructor
can carry along optional data with it using theof
keywordcontructior
akatags
- Accessing variants can only happen via Pattern Matching
constructor
can be constants or non-constants(which carry some value usingof
)- Variant Type
Options
list
andoption
are type constructors. They construct other types but are not types themsleves.- eg.
int list
,a' list
,int option
and so on
- eg.
- For options, we can use
Some
andNone
foroption
- I think
Some
andNone
are variant walaconstructors
- I think
- Syntax& Semantics
None
is a value of type'a option
Some e
is an expression of typet option
ife : t
. Ife ==> v
thenSome e ==> Some v
Exceptions
Exception vs Options
- Exceptions make it easy to pipeline operations
Higher order functions
- Higher-order functions either take other functions as input or return other functions as output (or both).
- What differentiates this from things like function pointers in C is the idea of not having to pass the exta param of values of the variable to be used in the program. This is taken care of by the concept of
closure
in functional languages. - About
fold
fold_left (+) 0 [1; 2; 3] <=> (((0 + 1) + 2 + 3) // zero is on the left fold_right (+) [1; 2; 3] 0 <=> 1 + (2 + (3 + 0))) // zero is on the right
Modularity
scope and open
let x = ListStack.peek (ListStack.push 42 ListStack.empty)
let x' = ListStack.(peek(push 42 empty))
let x'' = ListStack.(empty |> push 42 |> peek)
let x''' = let open ListStack in empty |> push 42 |> peek
(* Following is discouraged cuz pollute global scope *)
open ListStack
let v = empty |> push 42 |> peek
Signatures
- These is sort of how we would implement interface
- Here
Mt
is asig
- This also provides encapsulation in the way that you can define functions which are not in the signature in the struct but if you try accessing those method from the
module
directly, you’ll get a typechecking error - In signature matching, it checks if the
Mod
suffices theSig
. If the function inMod
can take more than what Sig allows it’s okay, but it must atleast satisfySig
. - Abstract Types
- Usually denoted by
t
Constraints
can be added with things likemodule type IntRing = Ring with type t = int
module type T = sig type t end module type U = T with type t = int
- Usually denoted by
- We can
seal
aSig
to a module using:module <NewSealedModName> : <SigName> = <UnsealedMod that Implements Sig>
- We can use
include
to includesignature
(declarations) orstructures
(definitions)
Compilation Units
- DM : Definitions and Modules
- DS : Declarations and Signatures
include vs open
Functors
Module
values are not like regular ocaml values. Most of the things like returning a module from a function etc will not work.- But we have
Functor
, which is amodule
level function. Input is a module, Output is a module. - Type annotation is mandatory when writing a functor
Mutability
ref
Environment
Tooling
- opam : package manager
- ocamlopt : native code compiler
- ocamlc : bytecode compiler (less used)
- toplevel/utop
- When using
#use
exit and reload the toplevel if debugging, sometimes previous load overloads or something. -:
in utop means it’s an anonymous definition
- When using
Links and Resources
Books
- A Guided Tour - Real World OCaml
- OCaml from the Very Beginning
- http://courses.cms.caltech.edu/cs134/cs134b/book.pdf
Notes from 3110
TODO
- Skipping TDD: https://cs3110.github.io/textbook/chapters/data/ounit.html#test-driven-development
- Don’t understand: 5.7.2. Constraints
- Skipped: 5.6.6. Sets and Maps implementation
Scratch notes
utop # let pp = Fmt.Dump.list Fmt.int ;;
val pp : int list Fmt.t = <fun>
utop # Format.printf "%a" pp [1;2;3] ;;
[1; 2; 3]- : unit = ()
pp above should be usable with ounit2. The printer type (Fmt.t which is Format.formatter -> ‘a -> unit) is very standard in OCaml, based on the stdlib Format module.