-
-
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! 👍