-
-
Save mjambon/5775807ee2d16f51392ec5936b67c3e5 to your computer and use it in GitHub Desktop.
(* | |
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 |
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.
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?
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.
Great, I updated the file:
- added note about
#threads;;
- updated terminology: "thread" -> "promise"
Thank you @erikmd!
Thanks @mjambon! :)
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! 👍