Skip to content

Instantly share code, notes, and snippets.

@mjambon
Last active May 29, 2022 19:59
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save mjambon/5775807ee2d16f51392ec5936b67c3e5 to your computer and use it in GitHub Desktop.
Save mjambon/5775807ee2d16f51392ec5936b67c3e5 to your computer and use it in GitHub Desktop.
OCaml/Lwt crash course. Adult supervision recommended.
(*
Interactive approach
--------------------
You can copy-paste code into `utop`, provided you load the lwt.unix
package:
#use "topfind";;
#thread;; (* only needed in the plain `ocaml` toplevel, not in `utop`. *)
#require "lwt.unix";;
Each statement must be followed by `;;` to let utop know that you're done.
You can also load the whole file using `#use`:
#use "ocaml_lwt_sample.ml";;
Standalone executable
---------------------
You an compile this program into a standalone executable
with the following command:
ocamlfind opt -o ocaml_lwt_sample -package lwt.unix -linkpkg \
ocaml_lwt_sample.ml
Then run it:
./ocaml_lwt_sample
*)
(*** Basic syntax ***)
(* Compute an int and call it `four`. It is immutable. *)
let four = 2 + 2
let greeting = "Yo"
(* Function definition. One argument of type `unit`. *)
let hello () =
(* Call a function of one argument *)
print_endline greeting
(* Another variable `greeting` that shadows the first one only in the
code that follows. *)
let greeting = "Hello"
(* Syntax wart - this phony `let` is how we avoid sticking `;;` everywhere *)
let () = hello ()
(* Note that we printed `Yo`, not `Hello` *)
(* Function of multiple arguments *)
let add a b =
a + b
(* Partial application: `add2 x` calls `add 2 x` *)
let add2 = add 2
(* Often we avoid partial application for more clarity *)
let add3 x =
add 3 x
(* When several arguments have the same type or there are many arguments,
we label them. *)
let say_hello ~greeting ~name =
Printf.printf "%s %s.\n" greeting name
(* Calling functions with labeled arguments is accepted by the compiler
only if the labels are correct. Argument no longer matters since the
arguments are labeled. *)
let () = say_hello ~name:"there" ~greeting:"Hi"
(* Optional arguments *)
let say_hello2 ?(greeting = "Hi") ~name () =
Printf.printf "%s %s.\n" greeting name
(* We can omit optional arguments or not. *)
let () = say_hello2 ~name:"you" ()
let () = say_hello2 ~greeting:"Hello" ~name:"programmer" ()
(* Pattern-matching *)
let is_hello s =
match s with
| "Hello" | "hello" -> true
| _ (* otherwise *) -> false
(* Type definitions *)
type time = int (* an alias *)
(* A record type definition *)
type profile = {
name: string;
age: int;
}
(* Variants *)
type color = Green | Red
type fruit = Banana | Apple of color
(* Polymorphic variants: same as regular variants but they
don't need a type definition, although it's recommended to simplify
to error messages.
*)
type result = [ `OK | `Error of string ]
(* Parametrized types *)
type 'a stuff = (* equivalent to Stuff<A> in other languages *)
{
id: string;
items: 'a list
}
(* More pattern-matching *)
let give_me_a_result () : result (* optional type annotation *) =
`Error "This is a test"
let success =
match give_me_a_result () with
| `OK -> true
| `Error _ -> false
(* Lists - immutable singly-linked lists.
They're the default data structure for storing collections of items.
These are not suitable for random access.
A list is either the empty list denoted `[]`
or a pair of the first element called the head and the rest of the list
called the tail, e.g. `element :: other_elements`
*)
let list0 = 1 :: (2 :: (3 :: []))
let list1 = 1 :: 2 :: 3 :: [] (* same as list0 *)
let list2 = [1; 2; 3] (* same as list1 *)
(* More pattern-matching *)
let first_fruit_is_an_apple fruits =
match fruits with
| [] -> false
| Banana :: _ -> false
| Apple _ :: _ -> true
(* Simpler code that will break silently if an
`Apple2` case is added to the type definition later: *)
let fragile_first_fruit_is_an_apple fruits =
match fruits with
| Apple _ :: _ -> true
| _ -> false
(* Recursive functions require the `rec` keyword (but type definitions are
implicitly recursive). We don't need to write recursive functions
too often in "enterprise" code but this is how all iterators
over lists are defined, and sometimes it's better to write our own.
The following is the same as the standard `List.filter`.
*)
let rec filter predicate list =
match list with
| [] -> []
| head :: tail ->
if predicate head then
head :: filter predicate tail
else
filter predicate tail
(* Similar code that performs the tests from right to left instead
but otherwise returns the same result
(assuming `predicate` is stateless). *)
let rec filter2 predicate list =
match list with
| [] -> []
| head :: tail ->
let new_tail = filter predicate tail in
if predicate head then
head :: new_tail
else
new_tail
let is_even x =
x mod 2 = 0
let filtered_list = filter is_even [0; 2; 3; 4; 5; 88; 99]
(* Using an anonymous function *)
let filtered_list2 = filter (fun x -> x mod 2 = 0) [0; 2; 3; 4; 5; 88; 99]
(* Exercises:
1. Implement the `iter` function, which takes a function and a list,
and applies the function to each element of the list from
left to right:
iter print_endline ["a"; "b"; "c"]
must print:
a
b
c
2. Define your own list type as a variant type
without the special syntax `[]` and `::`.
3. Modify your `iter` function to work on your own list type instead.
*)
(* The built-in option type
Defined as:
type 'a option = None | Some of 'a
*)
let obtain_value default_value optional_value =
match optional_value with
| None -> default_value
| Some x -> x
(* Optional arguments without a default use the option type *)
let show_optional_arg ?x () =
x
(* Exceptions
Exceptions are of the type `exn` which is a special variant type
than can be extended with new cases.
*)
exception Fishsticks of string
(* Now `Fishsticks "uh oh"` is a valid value for the type `exn`. *)
(* Catching exceptions *)
let found =
try
Some (List.find (fun x -> x < 0) [1;2;3])
with Not_found ->
None
(* Tuples *)
let some_stuff = (123, "abc", None)
(*** Mutable stuff ***)
(* Records with mutable fields: not often used directly *)
type point = {
mutable x: int;
mutable y: int;
}
let p =
let p = { x = 0; y = 0 } in
p.x <- 123;
p
(* References: a single mutable cell.
Predefined as:
type 'a ref = { mutable contents : 'a }
References come with 2 handy set/get operators `:=` and `!`,
plus `incr` and `decr` to operate on counters.
*)
let counter = ref 0
let () =
counter := 10
let ten = !counter
let () =
counter := !counter + 1
let eleven = !counter
let () = incr counter
let twelve = !counter
(* Assertions *)
let () =
assert (ten = 10);
assert (eleven = 11);
assert (twelve = 12)
(* Arrays: for efficient random access and mutability *)
let some_array = [| 123; 45; 678 |]
let fortyfive = some_array.(1)
(*** Modules ***)
(*
Each .ml source file results in a module after capitalization.
The standard library has a source file `printf.ml`.
*)
open Printf
let say_hello3 ?(greeting = "Hello") name =
(* instead of Printf.printf *)
printf "%s %s.\n" greeting name
(* We can also define submodules as follows *)
module Op = struct
let (=) a b =
String.lowercase a = String.lowercase b
end
let result1 = "Pistachio" = "pistachio" (* false *)
let result2 = Op.("Pistachio" = "pistachio") (* true *)
(* Same as result2 definition, alternative syntax *)
let result3 =
let open Op in
"Pistachio" = "pistachio"
(*** Asynchronous programming with Lwt ***)
(* Lwt is a regular OCaml library that supports a "cooperative
threads" model, similar to JavaScript.
Each computation is called a promise (formerly "thread"), but only
one promise can run at once. A promise is typically defined as an
anonymous function that will be called after the result of some
other promise becomes available, with a possible delay.
A promise is an opaque object of type `'a Lwt.t`, representing the
asynchronous computation of a value of type 'a.
Which promise runs at a given time is determined by a scheduler,
which is launched by the function `Lwt_main.run`.
When a promise terminates ("resolves"), it is in either of these
states: - successful, holding a result - failed, holding an
exception
Waiting on the promise is done using the bind operator `(>>=)` which
is the same function as `Lwt.bind`. *)
(* Make `(>>=)` available. *)
open Lwt
(* Sleep 1.5 seconds, then make the result `()` available. *)
let wait_for_a_while () =
Lwt_unix.sleep 1.5
let () =
Lwt_main.run (wait_for_a_while ())
(* `Lwt.return` wraps an OCaml value into a resolved lwt promise *)
let print_message_after_a_while () =
wait_for_a_while () >>= (fun () -> print_endline "done"; Lwt.return ())
(*
Several promises can wait on a given promise.
There's no guarantee on the order in which promises 1,2,3 will run.
Worse, the promises 1 and 2 are ignored, i.e. the main loop
`Lwt_main.run` won't wait for them. If promise 3 finishes first,
`1` and `2` won't be printed.
*)
let print_messages_after_a_while_v1 () =
let timer = wait_for_a_while () in
ignore (timer >>= fun () -> print_endline "1"; Lwt.return ());
ignore (timer >>= fun () -> print_endline "2"; Lwt.return ());
timer >>= fun () -> print_endline "3"; Lwt.return ()
let () =
print_endline "print_messages_after_a_while_v1";
Lwt_main.run (print_messages_after_a_while_v1 ())
(* Better, make sure to wait for the 3 promises.
Additionally, we print a message when we're done with all 3 promises. *)
let print_messages_after_a_while_v2 () =
let timer = wait_for_a_while () in
let t1 = timer >>= fun () -> print_endline "1"; Lwt.return () in
let t2 = timer >>= fun () -> print_endline "2"; Lwt.return () in
let t3 = timer >>= fun () -> print_endline "3"; Lwt.return () in
Lwt.join [t1; t2; t3] >>= fun () ->
print_endline "all done";
Lwt.return ()
let () =
print_endline "print_messages_after_a_while_v2";
Lwt_main.run (print_messages_after_a_while_v2 ())
(* Exceptions are propagated along bind points. If a promise B waits for its
input from another promise A but A results in an exception, then
B resolves to the same exception. *)
let make_promise_that_fails () =
Lwt_unix.sleep 0.1 >>= fun () ->
failwith "Uh oh" (* raises the exception `Failure "Uh oh"` *) >>= fun () ->
print_endline "This never happens.";
Lwt.return ()
let report_error promise_name make_promise =
Lwt.catch
(fun () -> make_promise ())
(fun e ->
Printf.eprintf "Expected exception in promise %s: %s\n"
promise_name
(Printexc.to_string e);
return ()
)
let () =
let promise = report_error "promise-that-fails" make_promise_that_fails in
Lwt_main.run promise
@erikmd
Copy link

erikmd commented Apr 13, 2021

Hi @mjambon, it seems with more-or-less recent versions of ocaml/lwt, it's necessary to run #thread;; before Line #require "lwt.unix";;
Nice tutorial by the way! 👍

@mjambon
Copy link
Author

mjambon commented Apr 13, 2021

Hi @erikmd! I forgot this existed... but where do you get a sense that #thread;; is necessary? The newer terminology is "promise" like in javascript rather than "thread" (as in LightWeight Threads). #thread is for using system threads, which are usable with lwt but otherwise independent and generally tricky to use. They shouldn't be needed in this example. I tested with ocaml 4.10.0 and lwt 5.3.0.

@erikmd
Copy link

erikmd commented Apr 13, 2021

Thanks @mjambon for your reply! I did not check every combination of ocaml/lwt versions;
basically when using ocaml 4.05.0 (the current version required by learn-ocaml) and lwt 4.5.0 in Debian GNU/Linux, I got the session:

$ ocaml
        OCaml version 4.05.0

# #use "topfind";;        
…
# #require "lwt.unix";;
…  
Error: Reference to undefined global `Mutex'

so as suggested in this issue: ocsigen/lwt#533 (comment), I tried:

$ ocaml
        OCaml version 4.05.0

# #use "topfind";;        
…
# #thread;;
/home/REDACTED/_opam/lib/ocaml/threads: added to search path
/home/REDACTED/_opam/lib/ocaml/unix.cma: loaded
/home/REDACTED/_opam/lib/ocaml/threads/threads.cma: loaded
# #require "lwt.unix";;

and the issue went away :)


IIUC, the #thread;; directive (which is related to ocamlc's CLI option -thread) was implied for some earlier versions of ocaml as soon as the threads.cma library was loaded… (I'm puzzled that you didn't reproduce the error I mentioned above with ocaml 4.10.0, did you?)
Anyway, you're true that this -thread flag for ocamlc is not necessary at all for lwt! but it seems it is necessary for lwt.unix, see e.g.:

$ ocamlfind ocamlc -linkpkg -package lwt >/dev/null
  # OK

$ ocamlfind ocamlc -linkpkg -package lwt.unix >/dev/null
ocamlfind: [WARNING] Package `threads': Linking problems may arise because of the missing -thread or -vmthread switch

$ ocamlfind ocamlc -linkpkg -thread -package lwt.unix >/dev/null
  # OK

Does this make sense?

@mjambon
Copy link
Author

mjambon commented Apr 13, 2021

I get the same error messages with the commands above. I didn't see them earlier when using utop. I guess it does some magic under the hood.

@mjambon
Copy link
Author

mjambon commented Apr 13, 2021

Great, I updated the file:

  • added note about #threads;;
  • updated terminology: "thread" -> "promise"

Thank you @erikmd!

@erikmd
Copy link

erikmd commented Apr 13, 2021

Thanks @mjambon! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment