├── .gitignore ├── .gitmodules ├── 02-exercises ├── 01-introduction │ ├── dune │ ├── dune-project │ └── problem.ml ├── 02-basic_types │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 03-define_functions │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 04-call_functions │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 05-twice │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 06-pattern-matching │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 07-simple_recursion │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 08-list_intro │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 09-list_range │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 10-list_product │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 11-sum_product │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 12-list_functions │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 13-labelled_arguments │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 14-variants │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 15-options │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 16-tuples │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 17-records │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 18-mutable_records │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 19-refs │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 20-anonymous_functions │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── 21-reading_sigs │ ├── dune │ ├── dune-project │ ├── problem.ml │ └── problem.mli ├── dune └── dune-project ├── 03-github ├── README.org ├── commands.ml ├── dune └── hub.ml ├── 04-frogger ├── Dockerfile ├── Makefile ├── README.org ├── assets │ ├── background.png │ ├── buggy-left.png │ ├── buggy-right.png │ ├── camel-down.png │ ├── camel-left.png │ ├── camel-right.png │ ├── camel-up.png │ ├── carpet_blue.png │ ├── carpet_green.png │ ├── carpet_red.png │ ├── confetti.png │ ├── police-car-left.png │ ├── police-car-right.png │ ├── red-pickup-left.png │ ├── red-pickup-right.png │ ├── skull.png │ ├── truck-left.png │ └── truck-right.png ├── config.ml ├── draw.ml ├── dune ├── frogger.ml ├── frogger.mli ├── import.ml ├── index.html ├── main.ml ├── scaffold.ml ├── scaffold.mli ├── suggested_frogger.mli └── test-js-of-ocaml-install │ ├── Makefile │ ├── dune │ ├── index.html │ └── main.ml ├── LICENSE.txt ├── Makefile ├── README.org ├── dune-project ├── make_learn_ocaml_directory.sh └── solutions ├── frogger └── frogger.ml └── github └── commands.ml /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | .merlin 3 | .DS_Store 4 | node_modules 5 | .#* 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "01-install-ocaml"] 2 | path = 01-install-ocaml 3 | url = https://github.com/ocamllabs/install-ocaml 4 | -------------------------------------------------------------------------------- /02-exercises/01-introduction/dune: -------------------------------------------------------------------------------- 1 | ;; -*- Scheme -*- 2 | 3 | (library 4 | (name problem_1) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/01-introduction/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/01-introduction/problem.ml: -------------------------------------------------------------------------------- 1 | (* Welcome to OCaml challenges! 2 | 3 | This exercise is just meant to familiarize you with the system. 4 | 5 | Write OCaml code using your favorite text editor; if you aren't already 6 | committed to one, we recommend Visual Studio Code. 7 | 8 | To compile your code and run inline tests, run 9 | 10 | [dune runtest] 11 | 12 | in a terminal session in this problem's directory. 13 | 14 | Try building this code. 15 | 16 | You should see a compilation error because it's missing the end quote. Add 17 | the end quote and re-run. You should see that the code compiled and ran! 18 | 19 | You can also execute code in utop directly. Try pasting this line of code 20 | into utop and running it there. *) 21 | let () = Stdio.printf "Hello, World! 22 | -------------------------------------------------------------------------------- /02-exercises/02-basic_types/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_2) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/02-basic_types/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/02-basic_types/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* In OCaml there are 6 basic types: int, float, char, string, bool, and unit. 4 | 5 | The following exercises and examples will give you a brief introduction to 6 | these types. Feel free to play around with them in utop. 7 | 8 | Note the keyword [let], which is how variable assignment is done in OCaml. 9 | 10 | In OCaml floats are distinguished from ints by their decimal points. 0 is an 11 | int, 0. is a float. 12 | 13 | In addition the basic math operations are also distinguished by a decimal 14 | point. For example, + allows you to add two ints and +. allows you to add two 15 | floats. *) 16 | 17 | (* Signatures 18 | ========== 19 | 20 | four is a value with the type int. We write the signature like this: 21 | 22 | val four : int 23 | 24 | Read it like this: "[four] is a value of type int". 25 | 26 | Signatures are similar to type declarations in other languages. They tell the 27 | compiler (and human readers of your code!) the types of variables and 28 | functions in your program. For example, in C, C++, and Java, the signature 29 | above would be written like so: 30 | 31 | int four; 32 | *) 33 | let four = 4 34 | 35 | (* float_four is a value with the type float. We write the signature like this: 36 | 37 | val float_four : float 38 | 39 | You may have noticed that the two signatures we showed you were in comments. 40 | Signatures are not always required! In many situations, you may omit them, 41 | and the compiler will infer the type of values. 42 | 43 | However, if you do write a signature for a value, the compiler will make sure 44 | to check that it's consistent with how that value is used. 45 | 46 | Try inserting an incorrect signature for [float_four] to see what error the 47 | compiler gives you. *) 48 | let float_four = 4. 49 | 50 | (* Function signatures 51 | =================== 52 | 53 | In OCaml, functions are also values! 54 | 55 | In a signature, the arrow [->] denotes an argument. The last type in a 56 | function signature is the result. 57 | 58 | So the signature for a function that takes two integers and returns an 59 | integer is: 60 | 61 | val int_average : int -> int -> int 62 | 63 | In Ocaml there's no explicit return statement: functions just return the 64 | value of the last statement in that function. *) 65 | let int_average x y = failwith "For you to implement" 66 | 67 | (* val float_average : float -> float -> float *) 68 | let float_average x y = failwith "For you to implement" 69 | 70 | (* There will be more about functions later, but note that in OCaml, there are 71 | no parentheses when applying a function! So the following expression computes 72 | the average of 10 and 20: 73 | 74 | int_average 10 20 75 | *) 76 | 77 | (* As in many languages strings are denoted with "" and chars are denoted with ''. 78 | 79 | String concatenation is done with the ^ operator. 80 | *) 81 | 82 | (* val first_name : string *) 83 | let first_name = "Fred" 84 | 85 | (* You can also write type annotations in definitions *) 86 | let last_name : string = "Flintstone" 87 | 88 | let full_name = first_name ^ " " ^ last_name 89 | 90 | let a_boolean_false : bool = false 91 | 92 | (* You can use 93 | && for logical and 94 | || for logical or 95 | *) 96 | let () = assert (true || a_boolean_false) 97 | 98 | (* The [unit] type 99 | =============== 100 | 101 | unit is a special type in OCaml that has only one possible value written (). 102 | It is generally used for mutation and io-operations such as printing. 103 | 104 | (I/O stands for input/output. Examples: printing to screen, reading a file, 105 | sending and receiving network requests.) 106 | 107 | To combine several unit operations together the ; operator is used. 108 | 109 | When evaluating a unit operation on the toplevel, you should wrap it in a let 110 | binding, as in 111 | 112 | let () = ... 113 | 114 | This isn't actually necessary in all cases, but it will save you from some 115 | frustrating debugging of compiler issues if you just always include it. 116 | 117 | For this reason, idiomatic OCaml code has [let () = ...] as its entrypoint, 118 | similar to the [main] function in languages like C, C++ and Java (however, 119 | in those languages it is required). 120 | *) 121 | let () = 122 | Stdio.print_endline "Hi, My name is "; 123 | Stdio.print_endline full_name; 124 | Stdio.print_endline " and I am 5 years old" 125 | 126 | (* Like many other programming languages, you can use format strings too *) 127 | let () = 128 | Stdio.printf "Hi, My name is %s and I am %d years old\n" full_name 5 129 | 130 | (* The lines that follow are inline tests. Each evaluates a boolean expression. 131 | They are run during the build, and failures -- evaluating to false -- are 132 | treated like compile errors by the build tool and editors. 133 | 134 | We will see other kinds of inline tests later, and some interesting patterns 135 | for using them. *) 136 | 137 | (* While OCaml supports polymorphic comparison, it is good practice to use 138 | equality and comparison functions specific to each type. 139 | 140 | So, [Int.equal] is the [equal] function defined in the [Int] module. Its 141 | signature is 142 | 143 | val equal : int -> int -> bool 144 | 145 | In words: [equal] takes two [int]s and returns a [bool]. The following line 146 | is applying that function to two inputs, [5] and [int_average 5 5]. *) 147 | 148 | let%test "Testing int_average..." = 149 | Int.equal (int_average 5 5) 5 150 | 151 | (* Modules can also contain operators. By convention, the equality operator is 152 | defined and equivalent to the [equal] function. To reference an operator in a 153 | module, we need to surround it with parentheses. Try removing the parentheses 154 | to see what error you get. 155 | 156 | Int.(=) is the same as Int.equal 157 | *) 158 | 159 | let%test "Testing int_average..." = 160 | Int.(=) (int_average 50 100) 75 161 | 162 | let%test "Testing float_average..." = 163 | Float.(=) (float_average 5. 5.) 5. 164 | 165 | let%test "Testing float_average..." = 166 | Float.equal (float_average 5. 10.) 7.5 167 | 168 | (* .mli files 169 | ========== 170 | 171 | Check out the [problem.mli] file in this directory! It declares the types for 172 | the two functions you had to implement. If the types in the [.mli] don't 173 | match the types of the values in the [.ml], the compiler will flag that as an 174 | error. 175 | *) 176 | -------------------------------------------------------------------------------- /02-exercises/02-basic_types/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* This is an mli, a file that declares the interface that the corresponding 4 | implementation file (problem.ml) exposes to other code. 5 | 6 | The compiler will enforce that the implementations you write for 7 | [int_average] and [float_average] in problem.ml have the type signatures 8 | written below. 9 | *) 10 | 11 | val int_average : int -> int -> int 12 | val float_average : float -> float -> float 13 | -------------------------------------------------------------------------------- /02-exercises/03-define_functions/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_3) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/03-define_functions/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/03-define_functions/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* We use let to define functions. 4 | 5 | Definitions take on the form: 6 | let FUNCTION_NAME ARG1 ARG2 ... = BODY 7 | 8 | In OCaml, outside of strings, whitespace and newlines are the same. 9 | 10 | So, you could also write 11 | let FUNCTION_NAME 12 | ARG1 13 | ARG2 14 | = 15 | BODY 16 | 17 | and it's the same to the compiler. 18 | 19 | For example, here we define a function add1 that takes a single int 20 | argument and returns that argument plus 1. 21 | *) 22 | let add1 arg = arg + 1 23 | 24 | (* This function uses the built-in ^ operator to append strings. *) 25 | let string_append x y = x ^ y 26 | 27 | (* Let's define our own functions using +, -, *, and / below. *) 28 | 29 | let plus x y = failwith "For you to implement" 30 | let times x y = failwith "For you to implement" 31 | let minus x y = failwith "For you to implement" 32 | let divide x y = failwith "For you to implement" 33 | 34 | let%test "Testing plus..." = Int.( = ) 2 (plus 1 1) 35 | let%test "Testing plus..." = Int.( = ) 49 (plus (-1) 50) 36 | let%test "Testing times..." = Int.( = ) 64 (times 8 8) 37 | let%test "Testing times..." = Int.( = ) (-2048) (times (-2) 1024) 38 | let%test "Testing minus..." = Int.( = ) (-4) (minus (-2) 2) 39 | let%test "Testing minus..." = Int.( = ) 1000 (minus 1337 337) 40 | let%test "Testing divide..." = Int.( = ) 512 (divide 1024 2) 41 | let%test "Testing divide..." = Int.( = ) 1010 (divide 31337 31) 42 | -------------------------------------------------------------------------------- /02-exercises/03-define_functions/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val plus : int -> int -> int 4 | val times : int -> int -> int 5 | val minus : int -> int -> int 6 | val divide : int -> int -> int 7 | -------------------------------------------------------------------------------- /02-exercises/04-call_functions/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_4) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/04-call_functions/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/04-call_functions/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Here are some example functions: *) 4 | let square x = x * x 5 | let half x = x / 2 6 | let add x y = x + y 7 | 8 | (* You can order function invocations with parentheses or let bindings *) 9 | (* Parens *) 10 | let () = 11 | Stdio.printf "(5^2)/2 = %i" (half (square 5)) 12 | 13 | (* Let bindings *) 14 | let () = 15 | let squared = square 5 in 16 | let halved = half squared in 17 | Stdio.printf "(5^2)/2 = %i" halved 18 | 19 | (* Try to write [average] by reusing [add] and [half] *) 20 | let average x y = failwith "For you to implement" 21 | 22 | let%test "Testing average..." = 23 | Int.(=) 5 (average 5 5) 24 | 25 | let%test "Testing average..." = 26 | Int.(=) 75 (average 50 100) 27 | -------------------------------------------------------------------------------- /02-exercises/04-call_functions/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val average : int -> int -> int 4 | -------------------------------------------------------------------------------- /02-exercises/05-twice/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_5) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/05-twice/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/05-twice/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* We can easily write a function that adds 1 to any number. 4 | Recall that the infix operator (+) will add two integers. 5 | *) 6 | 7 | let add1 x = failwith "For you to implement" 8 | 9 | (* Let's write a function that squares its argument (multiplies it by itself) *) 10 | let square x = failwith "For you to implement" 11 | 12 | (* Functions are first class in OCaml. This means that you can take 13 | a function and pass it around as an argument to other functions. 14 | 15 | Let's write a function named twice: it will take a function and apply 16 | that function to itself two times. 17 | 18 | For example, if we wanted to make an "add2" function, we could do it 19 | by writing: 20 | let add2 = twice add1 21 | 22 | It may be necessary to use parenthesis to specify which function is 23 | applied to which value. E.g. 24 | let add2 = add1 add1 x 25 | will not compile, however 26 | let add2 = add1 (add1 x) 27 | will compile. 28 | *) 29 | 30 | let twice f x = failwith "For you to implement" 31 | 32 | (* Now that we have twice, write add2 and raise_to_the_fourth *) 33 | 34 | let add2 = failwith "For you to implement" (* Hint: use add1 *) 35 | let raise_to_the_fourth = failwith "For you to implement" (* Hint: use square *) 36 | 37 | let%test "Testing add1..." = 38 | Int.(=) 5 (add1 4) 39 | 40 | let%test "Testing square..." = 41 | Int.(=) 16 (square 4) 42 | 43 | let%test "Testing square..." = 44 | Int.(=) 16 (square (-4)) 45 | 46 | let%test "Testing add1..." = 47 | Int.(=) 5 (twice add1 3) 48 | 49 | let%test "Testing add2..." = 50 | Int.(=) 1337 (add2 1335) 51 | 52 | let%test "Testing raise_to_the_fourth..." = 53 | Int.(=) 1 (raise_to_the_fourth 1) 54 | 55 | let%test "Testing raise_to_the_fourth..." = 56 | Int.(=) 10000 (raise_to_the_fourth 10) 57 | -------------------------------------------------------------------------------- /02-exercises/05-twice/problem.mli: -------------------------------------------------------------------------------- 1 | (* This file deliberately left empty. *) 2 | -------------------------------------------------------------------------------- /02-exercises/06-pattern-matching/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_6) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/06-pattern-matching/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/06-pattern-matching/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Pattern matching lets us compare inputs to known values. 4 | Patterns following "|" are tested in order. 5 | On the first match, we use the result following "->". 6 | The "_" pattern means "could be anything". 7 | *) 8 | let is_superman x = 9 | match x with 10 | | "Clark Kent" -> true 11 | | _ -> false 12 | ;; 13 | 14 | (* We can also pattern match on multiple values at the same time. *) 15 | let is_same_person x y = 16 | match x, y with 17 | | "Clark Kent", "Superman" 18 | | "Peter Parker", "Spiderman" -> true 19 | | _ -> false 20 | ;; 21 | 22 | (* Let's use our own pattern matching. Write a function that returns 23 | whether x is non zero by matching on x *) 24 | let non_zero x = failwith "For you to implement" 25 | 26 | let%test "Testing non_zero..." = Bool.( = ) false (non_zero 0) 27 | let%test "Testing non_zero..." = Bool.( = ) true (non_zero 500) 28 | let%test "Testing non_zero..." = Bool.( = ) true (non_zero (-400)) 29 | 30 | (* Now, write a function that returns true if x and y are both 31 | non-zero by matching on both of them. *) 32 | let both_non_zero x y = failwith "For you to implement" 33 | 34 | let%test "Testing both_non_zero..." = Bool.( = ) false (both_non_zero 0 0) 35 | let%test "Testing both_non_zero..." = Bool.( = ) false (both_non_zero 0 1) 36 | let%test "Testing both_non_zero..." = Bool.( = ) false (both_non_zero (-20) 0) 37 | let%test "Testing both_non_zero..." = Bool.( = ) true (both_non_zero 400 (-5)) 38 | -------------------------------------------------------------------------------- /02-exercises/06-pattern-matching/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val non_zero : int -> bool 4 | -------------------------------------------------------------------------------- /02-exercises/07-simple_recursion/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_7) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/07-simple_recursion/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/07-simple_recursion/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Remember that functions can call functions? 4 | They can call themselves too. But only with a special keyword. 5 | 6 | First try to compile this. We see "Unbound value add_every_number_up_to". 7 | 8 | Now change "let" to "let rec" and recompile. 9 | *) 10 | 11 | let add_every_number_up_to x = 12 | (* make sure we don't call this on negative numbers! *) 13 | assert (x >= 0); 14 | match x with 15 | | 0 -> 0 16 | | _ -> x + add_every_number_up_to (x - 1) 17 | ;; 18 | 19 | (* Let's write a function to multiply every number up to x. Remember: [factorial 0] is 1 *) 20 | let rec factorial x = 21 | assert (x >= 0); 22 | failwith "For you to implement" 23 | ;; 24 | 25 | let%test "Testing factorial..." = Int.( = ) 1 (factorial 0) 26 | let%test "Testing factorial..." = Int.( = ) 120 (factorial 5) 27 | let%test "Testing factorial..." = Int.( = ) 479001600 (factorial 12) 28 | -------------------------------------------------------------------------------- /02-exercises/07-simple_recursion/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val factorial : int -> int 4 | -------------------------------------------------------------------------------- /02-exercises/08-list_intro/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_8) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/08-list_intro/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/08-list_intro/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* OCaml natively supports linked lists as part of the language. 4 | Lists are commonly referred to as having heads and tails. 5 | The head is the first element of the linked list 6 | The tail is everything else. 7 | 8 | To construct a list we use the cons infix operator :: to prepend elements to 9 | the front of a list 10 | 11 | val (::) : 'a -> 'a list -> 'a list 12 | 13 | [] means "the empty list". 14 | hd :: tl means "the element hd added to the front of the list tl". 15 | 16 | When matching on a list, it's either empty or non-empty. To say it another way, it's 17 | either equal to [] or equal to (hd :: tl) where hd is the first element of the list 18 | and tl is all the rest of the elements of the list (which may itself be empty). 19 | *) 20 | 21 | let () = assert ([%compare.equal: int list] [ 5; 1; 8; 4 ] (5 :: 1 :: 8 :: 4 :: [])) 22 | 23 | (* This function computes the length of a list. *) 24 | let rec length lst = 25 | match lst with 26 | | [] -> 0 27 | | _ :: tl -> 1 + length tl 28 | ;; 29 | 30 | (* Write a function to add up the elements of a list by matching on it. *) 31 | let rec sum lst = failwith "For you to implement" 32 | 33 | (* The signature for the append operator is 34 | val (@) : 'a list -> 'a list -> 'a list 35 | 36 | It's an infix operator. 37 | *) 38 | let list_append first second = first @ second 39 | 40 | (* The way you put something on the head to the list 41 | uses the same kind of syntax for matching on lists. 42 | val (::) : 'a -> 'a list -> 'a list 43 | *) 44 | let new_head hd rest = hd :: rest 45 | 46 | let%test "Testing sum..." = Int.( = ) 0 (sum []) 47 | let%test "Testing sum..." = Int.( = ) 55 (sum [ 55 ]) 48 | let%test "Testing sum..." = Int.( = ) 0 (sum [ 5; -5; 1; -1 ]) 49 | let%test "Testing sum..." = Int.( = ) 12 (sum [ 5; 5; 1; 1 ]) 50 | -------------------------------------------------------------------------------- /02-exercises/08-list_intro/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val sum : int list -> int 4 | -------------------------------------------------------------------------------- /02-exercises/09-list_range/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_9) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/09-list_range/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/09-list_range/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* When working with two lists it's conveninet to have a way to concatenate them together. 4 | 5 | The append infix operator @ concatenates two lists: 6 | 7 | val (@) : 'a list -> 'a list -> 'a list 8 | 9 | This function is the same as the List.append function. 10 | *) 11 | let () = 12 | assert ([%compare.equal: int list] ([ 5; 1 ] @ [ 8; 4 ]) [ 5; 1; 8; 4 ]); 13 | assert ([%compare.equal: int list] (List.append [ 5; 1 ] [ 8; 4 ]) [ 5; 1; 8; 4 ]) 14 | ;; 15 | 16 | (* TODO: Write a function to construct a list of all integers in the range from [from] to [to_] 17 | 18 | including [from] but excluding [to_] in increasing order. 19 | 20 | val range : int -> int -> int list 21 | *) 22 | let range from to_ = failwith "For you to implement" 23 | 24 | (* Here's a different way of getting the [equal] function for a type [t]: 25 | 26 | [%compare.equal: t] 27 | 28 | For example, [%compare.equal: float] is replaced at compile-time with the 29 | equality function for floats. 30 | 31 | And [%compare.equal: int list] is the equality function for lists of 32 | integers. 33 | 34 | One situation where this is really useful is instantiations of containers 35 | (like the [int list] example above, which is used below in tests). Instead of 36 | writing an equality function by hand, or defining a module specialized to 37 | that type just to use its equality operator, you can ask the [ppx_compare] 38 | syntax extension to create it for you on the fly. 39 | *) 40 | let%test "Testing range..." = [%compare.equal: int list] (range 1 4) [ 1; 2; 3 ] 41 | 42 | let%test "Testing range..." = 43 | [%compare.equal: int list] (range (-5) 3) [ -5; -4; -3; -2; -1; 0; 1; 2 ] 44 | ;; 45 | -------------------------------------------------------------------------------- /02-exercises/09-list_range/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val range : int -> int -> int list 4 | -------------------------------------------------------------------------------- /02-exercises/10-list_product/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_10) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/10-list_product/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/10-list_product/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Now let's write a function to multiply together the elements of a list. *) 4 | let rec product xs = 5 | match xs with 6 | | [] -> failwith "For you to implement" 7 | | _for_you_to_implement -> failwith "For you to implement" 8 | ;; 9 | 10 | let%test "Testing product..." = Int.equal 1 (product []) 11 | let%test "Testing product..." = Int.equal 55 (product [ 55 ]) 12 | let%test "Testing product..." = Int.equal 25 (product [ 5; -5; 1; -1 ]) 13 | let%test "Testing product..." = Int.equal 25 (product [ 5; 5; 1; 1 ]) 14 | -------------------------------------------------------------------------------- /02-exercises/10-list_product/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val product : int list -> int 4 | -------------------------------------------------------------------------------- /02-exercises/11-sum_product/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_11) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/11-sum_product/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/11-sum_product/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | let plus x y = x + y 4 | let times x y = x * y 5 | 6 | (* Sometimes, multiple functions look similar: *) 7 | let rec add_every_number_up_to x = 8 | match x with 9 | | 0 -> 0 10 | | _ -> plus x (add_every_number_up_to (x - 1)) 11 | ;; 12 | 13 | let rec factorial x = 14 | match x with 15 | | 0 -> 1 16 | | _ -> times x (factorial (x - 1)) 17 | ;; 18 | 19 | (* These functions have a lot in common: 20 | 21 | let rec NAME x = 22 | match x with 23 | | 0 -> ANSWER 24 | | _ -> COMBINE x (NAME (x-1)) 25 | *) 26 | 27 | (* OCaml lets us write the common parts just once. 28 | We just add an extra input for every part that changes (other than the name): 29 | *) 30 | let rec up_to answer combine x = 31 | match x with 32 | | 0 -> answer 33 | | _ -> combine x (up_to answer combine (x - 1)) 34 | ;; 35 | 36 | (* Now we can write our original functions in one line each! *) 37 | let simpler_add_every_number_up_to x = up_to 0 plus x 38 | let simpler_factorial x = up_to 1 times x 39 | 40 | (* Note that with infix operators like + and *, you can actually pass them as functions! 41 | You can do this by writing ( + ) and ( * ). So another way to write the above two 42 | functions would be: 43 | 44 | let simpler_add_every_number_up_to x = up_to 0 ( + ) x 45 | let simpler_factorial x = up_to 1 ( * ) x 46 | *) 47 | 48 | (* Remember sum and product? *) 49 | let rec sum xs = 50 | match xs with 51 | | [] -> 0 52 | | x :: ys -> plus x (sum ys) 53 | ;; 54 | 55 | let rec product xs = 56 | match xs with 57 | | [] -> 1 58 | | x :: ys -> times x (product ys) 59 | ;; 60 | 61 | (* These functions look pretty similar too: 62 | 63 | let rec NAME xs = 64 | match xs with 65 | | [] -> ANSWER 66 | | x :: ys -> COMBINE x (NAME ys) 67 | *) 68 | 69 | (* Let's write the common parts just once: *) 70 | let rec every answer combine xs = failwith "For you to implement" 71 | 72 | (* Now let's rewrite sum and product in just one line each using [every] *) 73 | let simpler_sum xs = failwith "For you to implement" 74 | let simpler_product xs = failwith "For you to implement" 75 | 76 | let%test "Testing simpler_product..." = Int.( = ) 1 (simpler_product []) 77 | let%test "Testing simpler_product..." = Int.( = ) 55 (simpler_product [ 55 ]) 78 | let%test "Testing simpler_product..." = Int.( = ) 25 (simpler_product [ 5; -5; 1; -1 ]) 79 | let%test "Testing simpler_product..." = Int.( = ) 25 (simpler_product [ 5; 5; 1; 1 ]) 80 | let%test "Testing simpler_sum..." = Int.( = ) 0 (simpler_sum []) 81 | let%test "Testing simpler_sum..." = Int.( = ) 55 (simpler_sum [ 55 ]) 82 | let%test "Testing simpler_sum..." = Int.( = ) 0 (simpler_sum [ 5; -5; 1; -1 ]) 83 | let%test "Testing simpler_sum..." = Int.( = ) 12 (simpler_sum [ 5; 5; 1; 1 ]) 84 | -------------------------------------------------------------------------------- /02-exercises/11-sum_product/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val simpler_sum : int list -> int 4 | val simpler_product : int list -> int 5 | -------------------------------------------------------------------------------- /02-exercises/12-list_functions/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_12) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/12-list_functions/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/12-list_functions/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Many of the list functions we've been writing by hand are actually available 4 | in the language in a nice first class way. 5 | 6 | Let's take look at some of the useful functions that are given to you. 7 | *) 8 | 9 | (* List.fold 10 | 11 | val fold : 'a list ‑> init:'b ‑> f:('b ‑> 'a ‑> 'b) ‑> 'b 12 | 13 | Maybe this looks familiar? This is the same as the every 14 | function we wrote in the last problem. 15 | 16 | Let's rewrite simpler_sum and simpler_product using List.fold 17 | *) 18 | 19 | let simpler_sum xs = failwith "For you to implement" 20 | let simpler_product xs = failwith "For you to implement" 21 | 22 | (* List.map 23 | 24 | val map : 'a list ‑> f:('a ‑> 'b) ‑> 'b list 25 | 26 | [map] allows us to transforms lists from one type to lists 27 | of another type by applying some function (f) to every element 28 | of the list. 29 | 30 | Let's write a function that takes in an int list and transforms 31 | it into a float list 32 | *) 33 | 34 | let float_of_int xs = failwith "For you to implement" 35 | 36 | (* List.init 37 | 38 | val init : int -> f:(int -> 'a) -> 'a t 39 | 40 | [init] allows you to construct new lists. Given a number 41 | representing the number of elements to generate and a function to 42 | construct a new element, it returns a new list 43 | 44 | Let's rewrite the range function we wrote in problem 9 to use [init] 45 | *) 46 | 47 | let range from to_ = failwith "For you to implement" 48 | 49 | (* List.range 50 | 51 | Turns out this special case of [List.init] is useful enough that it has it's own 52 | function: 53 | 54 | val range : 55 | ?stride:int 56 | -> ?start:[ `exclusive | `inclusive ] 57 | -> ?stop:[ `exclusive | `inclusive ] 58 | -> int 59 | -> int 60 | -> int list *) 61 | 62 | (* List.iter 63 | 64 | val iter : 'a list -> f:('a -> unit) -> unit 65 | 66 | Sometimes you want to do something side-effecting to all the elements of a list, 67 | such as printing them out. [iter] allows you to run a side-effecting 68 | function on every element of a list 69 | 70 | Lets use [iter] to print a list of ints 71 | *) 72 | 73 | let print_int_list xs = failwith "For you to implement" 74 | 75 | (* There are many more useful List functions but a couple that are worth noting are 76 | 77 | * List.find 78 | 79 | val find : 'a list -> f:('a -> bool) -> 'a option 80 | 81 | This allows you to find the first element in a list that satifies some condition f 82 | 83 | * List.filter 84 | 85 | val filter : 'a list -> f:('a -> bool) -> 'a list 86 | 87 | This allows you to remove all elements from a list that do not satisfy some condition f 88 | 89 | * List.mapi 90 | 91 | val mapi : 'a list -> f:(int -> 'a -> 'b) -> 'b list 92 | 93 | This is just like map, but it also tells you the index of the element in the list 94 | 95 | * List.zip 96 | 97 | val zip : 'a list -> 'b list -> ('a * 'b) list option 98 | 99 | This allows you to combine two lists pairwise. It will return None if the lists are not 100 | equal in length 101 | *) 102 | 103 | let%test "Testing simpler_product..." = Int.( = ) 1 (simpler_product []) 104 | let%test "Testing simpler_product..." = Int.( = ) 55 (simpler_product [ 55 ]) 105 | let%test "Testing simpler_product..." = Int.( = ) 25 (simpler_product [ 5; -5; 1; -1 ]) 106 | let%test "Testing simpler_product..." = Int.( = ) 25 (simpler_product [ 5; 5; 1; 1 ]) 107 | let%test "Testing simpler_sum..." = Int.( = ) 0 (simpler_sum []) 108 | let%test "Testing simpler_sum..." = Int.( = ) 55 (simpler_sum [ 55 ]) 109 | let%test "Testing simpler_sum..." = Int.( = ) 0 (simpler_sum [ 5; -5; 1; -1 ]) 110 | let%test "Testing simpler_sum..." = Int.( = ) 12 (simpler_sum [ 5; 5; 1; 1 ]) 111 | 112 | let%test "Testing float_of_int..." = [%compare.equal: float list] (float_of_int [1; 2; 3]) [ 1.0; 2.0; 3.0 ] 113 | 114 | let%test "Testing range..." = [%compare.equal: int list] (range 1 4) [ 1; 2; 3 ] 115 | 116 | let%test "Testing range..." = 117 | [%compare.equal: int list] (range (-5) 3) [ -5; -4; -3; -2; -1; 0; 1; 2 ] 118 | ;; 119 | -------------------------------------------------------------------------------- /02-exercises/12-list_functions/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val print_int_list : int list -> unit -------------------------------------------------------------------------------- /02-exercises/13-labelled_arguments/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_13) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/13-labelled_arguments/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/13-labelled_arguments/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | (* The following function has the signature: 3 | 4 | val divide : int -> int -> int 5 | 6 | Looking at just the signature, it's not obvious which int argument is 7 | the dividend and which is the divisor. 8 | *) 9 | let divide dividend divisor = dividend / divisor 10 | 11 | (* We can fix this using labelled arguments. 12 | 13 | To label an argument in a signature, "NAME:" is put before the type. 14 | When defining the function, we put a tilde (~) before the name of the argument. 15 | 16 | The following function has the signature: 17 | 18 | val divide : dividend:int -> divisor:int -> int 19 | *) 20 | let divide ~dividend ~divisor = dividend / divisor 21 | 22 | (* We can then call it using: 23 | 24 | divide ~dividend:9 ~divisor:3 25 | 26 | Labelled arguments can be passed in in any order (!) 27 | 28 | We can also pass variables into the labelled argument: 29 | 30 | let dividend = 9 in 31 | let divisor = 3 in 32 | divide ~dividend:dividend ~divisor:divisor 33 | 34 | If the variable name happens to be the same as the labelled argument, we 35 | don't even have to write it twice: 36 | 37 | let dividend = 9 in 38 | let divisor = 3 in 39 | divide ~dividend ~divisor 40 | *) 41 | 42 | (* Now implement [modulo ~dividend ~divisor] using our version of divide with labelled 43 | arguments (e.g. [modulo ~dividend:7 ~divisor:2] should equal 1) *) 44 | let modulo ~dividend ~divisor = failwith "For you to implement" 45 | 46 | let%test "Testing modulo..." = 47 | Int.(=) 2 (modulo ~dividend:17 ~divisor:5) 48 | 49 | let%test "Testing modulo..." = 50 | Int.(=) 0 (modulo ~dividend:99 ~divisor:9) 51 | 52 | -------------------------------------------------------------------------------- /02-exercises/13-labelled_arguments/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val modulo : dividend:int -> divisor:int -> int 4 | -------------------------------------------------------------------------------- /02-exercises/14-variants/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_14) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/14-variants/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/14-variants/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* As in most languages, you can define your own types. 4 | The keyword "type" introduces a type definition. 5 | 6 | One of the non-basic types in OCaml is called the variant type. 7 | Variant types are similar to Enums in other languages. They are 8 | types which may take on multiple forms, where each form is marked 9 | by an explicit tag. A variant type is defined as follows: 10 | *) 11 | type color = 12 | | Red 13 | | Green 14 | | Blue 15 | 16 | (* Variants are very useful in combination with pattern matching *) 17 | let to_string color = 18 | match color with 19 | | Red -> "red" 20 | | Green -> "green" 21 | | Blue -> "blue" 22 | 23 | (* OCaml variants are in many ways more powerful than Enums because the different 24 | constructors of your variant can include data in them. Here's an example: 25 | *) 26 | type card_value = 27 | | Ace 28 | | King 29 | | Queen 30 | | Jack 31 | | Number of int 32 | 33 | let one_card_value : card_value = Queen 34 | let another_card_value : card_value = Number 8 35 | 36 | let card_value_to_string card_value = 37 | match card_value with 38 | | Ace -> "Ace" 39 | | King -> "King" 40 | | Queen -> "Queen" 41 | | Jack -> "Jack" 42 | | Number i -> Int.to_string i 43 | 44 | (* Write a function that computes the score of a card (aces should score 11 45 | and face cards should score 10). *) 46 | let card_value_to_score card_value = 47 | failwith "For you to implement" 48 | 49 | let%test "Testing card_value_to_score..." = 50 | Int.(=) 11 (card_value_to_score Ace) 51 | 52 | let%test "Testing card_value_to_score..." = 53 | Int.(=) 10 (card_value_to_score King) 54 | 55 | let%test "Testing card_value_to_score..." = 56 | Int.(=) 10 (card_value_to_score Queen) 57 | 58 | let%test "Testing card_value_to_score..." = 59 | Int.(=) 10 (card_value_to_score Jack) 60 | 61 | let%test "Testing card_value_to_score..." = 62 | Int.(=) 5 (card_value_to_score (Number 5)) 63 | 64 | -------------------------------------------------------------------------------- /02-exercises/14-variants/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | type card_value 4 | 5 | val card_value_to_score : card_value -> int 6 | -------------------------------------------------------------------------------- /02-exercises/15-options/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_15) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/15-options/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/15-options/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Many languages have a concept of "Null", which describes that some data is 4 | absent. In OCaml, we can model the presence/absence data using ordinary 5 | variants. 6 | 7 | Note: we're defining the [option] type here to show you that it isn't magic. 8 | In real life you would always use the [option] type provided by the standard 9 | library. [Base] comes with a convenient [Option] module with many useful 10 | functions. *) 11 | type 'a option = 12 | | None 13 | | Some of 'a 14 | 15 | (* An ['a option] is either [None], meaning absence of data, or [Some x] meaning 16 | the data exists, and that data specifically is [x]. Here's an example: *) 17 | 18 | let what_number_am_i_thinking (my_number : int option) = 19 | match my_number with 20 | | None -> "I'm not thinking of any number!" 21 | | Some number -> "My number is: " ^ (Int.to_string number) 22 | 23 | let%test _ = 24 | String.(=) (what_number_am_i_thinking None) "I'm not thinking of any number!" 25 | 26 | let%test _ = 27 | String.(=) (what_number_am_i_thinking (Some 7)) "My number is: 7" 28 | 29 | (* Implement the function [safe_divide ~dividend ~divisor], which takes two ints 30 | and returns an int option. It should return None if [divisor = 0], and 31 | otherwise returns [Some x] where [x] is the division result *) 32 | let safe_divide ~dividend ~divisor = 33 | failwith "For you to implement" 34 | 35 | let%test "Testing safe_divide..." = 36 | match (safe_divide ~dividend:3 ~divisor:2) with 37 | | Some 1 -> true 38 | | _ -> false 39 | 40 | let%test "Testing safe_divide..." = 41 | match safe_divide ~dividend:3 ~divisor:0 with 42 | | None -> true 43 | | _ -> false 44 | 45 | (* Implement a function [concatenate string1 string2], which takes two 46 | [string option]s and returns a [string option] that is [Some x] 47 | where x is the concatenation of the two strings, if they exist, and 48 | [None] if either of the strings is [None]. *) 49 | let option_concatenate string1 string2 = 50 | failwith "For you to implement" 51 | 52 | let%test "Testing option_concatenate..." = 53 | match option_concatenate (Some "hello") (Some "world") with 54 | | Some "helloworld" -> true 55 | | _ -> false 56 | 57 | let%test "Testing option_concatenate..." = 58 | match option_concatenate None (Some "world") with 59 | | None -> true 60 | | _ -> false 61 | 62 | let%test "Testing option_concatenate..." = 63 | match option_concatenate (Some "hello") None with 64 | | None -> true 65 | | _ -> false 66 | -------------------------------------------------------------------------------- /02-exercises/15-options/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | type 'a option = 4 | | None 5 | | Some of 'a 6 | 7 | val safe_divide : dividend:int -> divisor:int -> int option 8 | -------------------------------------------------------------------------------- /02-exercises/16-tuples/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_16) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/16-tuples/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/16-tuples/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Another non-basic type in OCaml is a tuple. A tuple is an ordered collection 4 | of values that can each be of a different type. The signature for a tuple is 5 | written by separating all the types within the tuple by a *. 6 | *) 7 | type int_and_string_and_char = int * string * char 8 | 9 | (* Tuples are created by joining values with a comma: *) 10 | let example : int_and_string_and_char = 5, "hello", 'A' 11 | 12 | (* You can also extract the components of a tuple: *) 13 | let i, s, c = example 14 | 15 | let () = 16 | assert (i = 5); 17 | assert (String.( = ) s "hello"); 18 | assert (Char.( = ) c 'A') 19 | ;; 20 | 21 | (* Consider a coordinate type containing the x and y values of a coordinate. 22 | Write a function that computes the sum of two coordinates. 23 | *) 24 | type coordinate = int * int 25 | 26 | let add coord1 coord2 = failwith "For you to implement" 27 | 28 | (* Now consider a name type containing strings representing first and last names *) 29 | type name = string * string 30 | 31 | (* Or an initials type containing chars representing first and last initials *) 32 | type initials = char * char 33 | 34 | (* Say we want to write a function that extracts the first element from a coordinate, 35 | name, or initials. We currently can't write that because they all have different 36 | types. 37 | 38 | Lets define a new pair type which is parameterized over the type contained in 39 | the pair. We write this as 40 | *) 41 | type 'a pair = 'a * 'a 42 | 43 | (* Our types defined above could be rewritten as 44 | 45 | type coordinate = int pair 46 | type name = string pair 47 | type initials = char pair 48 | *) 49 | 50 | (* We can construct pairs just like we construct regular tuples *) 51 | let int_pair : int pair = 5, 7 52 | let string_pair : string pair = "foo", "bar" 53 | let nested_char_pair : char pair pair = ('a', 'b'), ('c', 'd') 54 | 55 | (* Write functions to extract the first and second elements from a pair. *) 56 | (* val first : 'a pair -> 'a *) 57 | let first pair = failwith "For you to implement" 58 | 59 | (* val second : 'a pair -> 'a *) 60 | let second pair = failwith "For you to implement" 61 | 62 | (* Notice the cool [%compare.equal: int*int] here! *) 63 | let%test "Testing add..." = [%compare.equal: int * int] (4, 7) (add (5, 3) (-1, 4)) 64 | let%test "Testing first..." = String.( = ) "foo" (first ("foo", "bar")) 65 | let%test "Testing second..." = Char.( = ) 'b' (second ('a', 'b')) 66 | -------------------------------------------------------------------------------- /02-exercises/16-tuples/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val add : int * int -> int * int -> int * int 4 | 5 | type 'a pair 6 | val first : 'a pair -> 'a 7 | val second : 'a pair -> 'a 8 | 9 | -------------------------------------------------------------------------------- /02-exercises/17-records/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_17) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/17-records/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/17-records/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* OCaml allows you to define record types. 4 | These are like structs in C, or data members of a class in python/ruby/java. 5 | *) 6 | 7 | type person = (* The name of the type is [person] *) 8 | (* it contains four fields *) 9 | (* The first field, called "age" is of type int. *) 10 | { age : int 11 | ; first_name : string 12 | ; last_name : string 13 | ; number_of_cars : int 14 | } [@@deriving compare] 15 | 16 | (* We can create a [person] like this. 17 | When defining and matching on a record, the fields 18 | can be listed in any order. 19 | *) 20 | let an_example : person = 21 | { first_name = "Cotton-eyed" 22 | ; last_name = "Joe" 23 | ; age = 22 24 | ; number_of_cars = 0 25 | } 26 | 27 | (* In order to get a field out of a record we use the "." operator: 28 | VARIABLE.FIELD 29 | *) 30 | let age : int = an_example.age 31 | let () = assert (age = 22) 32 | 33 | (* We can also match on records to get field information. *) 34 | let print_info {first_name; last_name; age; number_of_cars} = 35 | Stdio.print_endline first_name; 36 | Stdio.print_endline last_name; 37 | Stdio.printf "Age: %d, # of cars: %d\n" age number_of_cars 38 | ;; 39 | 40 | (* If we don't care about an argument we can ignore it using "= _" *) 41 | let print_name ({first_name; last_name; age = _; number_of_cars = _}) = 42 | Stdio.print_endline first_name; 43 | Stdio.print_endline last_name 44 | 45 | (* Finally, we can perform "functional updates" by replacing the value of a field, 46 | yielding a brand new record. We use the "with" keyword to do this. *) 47 | 48 | (* val add_one_to_age : person -> person *) 49 | let add_one_to_age person = 50 | { person with age = person.age + 1 } 51 | 52 | let () = assert (23 = (add_one_to_age an_example).age) 53 | 54 | (* Write a function that does different things for different people: 55 | When the person's first name is "Jan", 56 | you should return a record with the age set to 30. 57 | 58 | Otherwise, you should increase the number of cars by 6. 59 | *) 60 | 61 | (* val modify_person : person -> person *) 62 | 63 | let modify_person (person : person) = 64 | failwith "For you to implement" 65 | 66 | module For_testing = struct 67 | let test_ex1 : person = { 68 | first_name = "Jan"; 69 | last_name = "Saffer"; 70 | age = 55; 71 | number_of_cars = 0; 72 | };; 73 | 74 | let test_ex1' : person = {test_ex1 with age = 30};; 75 | 76 | let test_ex2 : person = { 77 | first_name = "Hugo"; 78 | last_name = "Heuzard"; 79 | age = 4; 80 | number_of_cars = 55; 81 | };; 82 | 83 | let test_ex2' : person = { test_ex2 with number_of_cars = 61};; 84 | 85 | let%test "Testing modify_person..." = 86 | [%compare.equal: person] test_ex1' (modify_person test_ex1) 87 | ;; 88 | 89 | let%test "Testing modify_person..." = 90 | [%compare.equal: person] test_ex2' (modify_person test_ex2) 91 | ;; 92 | end 93 | -------------------------------------------------------------------------------- /02-exercises/17-records/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | type person 4 | 5 | val modify_person : person -> person 6 | -------------------------------------------------------------------------------- /02-exercises/18-mutable_records/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_18) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/18-mutable_records/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/18-mutable_records/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* Sometimes rather than redefining the record you would like to have a field or 4 | a set of fields that you can modify on the fly. 5 | 6 | In OCaml if you want to have a field in a record that can be updated in place 7 | you must use some additional syntax. The mutable keyword makes the field 8 | modifiable. 9 | 10 | Then you can use <- to set the record value to a new value. *) 11 | type color = 12 | | Red 13 | | Yellow 14 | | Green 15 | 16 | (* You'll get an error about Unbound value compare_color. This is because we 17 | used the [compare] ppx for [stoplight] below, which has a [color] as one of 18 | its fields, but we didn't have [compare] on [color]. 19 | 20 | Fix it by adding this: [@@deriving compare] 21 | *) 22 | 23 | type stoplight = 24 | { location : string (* stoplights don't usually move *) 25 | ; mutable color : color (* but they often change color *) 26 | } 27 | [@@deriving compare] 28 | 29 | (* On creation mutable fields are defined just like normal fields *) 30 | let an_example : stoplight = 31 | { location = "The corner of Vesey Street and the West Side highway"; color = Red } 32 | ;; 33 | 34 | (* Now rather than using a functional update we can use a mutable update. 35 | This doesn't return a new stoplight, it modifies the input stoplight. 36 | *) 37 | let set_color stoplight color = stoplight.color <- color 38 | 39 | (* Since we know that stoplights always go from Green to Yellow, Yellow to 40 | Red, and Red to Green, we can just write a function to advance the color 41 | of the light without taking an input color. *) 42 | let advance_color stoplight = failwith "For you to implement" 43 | 44 | module For_testing = struct 45 | let test_ex_red : stoplight = { location = ""; color = Red } 46 | let test_ex_red' : stoplight = { test_ex_red with color = Green } 47 | let test_ex_yellow : stoplight = { location = ""; color = Yellow } 48 | let test_ex_yellow' : stoplight = { test_ex_red with color = Red } 49 | let test_ex_green : stoplight = { location = ""; color = Green } 50 | let test_ex_green' : stoplight = { test_ex_red with color = Yellow } 51 | 52 | let%test "Testing advance_color..." = 53 | advance_color test_ex_green; 54 | [%compare.equal: stoplight] test_ex_green' test_ex_green 55 | ;; 56 | 57 | let%test "Testing advance_color..." = 58 | advance_color test_ex_yellow; 59 | [%compare.equal: stoplight] test_ex_yellow' test_ex_yellow' 60 | ;; 61 | 62 | let%test "Testing advance_color..." = 63 | advance_color test_ex_red; 64 | [%compare.equal: stoplight] test_ex_red' test_ex_red 65 | ;; 66 | end 67 | -------------------------------------------------------------------------------- /02-exercises/18-mutable_records/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | type stoplight 4 | 5 | val advance_color : stoplight -> unit 6 | -------------------------------------------------------------------------------- /02-exercises/19-refs/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_19) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/19-refs/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/19-refs/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* It is sometimes useful to create a single mutable value. We can do this 4 | using a ref. We can create an [int ref] containing 0 as follows: 5 | *) 6 | let x = ref 0 7 | 8 | (* Then we can access the value in the ref using the ! operator, and 9 | we can update it using the := operator. So, we could increment our 10 | ref as follows: 11 | *) 12 | let () = 13 | x := !x + 1 14 | 15 | (* Write a function min_and_max which returns a tuple containing the 16 | minimum and maximum values in a non-empty list of positive 17 | integers. Your function should raise if the list is empty. 18 | 19 | Your function should iterate over the list and maintain refs of the 20 | minimum and maximum values seen so far. *) 21 | let min_and_max lst = 22 | failwith "For you to implement" 23 | 24 | let%test "Testing min_and_max..." = 25 | [%compare.equal: int*int] (min_and_max [5;9;2;4;3]) (2,9) 26 | ;; 27 | 28 | let%test "Testing min_and_max..." = 29 | [%compare.equal: int*int] (min_and_max [11;15;7;34]) (7,34) 30 | ;; 31 | -------------------------------------------------------------------------------- /02-exercises/19-refs/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val min_and_max : int list -> int * int 4 | -------------------------------------------------------------------------------- /02-exercises/20-anonymous_functions/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_20) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/20-anonymous_functions/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/20-anonymous_functions/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* In OCaml, functions are values, so we can pass them in as 4 | arguments to other functions. 5 | 6 | To represent a function in a signature, you wrap its type in parenthesis, 7 | with arrows separating arguments. 8 | 9 | Recall: a function called [add1] which takes an integer and returns an integer has the type 10 | val add1 : int -> int 11 | 12 | So, to use that signature in a type, we'd write 13 | (int -> int) 14 | 15 | We now define a function called [map_option]. 16 | [map_option] takes a function and an option. 17 | 18 | If the option has a value of [None], [map_option] returns [None] 19 | If the option has a value of [Some x], the function is called on x, 20 | and wrapped up in a [Some]. 21 | 22 | It may seem unintuitive, but this kind of function is very useful 23 | because it allows you to continue applying functions to data 24 | without having to explicitly deal with null values or worry about 25 | null pointer exceptions if the data isn't there! 26 | 27 | The signature for the function is 28 | 29 | val map_option : ('a -> 'b) -> 'a option -> 'b option 30 | *) 31 | let map_option f opt = 32 | match opt with 33 | | None -> None 34 | | Some i -> Some (f i) 35 | 36 | let double i = 2 * i 37 | 38 | let () = 39 | assert 40 | ([%compare.equal: int option] 41 | (map_option double None) 42 | None) 43 | 44 | let () = 45 | assert 46 | ([%compare.equal: int option] 47 | (map_option double (Some 2)) 48 | (Some 4)) 49 | 50 | (* Instead of defining the function double beforehand, we can use 51 | an anonymous function. 52 | 53 | To write an anonymous function, the "fun" keyword is used in the following form 54 | 55 | (fun ARG1 ARG2 ... -> BODY) 56 | 57 | The following has the same effect as above: 58 | *) 59 | let () = 60 | assert 61 | ([%compare.equal: int option] 62 | (map_option (fun i -> 2 * i) (Some 2)) 63 | (Some 4)) 64 | 65 | (* Define a function, [apply_if_nonzero], which takes a function from 66 | int to int and an int, and applies the function if the integer 67 | is not zero, and otherwise just returns 0. 68 | *) 69 | let apply_if_nonzero f i = 70 | failwith "For you to implement" 71 | 72 | let%test "Testing apply_if_nonzero..." = 73 | Int.(=) 0 (apply_if_nonzero (fun x -> 10 / x) 0) 74 | 75 | let%test "Testing apply_if_nonzero..." = 76 | Int.(=) 2 (apply_if_nonzero (fun x -> 10 / x) 5) 77 | -------------------------------------------------------------------------------- /02-exercises/20-anonymous_functions/problem.mli: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | val apply_if_nonzero : (int -> int) -> int -> int 4 | -------------------------------------------------------------------------------- /02-exercises/21-reading_sigs/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (library 4 | (name problem_21) 5 | (libraries base stdio) 6 | (inline_tests) 7 | (preprocess (pps ppx_jane))) 8 | 9 | (env 10 | (dev 11 | (flags (:standard 12 | -w -20 13 | -w -27 14 | -w -32 15 | -w -34 16 | -w -37 17 | -w -39))) 18 | (release 19 | (flags (:standard)))) 20 | -------------------------------------------------------------------------------- /02-exercises/21-reading_sigs/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /02-exercises/21-reading_sigs/problem.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | 3 | (* OCaml, like many other languages, provides a way to interact with code via 4 | interfaces. This allows implementation details to be hidden away, and for 5 | grouped units of code to restrict how they are used. 6 | 7 | Here's an example of a module signature coupled with an implementation. The 8 | signature is wrapped in a sig / end pair. The implementation is wrapped in a 9 | struct / end pair. *) 10 | module Example : sig 11 | (* Here, 'val' indicates that we are exposing a value. This value is an integer *) 12 | 13 | val the_meaning_of_life_the_universe_and_everything : int 14 | 15 | (* To declare functions, again we use 'val' - in OCaml, functions are values. 16 | This value takes an integer as a parameter and returns an integer 17 | *) 18 | 19 | val subtract_one : int -> int 20 | end = struct 21 | let the_meaning_of_life_the_universe_and_everything = 42 22 | let subtract_one x = x - 1 23 | end 24 | 25 | (* Here's how we use these values *) 26 | let one_less_than_the_meaning_of_life_etc = 27 | Example.subtract_one Example.the_meaning_of_life_the_universe_and_everything 28 | ;; 29 | 30 | assert (one_less_than_the_meaning_of_life_etc = 41) 31 | 32 | (* Types can be exposed via signatures in OCaml as well. Here's an example of declaring 33 | an "abstract" type - one where the definition of the type is not exposed. 34 | *) 35 | module Abstract_type_example : sig 36 | (* We do not let the user know that [t] is an integer *) 37 | 38 | type t 39 | 40 | (* This function allows [t] to be coerced into an integer *) 41 | 42 | val to_int : t -> int 43 | 44 | (* Users need some way to start with some [t] *) 45 | 46 | val zero : t 47 | val one : t 48 | 49 | (* Let them do something with the [t] *) 50 | 51 | val add : t -> t -> t 52 | end = struct 53 | type t = int 54 | 55 | let to_int x = x 56 | let zero = 0 57 | let one = 1 58 | let add = ( + ) 59 | end 60 | 61 | (* Here's an example of adding 2 and 2 *) 62 | let two = Abstract_type_example.add Abstract_type_example.one Abstract_type_example.one 63 | let four = Abstract_type_example.to_int (Abstract_type_example.add two two);; 64 | 65 | assert (four = 4) 66 | 67 | module Fraction : sig 68 | type t 69 | (* TODO: Add signatures for the create and value functions to expose them in 70 | the Fraction module. *) 71 | end = struct 72 | type t = int * int 73 | 74 | let create ~numerator ~denominator = numerator, denominator 75 | let value (numerator, denominator) = Float.of_int numerator /. Float.of_int denominator 76 | end 77 | 78 | let%test "Testing Fraction.value..." = 79 | Float.( = ) 2.5 (Fraction.value (Fraction.create ~numerator:5 ~denominator:2)) 80 | ;; 81 | 82 | let%test "Testing Fraction.value..." = 83 | Float.( = ) 0.4 (Fraction.value (Fraction.create ~numerator:4 ~denominator:10)) 84 | ;; 85 | -------------------------------------------------------------------------------- /02-exercises/21-reading_sigs/problem.mli: -------------------------------------------------------------------------------- 1 | (* This file deliberately left empty. *) 2 | -------------------------------------------------------------------------------- /02-exercises/dune: -------------------------------------------------------------------------------- 1 | ;; -*- Scheme -*- 2 | 3 | (alias 4 | (name DEFAULT) 5 | (deps 6 | (alias_rec 01-introduction/runtest) 7 | (alias_rec 02-basic_types/runtest) 8 | (alias_rec 03-define_functions/runtest) 9 | (alias_rec 04-call_functions/runtest) 10 | (alias_rec 05-twice/runtest) 11 | (alias_rec 06-pattern-matching/runtest) 12 | (alias_rec 07-simple_recursion/runtest) 13 | (alias_rec 08-list_intro/runtest) 14 | (alias_rec 09-list_range/runtest) 15 | (alias_rec 10-list_product/runtest) 16 | (alias_rec 11-sum_product/runtest) 17 | (alias_rec 12-list_functions/runtest) 18 | (alias_rec 13-labelled_arguments/runtest) 19 | (alias_rec 14-variants/runtest) 20 | (alias_rec 15-options/runtest) 21 | (alias_rec 16-tuples/runtest) 22 | (alias_rec 17-records/runtest) 23 | (alias_rec 18-mutable_records/runtest) 24 | (alias_rec 19-refs/runtest) 25 | (alias_rec 20-anonymous_functions/runtest) 26 | (alias_rec 21-reading_sigs/runtest) 27 | )) 28 | -------------------------------------------------------------------------------- /02-exercises/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /03-github/README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Using the GitHub REST API 2 | 3 | Almost the whole of GitHub's functionality can be accessed and controlled using 4 | their REST API. MirageOS includes a library for OCaml which wraps many of these 5 | APIs, providing an easy and convenient way to query and control GitHub from the 6 | command line. 7 | 8 | You will be writing an OCaml reimplementation of some parts of GitHub's own 9 | [[https://github.com/github/hub][hub CLI]]. 10 | 11 | * Preparation 12 | Make sure your switch has the lwt_ssl and github-unix opam packages installed. 13 | You may first want to run =opam depext lwt_ssl= or ensure that pkg-config and 14 | the OpenSSL developement headers are installed. 15 | 16 | * Build and run 17 | Type =dune build hub.exe= to build, and =dune exec hub.exe -- args= to run it. 18 | 19 | * Challenges 20 | It's possible to start off trying out the bindings in utop. Just issue 21 | =#thread;;= to load system threading support and then =#require "github-unix";;= 22 | to get going. 23 | 24 | The bindings live within the Github "namespace" with a module corresponding to 25 | each section of the REST API. For this exercise, you'll mostly be using the 26 | Issue and Pull modules which correspond to the Issues and Pull Requests APIs. 27 | 28 | Like most of Mirage, the library is structured around a monad, which is defined 29 | in the aptly named Monad module. =Commands.show_issue= is a good place to start, 30 | and you will see that =Github.Issue.get= is the main call you'll want. Having 31 | got a promise back, you want to use =Github.Monad.run= in order to get something 32 | you can pass to =Lwt_main.run= in order to get an actual value. 33 | 34 | =Commands.list_issues= and =Commands.list_prs= are similar to each other, but 35 | you'll need to investigate the =Github.Stream= module. Rather than resorting to 36 | =to_list=, see if you can use =Github.Stream.next= to iterate over only the first 37 | 10 issues/prs returned. 38 | 39 | Finally, =checkout_pr= gives an opportunity to interact with the current 40 | directory - it's up to you whether you want to use Lwt_unix to interact with Git, 41 | the vanilla OCaml Unix library (particular Unix.open_process, to query git) or, 42 | for a complete MirageOS experience, the ocaml-git library to determine the state 43 | of the current directory. 44 | 45 | * Extensions 46 | If you look in =hub.ml=, you can see the definition for the command line parameters. 47 | Lots of extensions to these commands can be achieved by adding extra flags similar 48 | to user_t and repo_t. These can include result limiting, sorting orders, and so on. 49 | 50 | Running anonymously, you may quickly hit GitHub's rate limiting on REST API calls. 51 | There are a couple of ways around this - the best is to take advantage of the 52 | =git jar= command installed. All API calls have a =?token= optional parameter which 53 | can be either a login token or an OAUTH token. github-unix installs the git jar command 54 | which allows you create and store OAUTH tokens in your home directory and retrieve 55 | them using the =Github_ookie_jar= module. =Github.Token= also contains functions for 56 | negotiating GitHub's 2 factor login. 57 | -------------------------------------------------------------------------------- /03-github/commands.ml: -------------------------------------------------------------------------------- 1 | (* Four possible subcommands for hub *) 2 | 3 | let show_issue ~user:_ ~repo:_ _number : unit = 4 | failwith "Display summary information for user/repo#number" 5 | 6 | let list_issues ~user:_ ~repo:_ () : unit = 7 | failwith "List issues for user/repo" 8 | 9 | let list_prs ~user:_ ~repo:_ () : unit = 10 | failwith "List pull requests for user/repo" 11 | 12 | let checkout_pr ~user:_ ~repo:_ ?branch:_ _number : unit = 13 | failwith "Checkout user/repo#number either to the name of pull request branch \ 14 | or to the branch name given in ?branch" 15 | -------------------------------------------------------------------------------- /03-github/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name hub) 3 | (modules hub commands) 4 | (libraries cmdliner github-unix)) 5 | -------------------------------------------------------------------------------- /03-github/hub.ml: -------------------------------------------------------------------------------- 1 | open Cmdliner 2 | 3 | let user_t = 4 | let doc = "GitHub user/organisation to read" in 5 | let open Arg in 6 | value & opt string "ocaml" & info ["user"; "u"] ~doc 7 | 8 | let repo_t = 9 | let doc = "GitHub repository to read" in 10 | let open Arg in 11 | value & opt string "ocaml" & info ["repository"; "r"] ~doc 12 | 13 | (* This helper takes a list of subcommand and parses the positional 14 | * arguments to return the command (if matched) and any additional arguments *) 15 | let mk_subcommands commands = 16 | let command = 17 | let doc = Arg.info ~docv:"COMMAND" [] in 18 | let commands = 19 | List.fold_left 20 | (fun acc (c,f,_,_) -> (c,f) :: acc) [] commands in 21 | Arg.(value & pos 0 (some & enum commands) None & doc) 22 | in 23 | let params = 24 | let doc = Arg.info ~doc:"Optional parameters." [] in 25 | Arg.(value & pos_right 0 string [] & doc) 26 | in 27 | command, params 28 | 29 | (* The issue command by default lists issues and has a show subcommand *) 30 | let issue_cmd = 31 | let commands = [ 32 | "show", `show, ["NUMBER"], "Displays issue $(i,NUMBER)"; 33 | ] in 34 | let man = 35 | [ `S Manpage.s_description 36 | ; `P "This command manipulates GitHub issues" 37 | ] 38 | in 39 | let doc = "The issue command" in 40 | let command, params = mk_subcommands commands in 41 | let issue command params user repo = 42 | match command, params with 43 | | None, [] -> Commands.list_issues ~user ~repo (); `Ok () 44 | | Some `show, [number] -> Commands.show_issue ~user ~repo (int_of_string number); `Ok () 45 | | Some `show, _ -> `Error (false, "Expect one issue number for hub issue command") 46 | | _ -> `Error (false, "Unrecognised hub issue command") 47 | in 48 | Term.(ret (const issue $command $params $user_t $repo_t), 49 | info "issue" ~man ~doc) 50 | 51 | (* The pr command has a list command (which is also the default) and a checkout command *) 52 | let pr_cmd = 53 | let commands = [ 54 | "checkout", `checkout, ["NUMBER"; "[BRANCH]"], "Checks out the branch for a PR in this clone"; 55 | "list", `list, [], "Lists PRs"; 56 | ] in 57 | let man = 58 | [ `S Manpage.s_description 59 | ; `P "This command manipulates GitHub pull requests" 60 | ] 61 | in 62 | let doc = "The pr command" in 63 | let command, params = mk_subcommands commands in 64 | let issue command params user repo = 65 | match command, params with 66 | | None, _ 67 | | Some `list, _ -> 68 | Commands.list_prs ~user ~repo (); `Ok () 69 | | Some `checkout, [number] -> 70 | Commands.checkout_pr ~user ~repo (int_of_string number); `Ok () 71 | | Some `checkout, [number; branch] -> 72 | Commands.checkout_pr ~user ~repo ~branch (int_of_string number); `Ok () 73 | | Some `checkout, _ -> 74 | `Error (false, "Expect a pr number and optionally a branch for hub pr checkout command") 75 | | _ -> `Error (false, "Unrecognised hub pr command") 76 | in 77 | Term.(ret (const issue $command $params $user_t $repo_t), info "pr" ~man ~doc) 78 | 79 | (* The default command is just the help screen *) 80 | let default_cmd = 81 | let doc = "Hub is a tool that wraps git in order to extend with extra functionality that makes \ 82 | it better when working with GitHub. It's written in pure OCaml which makes it even \ 83 | more cool." in 84 | let sdocs = Manpage.s_common_options in 85 | let man_xrefs = [] in 86 | let man = 87 | [ `S Manpage.s_description 88 | ; `P "The $(tname) tool provides cool features." 89 | ; `P "You'd expect to have more paragraphs in the final man page!" 90 | ] 91 | in 92 | Term.(ret (const (fun _ -> `Help (`Pager, None)) $ pure ()), 93 | info "hub" ~version:"ReasonConf2019" ~doc ~man_xrefs ~sdocs ~man) 94 | 95 | let cmds = [issue_cmd; pr_cmd] 96 | 97 | let () = Term.(exit @@ eval_choice default_cmd cmds) 98 | -------------------------------------------------------------------------------- /04-frogger/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM kyma/docker-nginx 2 | COPY . /var/www 3 | CMD 'nginx' 4 | -------------------------------------------------------------------------------- /04-frogger/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | dune build @DEFAULT 3 | -------------------------------------------------------------------------------- /04-frogger/README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Frogger in your browser 2 | 3 | If you've never played Frogger, play an online version (picked at random) here: 4 | [[https://denodell.github.io/frogger/]] 5 | 6 | You will be writing a simplified version that discretizes time and space, which 7 | makes the collision and movement logic a lot easier to implement. [[https://ocamllabs.github.io/learn-ocaml-workshop/frogger.html][Here's]] an 8 | example of the finished product. Try pressing the keys ~i~ and ~u~ to see some 9 | fun tricks! 10 | 11 | * ~js_of_ocaml~ 12 | This version of Frogger will run in the browser by using ~js-of-ocaml~ to 13 | transpile OCaml bytecode into Javascript. Fortunately for us, ~dune~ 14 | supports this out of the box: all one needs to do is ask for the ~.bc.js~ 15 | target (see the definition of the ~DEFAULT~ alias in the [[file:dune][dune]] file). 16 | 17 | First, test your installation of ~js-of-ocaml~ by running ~make~ in the 18 | [[file:test-js-of-ocaml-install][test-js-of-ocaml-install]] directory. Then, point your browser at 19 | ~_build/default/03-frogger/test-js-of-ocaml-install/index.html~. 20 | 21 | * Writing a game in functional programming style 22 | We have written a simple scaffold that handles graphics, events and 23 | interactions with the DOM so you can focus on implementing just the game 24 | logic. [[file:scaffold.mli][scaffold.mli]] defines modules and types that you'll need to use, like 25 | the number and kinds of rows in the playing board, and images of characters. 26 | 27 | Take a look at [[file:frogger.mli][frogger.mli]]. This is the interface you will implement by 28 | writing a corresponding ~frogger.ml~. The contract between the scaffold and 29 | your code is that you implement the four functions at the bottom of this 30 | ~.mli~, and the scaffold will call those functions with the appropriate events 31 | at the right times. 32 | 33 | A ~World.t~ represents the entire state of the game at a given point in time. 34 | It is up to you to define what goes in the type -- that's why ~frogger.mli~ 35 | does not specify what's inside the type (we call this an /opaque/ type). 36 | However, to help you get started, we've specified some function signatures in 37 | the ~World~ module [[file:suggested_frogger.mli][here]] that are likely to be useful. 38 | 39 | To specify the logic of the game, you'll need to figure out the following 40 | things (these correspond to the functions in [[file:frogger.mli][frogger.mli]]): 41 | 42 | ** How to create the world 43 | This is the ~create~ function. You may want to use the ~Random~ module (from 44 | ~Base~) to make life interesting. 45 | 46 | ** How to advance the world one timestep 47 | For this first project, we'll say that time advances only in units of 1 48 | second. The ~tick~ function should implement how the ~World.t~ is transformed 49 | when time advances. Note its signature: 50 | 51 | #+BEGIN_SRC ocaml 52 | val tick : World.t -> World.t 53 | #+END_SRC 54 | 55 | It takes a ~World.t~ and returns a new ~World.t~. Writing your game in this 56 | functional style will allow us to do some interesting things later on. 57 | 58 | ** How to respond to player input 59 | Players can press one of the four arrow keys to move their character around. 60 | You specify what to do when they do that by writing a ~handle_input~ 61 | function. 62 | 63 | All this function needs to know is: what the current state of the world is (a 64 | ~World.t~), and what button player pressed (a ~Key.t~). Its output: the 65 | resulting state of the world (a ~World.t~). 66 | 67 | ** ~handle_event~: dispatch to ~tick~ or ~handle_input~ 68 | It's nice to be able to say, "The only things that happen in this game are: 69 | time progressing, and the player doing something." Your ~handle_event~ 70 | function should just ~match~ on the kind of event and dispatch to the 71 | appropriate handler (one of the two above). 72 | 73 | ** How to draw the world 74 | A list of tuples ~(Image.t, Position.t)~ tell the scaffold which images to 75 | draw where, and in what order: images later in the list will be overlaid on 76 | top of earlier ones at the same position. 77 | 78 | * That seems awfully complicated! How should I start? 79 | A reasonable starting point is to just move the frog (well, camel) around the 80 | game board. Read [[file:frogger.ml][frogger.ml]] and follow the suggestions to build this basic game. 81 | 82 | * Build and run 83 | Type =make= to build, and point your browser to 84 | =_build/default/03-frogger/index.html= to play the game! 85 | 86 | * Extensions 87 | ** AI 88 | Write an AI player for your game. Given the initial ~World.t~, it should emit 89 | a sequence of ~Key.t option~, one for every timestep. 90 | 91 | To see your AI in action you will need to modify the scaffold a little: 92 | instead of feeding player input into the ~handle_input~ function, make it 93 | feed in the output of the AI you write. 94 | 95 | *** Some interesting extensions once you've written an AI 96 | 1. Does your ~create~ function ever produce initial states that cannot be 97 | played to a win? 98 | 99 | 2. How would you write AI to deal with potential randomness in the ~tick~ 100 | function? 101 | 102 | ** Continuous time 103 | While the interpolating scaffold is a neat trick, it's not perfect because 104 | the collision detection logic is now out-of-sync with what's going on 105 | visually. Extend the interface, game logic and scaffold to produce a 106 | smoothly-animated Frogger that also plays right. 107 | -------------------------------------------------------------------------------- /04-frogger/assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/background.png -------------------------------------------------------------------------------- /04-frogger/assets/buggy-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/buggy-left.png -------------------------------------------------------------------------------- /04-frogger/assets/buggy-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/buggy-right.png -------------------------------------------------------------------------------- /04-frogger/assets/camel-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/camel-down.png -------------------------------------------------------------------------------- /04-frogger/assets/camel-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/camel-left.png -------------------------------------------------------------------------------- /04-frogger/assets/camel-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/camel-right.png -------------------------------------------------------------------------------- /04-frogger/assets/camel-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/camel-up.png -------------------------------------------------------------------------------- /04-frogger/assets/carpet_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/carpet_blue.png -------------------------------------------------------------------------------- /04-frogger/assets/carpet_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/carpet_green.png -------------------------------------------------------------------------------- /04-frogger/assets/carpet_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/carpet_red.png -------------------------------------------------------------------------------- /04-frogger/assets/confetti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/confetti.png -------------------------------------------------------------------------------- /04-frogger/assets/police-car-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/police-car-left.png -------------------------------------------------------------------------------- /04-frogger/assets/police-car-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/police-car-right.png -------------------------------------------------------------------------------- /04-frogger/assets/red-pickup-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/red-pickup-left.png -------------------------------------------------------------------------------- /04-frogger/assets/red-pickup-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/red-pickup-right.png -------------------------------------------------------------------------------- /04-frogger/assets/skull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/skull.png -------------------------------------------------------------------------------- /04-frogger/assets/truck-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/truck-left.png -------------------------------------------------------------------------------- /04-frogger/assets/truck-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sudha247/learn-ocaml-workshop/b46ee4375098adf7c299a245304516fffed7e0cd/04-frogger/assets/truck-right.png -------------------------------------------------------------------------------- /04-frogger/config.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | open! Import 3 | 4 | open Scaffold 5 | 6 | type t = 7 | { num_cols : int 8 | ; num_rows : int 9 | ; grid_size_in_px : int 10 | ; render_interval_ms : float 11 | ; logic_interval_ms : float 12 | } 13 | 14 | let default = 15 | { num_rows = List.length Board.rows 16 | ; num_cols = Board.num_cols 17 | ; grid_size_in_px = 50 18 | ; render_interval_ms = 50. 19 | ; logic_interval_ms = 1000. 20 | } 21 | 22 | let width t = t.num_cols * t.grid_size_in_px 23 | let height t = t.num_rows * t.grid_size_in_px 24 | 25 | 26 | -------------------------------------------------------------------------------- /04-frogger/draw.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | open! Js_of_ocaml 3 | open! Import 4 | 5 | open Scaffold 6 | 7 | module Screen = struct 8 | type t = 9 | { context : Html.canvasRenderingContext2D Js.t 10 | ; width : int 11 | ; height : int 12 | } 13 | end 14 | 15 | module Image_impl = struct 16 | type t = 17 | { image_element : Html.imageElement Js.t 18 | } 19 | 20 | let create path = 21 | let image_element = Html.createImg document in 22 | image_element##.src := Js.string path; 23 | { image_element 24 | } 25 | ;; 26 | 27 | (* If we were using a concurrency library like [Async] or [Lwt], we would want 28 | to make [width] and [height] members of the record. But they can only be 29 | read after the image has loaded. *) 30 | let width t = jsoptdef_value_exn (t.image_element##.naturalWidth ) 31 | let height t = jsoptdef_value_exn (t.image_element##.naturalHeight) 32 | 33 | let draw (screen : Screen.t) t x y img_width img_height = 34 | let f = Int.to_float in 35 | screen.context##drawImage_full 36 | t.image_element 37 | 0. 0. 38 | (width t |> f) (height t |> f) 39 | x y 40 | (f img_width) (f img_height) 41 | end 42 | 43 | module Wad = struct 44 | type t = 45 | { background : Image_impl.t 46 | ; skull_and_crossbones : Image_impl.t 47 | ; frog_up : Image_impl.t 48 | ; frog_down : Image_impl.t 49 | ; frog_left : Image_impl.t 50 | ; frog_right : Image_impl.t 51 | ; car1_left : Image_impl.t 52 | ; car2_left : Image_impl.t 53 | ; car1_right : Image_impl.t 54 | ; car2_right : Image_impl.t 55 | ; car3_left : Image_impl.t 56 | ; car3_right : Image_impl.t 57 | ; confetti : Image_impl.t 58 | ; log1 : Image_impl.t 59 | ; log2 : Image_impl.t 60 | ; log3 : Image_impl.t 61 | } 62 | [@@deriving fields] 63 | 64 | let create (_config : Config.t) = 65 | let background = Image_impl.create "assets/background.png" in 66 | let skull_and_crossbones = Image_impl.create "assets/skull.png" in 67 | let frog_up = Image_impl.create "assets/camel-up.png" in 68 | let frog_down = Image_impl.create "assets/camel-down.png" in 69 | let frog_left = Image_impl.create "assets/camel-left.png" in 70 | let frog_right = Image_impl.create "assets/camel-right.png" in 71 | let car1_left = Image_impl.create "assets/buggy-left.png" in 72 | let car1_right = Image_impl.create "assets/buggy-right.png" in 73 | let car2_left = Image_impl.create "assets/truck-left.png" in 74 | let car2_right = Image_impl.create "assets/truck-right.png" in 75 | let car3_left = Image_impl.create "assets/police-car-left.png" in 76 | let car3_right = Image_impl.create "assets/police-car-right.png" in 77 | let log1 = Image_impl.create "assets/carpet_blue.png" in 78 | let log2 = Image_impl.create "assets/carpet_green.png" in 79 | let log3 = Image_impl.create "assets/carpet_red.png" in 80 | let confetti = Image_impl.create "assets/confetti.png" in 81 | { background 82 | ; skull_and_crossbones 83 | ; frog_up 84 | ; frog_down 85 | ; frog_left 86 | ; frog_right 87 | ; car1_left 88 | ; car2_left 89 | ; car1_right 90 | ; car2_right 91 | ; car3_left 92 | ; car3_right 93 | ; confetti 94 | ; log1 95 | ; log2 96 | ; log3 97 | } 98 | ;; 99 | 100 | let lookup_image t (image : Image.t) = 101 | match image with 102 | | Frog_up -> t.frog_up 103 | | Frog_down -> t.frog_down 104 | | Frog_left -> t.frog_left 105 | | Frog_right -> t.frog_right 106 | 107 | | Car1_left -> t.car1_left 108 | | Car1_right -> t.car1_right 109 | | Car2_left -> t.car2_left 110 | | Car2_right -> t.car2_right 111 | | Car3_left -> t.car3_left 112 | | Car3_right -> t.car3_right 113 | 114 | | Log1 -> t.log1 115 | | Log2 -> t.log2 116 | | Log3 -> t.log3 117 | 118 | | Confetti -> t.confetti 119 | | Skull_and_crossbones -> t.skull_and_crossbones 120 | end 121 | -------------------------------------------------------------------------------- /04-frogger/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name main) 3 | (preprocess 4 | (pps js_of_ocaml-ppx ppx_jane)) 5 | (modules_without_implementation suggested_frogger) 6 | (libraries base js_of_ocaml)) 7 | 8 | (alias 9 | (name DEFAULT) 10 | (deps 11 | main.bc.js 12 | index.html 13 | (glob_files assets/*.png) 14 | Dockerfile)) 15 | -------------------------------------------------------------------------------- /04-frogger/frogger.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | open Scaffold 3 | 4 | [@@@warning "-27-32"] 5 | 6 | module Frog = struct 7 | type t = 8 | { position : Position.t 9 | } [@@deriving fields] 10 | 11 | let create = Fields.create 12 | end 13 | 14 | module World = struct 15 | type t = 16 | { frog : Frog.t 17 | } [@@deriving fields] 18 | 19 | let create = Fields.create 20 | end 21 | 22 | let create_frog () = 23 | failwith 24 | "Figure out how to initialize the [Frog.t] at the beginning of the game. \ 25 | Call [Frog.create] with some arguments." 26 | ;; 27 | 28 | let create () = 29 | failwith 30 | "Call [World.create] and [create_frog] to construct the initial state \ 31 | of the game. Try using [Random.int] -- variety is the spice of life!" 32 | ;; 33 | 34 | let tick (world : World.t) = 35 | failwith 36 | "This function will end up getting called every timestep, which happens to \ 37 | be set to 1 second for this game in the scaffold (so you can easily see \ 38 | what's going on). For the first step (just moving the frog/camel around), \ 39 | you can just return [world] here. Later you'll want do interesting things \ 40 | like move all the cars and logs, detect collisions and figure out if the \ 41 | player has died or won. " 42 | ;; 43 | 44 | let handle_input (world : World.t) key = 45 | failwith 46 | "This function will end up getting called whenever the player presses one of \ 47 | the four arrow keys. What should the new state of the world be? Create and \ 48 | return it based on the current state of the world (the [world] argument), \ 49 | and the key that was pressed ([key]). Use either [World.create] or the \ 50 | record update syntax: 51 | { world with frog = Frog.create ... } 52 | " 53 | ;; 54 | 55 | let draw (world : World.t) = 56 | failwith 57 | "Return a list with a single item: a tuple consisting of one of the choices \ 58 | in [Images.t] in [scaffold.mli]; and the current position of the [Frog]." 59 | ;; 60 | 61 | let handle_event world event = 62 | failwith 63 | "This function should probably be just 3 lines long: [match event with ...]" 64 | ;; 65 | 66 | let finished world = 67 | failwith 68 | "This can probably just return [false] in the beginning." 69 | ;; 70 | -------------------------------------------------------------------------------- /04-frogger/frogger.mli: -------------------------------------------------------------------------------- 1 | open Scaffold 2 | 3 | module World : sig 4 | type t 5 | end 6 | 7 | val create : unit -> World.t 8 | val handle_event : World.t -> Event.t -> World.t 9 | val draw : World.t -> Display_list.t 10 | val finished : World.t -> bool 11 | 12 | 13 | -------------------------------------------------------------------------------- /04-frogger/import.ml: -------------------------------------------------------------------------------- 1 | open Js_of_ocaml 2 | 3 | module Html = Dom_html 4 | let document = Html.window##.document 5 | 6 | let jsopt_value_exn x = Js.Opt.get x (fun () -> assert false) 7 | let jsoptdef_value_exn x = Js.Optdef.get x (fun () -> assert false) 8 | -------------------------------------------------------------------------------- /04-frogger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Frogger 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /04-frogger/main.ml: -------------------------------------------------------------------------------- 1 | open! Base 2 | open! Js_of_ocaml 3 | open! Import 4 | 5 | open Scaffold 6 | open Draw 7 | 8 | [@@@warning "-27"] 9 | 10 | let draw_background screen (config : Config.t) (wad : Wad.t) = 11 | Image_impl.draw screen wad.background 12 | 0. 0. 13 | screen.width screen.height 14 | ;; 15 | 16 | let render 17 | config 18 | wad 19 | (screen : Screen.t) 20 | (dl_current : Display_list.t) 21 | (dl_next : Display_list.t) 22 | (alpha : float) 23 | = 24 | draw_background screen config wad; 25 | let m = config.Config.grid_size_in_px in 26 | let grid_to_screen (p : Position.t) = 27 | p.x * m, 28 | (config.num_rows - p.y - 1) * m 29 | in 30 | let interpolate_x cur next = 31 | (* This isn't quite perfect: really, we should draw two copies, not just 32 | one, when the sprite is wrapping around the edge. Left as an exercise for 33 | the student! *) 34 | if Int.abs (cur - next) < (config.num_cols - 1) * m 35 | then 36 | (1. -. alpha) *. (Int.to_float cur) +. (alpha *. (Int.to_float next)) 37 | else ( 38 | let next = 39 | if next > cur 40 | then 41 | next - config.num_cols * m 42 | else 43 | next + config.num_cols * m 44 | in 45 | (1. -. alpha) *. (Int.to_float cur) +. (alpha *. (Int.to_float next)) 46 | ) 47 | in 48 | let interpolate_y cur next = (1. -. alpha) *. (Int.to_float cur) +. (alpha *. (Int.to_float next)) in 49 | List.iter (List.zip_exn dl_current dl_next) 50 | ~f:(fun ((image_cur, pos_cur), (image_next, pos_next)) -> 51 | let x_cur , y_cur = grid_to_screen pos_cur in 52 | let x_next, y_next = grid_to_screen pos_next in 53 | let x = interpolate_x x_cur x_next in 54 | let y = interpolate_y y_cur y_next in 55 | Image_impl.draw 56 | screen 57 | (Wad.lookup_image wad image_cur) 58 | x y 59 | m m) 60 | ;; 61 | 62 | module Game_impl = struct 63 | type 'world t = 64 | { init : 'world 65 | ; handle_event : 'world -> Event.t -> 'world 66 | ; draw : 'world -> Display_list.t 67 | ; finished : 'world -> bool 68 | } 69 | end 70 | 71 | module Scaffold_state = struct 72 | type 'world t = 73 | { world_cur : 'world 74 | ; world_next : 'world option 75 | ; interpolation_alpha : float 76 | ; should_interpolate : bool 77 | ; history : 'world list 78 | } 79 | 80 | let create (world_init : 'world) = 81 | { world_cur = world_init 82 | ; world_next = None 83 | ; interpolation_alpha = 0. 84 | ; should_interpolate = false 85 | ; history = [ ] 86 | } 87 | 88 | let render config wad screen (game_impl : 'world Game_impl.t) t = 89 | let world_next = Option.value ~default:t.world_cur t.world_next in 90 | let alpha = 91 | if t.should_interpolate 92 | then 93 | t.interpolation_alpha -. 0.5 94 | else 95 | 0. 96 | in 97 | render config wad screen 98 | (game_impl.draw t.world_cur) 99 | (game_impl.draw world_next) 100 | alpha 101 | ;; 102 | 103 | let set_world_next_if_interpolating (game_impl : 'world Game_impl.t) t = 104 | let world_next = 105 | if t.should_interpolate 106 | then 107 | Some (game_impl.handle_event t.world_cur Tick) 108 | else 109 | (* Do not call [game_impl.handle_event] when not interpolating, 110 | otherwise student implementations using mutable state will behave 111 | unexpectedly. *) 112 | t.world_next 113 | in 114 | { t with world_next } 115 | ;; 116 | 117 | let set_world_cur_and_save_history (game_impl : 'world Game_impl.t) world_cur t = 118 | let history = 119 | if game_impl.finished world_cur 120 | then t.history 121 | else t.world_cur :: t.history 122 | in 123 | { t with 124 | world_cur 125 | ; history 126 | } 127 | ;; 128 | 129 | let apply_tick (game_impl : 'world Game_impl.t) t = 130 | let world_cur = game_impl.handle_event t.world_cur Tick in 131 | set_world_next_if_interpolating game_impl ( 132 | set_world_cur_and_save_history 133 | game_impl 134 | world_cur 135 | { t with interpolation_alpha = 0. }) 136 | ;; 137 | 138 | let toggle_interpolation (game_impl : 'world Game_impl.t) t = 139 | set_world_next_if_interpolating game_impl { t with should_interpolate = not t.should_interpolate } 140 | ;; 141 | 142 | let apply_keypress (game_impl : 'world Game_impl.t) t which_key = 143 | let world_cur = game_impl.handle_event t.world_cur (Keypress which_key) in 144 | set_world_next_if_interpolating 145 | game_impl 146 | (set_world_cur_and_save_history 147 | game_impl 148 | world_cur 149 | t) 150 | ;; 151 | 152 | let undo (game_impl : 'world Game_impl.t) t = 153 | match t.history with 154 | | [] -> 155 | { t with interpolation_alpha = 0. } 156 | | x :: xs -> 157 | set_world_next_if_interpolating game_impl 158 | { t with 159 | world_cur = x 160 | ; history = xs 161 | ; interpolation_alpha = 0. 162 | } 163 | ;; 164 | end 165 | 166 | let init_event_handlers 167 | (config : Config.t) 168 | (screen : Screen.t) 169 | wad 170 | (game_impl : _ Game_impl.t) 171 | = 172 | let scaffold_state = ref (Scaffold_state.create game_impl.init) in 173 | let _ = 174 | Html.window##setInterval (Js.wrap_callback (fun () -> 175 | let () = Scaffold_state.render config wad screen game_impl !scaffold_state in 176 | let new_interpolation_alpha = 177 | (!scaffold_state).interpolation_alpha +. 178 | config.render_interval_ms /. config.logic_interval_ms 179 | in 180 | scaffold_state := 181 | if Float.(>=) new_interpolation_alpha 1.0 182 | then Scaffold_state.apply_tick game_impl !scaffold_state 183 | else 184 | { !scaffold_state with interpolation_alpha = new_interpolation_alpha } 185 | )) 186 | config.render_interval_ms 187 | in 188 | Html.window##.onkeydown := (Dom.handler (fun key_event -> 189 | let new_scaffold_state cur_scaffold_state = 190 | let handle_keypress which_key = 191 | Scaffold_state.apply_keypress game_impl cur_scaffold_state which_key 192 | in 193 | let key = jsoptdef_value_exn key_event##.key in 194 | match Js.to_string key with 195 | | "ArrowUp" -> handle_keypress Arrow_up 196 | | "ArrowDown" -> handle_keypress Arrow_down 197 | | "ArrowLeft" -> handle_keypress Arrow_left 198 | | "ArrowRight" -> handle_keypress Arrow_right 199 | | "i" -> Scaffold_state.toggle_interpolation game_impl cur_scaffold_state 200 | | "u" -> Scaffold_state.undo game_impl cur_scaffold_state 201 | | _ -> cur_scaffold_state 202 | in 203 | scaffold_state := new_scaffold_state !scaffold_state; 204 | Js._true)) 205 | ;; 206 | 207 | let create_canvas (config : Config.t) = 208 | let canvas = Html.createCanvas document in 209 | canvas##.width := Config.width config; 210 | canvas##.height := Config.height config; 211 | canvas 212 | ;; 213 | 214 | let initialize_and_main_loop (config : Config.t) wad (game : _ Game_impl.t) = 215 | let board_div = jsopt_value_exn (document##getElementById (Js.string "board")) in 216 | let canvas = create_canvas config in 217 | Dom.appendChild board_div canvas; 218 | let context = canvas##getContext (Html._2d_) in 219 | let screen = { Screen.context ; width = Config.width config ; height = Config.height config } in 220 | init_event_handlers config screen wad game 221 | ;; 222 | 223 | let main game = 224 | (* It's important to load assets here rather than in the onload handler, so 225 | that the handler runs after they're loaded. This is an okay thing to do for 226 | a simple program like this which knows all the things it needs to load at 227 | startup. 228 | 229 | When that's not a tenable strategy, you must wait for images (and/or other 230 | assets) to load before using them. While this _can_ be done with callbacks, 231 | a monadic concurrency library (conceptually related to futures and 232 | promises) is the idiomatic way to handle that in OCaml. *) 233 | let wad = Wad.create Config.default in 234 | Html.window##.onload := Html.handler ( 235 | fun _ -> 236 | let () = 237 | initialize_and_main_loop 238 | Config.default 239 | wad 240 | game 241 | in 242 | Js._false) 243 | 244 | let () = 245 | let game = 246 | { Game_impl. 247 | init = Frogger.create () 248 | ; handle_event = Frogger.handle_event 249 | ; draw = Frogger.draw 250 | ; finished = Frogger.finished 251 | } 252 | in 253 | main game 254 | ;; 255 | -------------------------------------------------------------------------------- /04-frogger/scaffold.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | module Position = struct 4 | type t = 5 | { x : int 6 | ; y : int 7 | } [@@deriving fields, sexp] 8 | 9 | let create = Fields.create 10 | end 11 | 12 | module Image = struct 13 | type t = 14 | | Frog_up 15 | | Frog_down 16 | | Frog_left 17 | | Frog_right 18 | 19 | | Car1_left 20 | | Car1_right 21 | | Car2_left 22 | | Car2_right 23 | | Car3_left 24 | | Car3_right 25 | 26 | | Log1 27 | | Log2 28 | | Log3 29 | 30 | | Confetti 31 | | Skull_and_crossbones 32 | [@@deriving sexp, variants] 33 | end 34 | 35 | module Display_list = struct 36 | module Display_command = struct 37 | type nonrec t = Image.t * Position.t [@@deriving sexp] 38 | end 39 | 40 | type t = Display_command.t list [@@deriving sexp] 41 | end 42 | 43 | module Key = struct 44 | type t = 45 | | Arrow_up 46 | | Arrow_down 47 | | Arrow_left 48 | | Arrow_right 49 | end 50 | 51 | module Event = struct 52 | type t = 53 | | Tick 54 | | Keypress of Key.t 55 | end 56 | 57 | module Board = struct 58 | (** Every row of the game board is one of these three kinds. *) 59 | module Row = struct 60 | type t = 61 | | Safe_strip 62 | | Road 63 | | River 64 | end 65 | 66 | let num_cols = 10 67 | 68 | (** The first and last rows are guaranteed to be [Safe_strip]s. *) 69 | let rows = 70 | let open Row in 71 | [ Safe_strip 72 | ; River 73 | ; River 74 | ; River 75 | ; River 76 | ; River 77 | ; Safe_strip 78 | ; Road 79 | ; Road 80 | ; Road 81 | ; Road 82 | ; Safe_strip 83 | ] 84 | |> List.rev 85 | ;; 86 | end 87 | 88 | let console_log s = 89 | Js_of_ocaml.Firebug.console##log s 90 | ;; 91 | -------------------------------------------------------------------------------- /04-frogger/scaffold.mli: -------------------------------------------------------------------------------- 1 | (** The grid system: 2 | 3 | 0. The positions of all objects are snapped onto a coarse grid. 4 | 1. The frog is 1x1 5 | 2. Every car is 1x1 6 | 3. Every log is 1x1 7 | *) 8 | 9 | (** The playable area of the screen will be referred to as the [board]. *) 10 | module Board : sig 11 | 12 | (** Every row of the game board is one of these three kinds. *) 13 | module Row : sig 14 | type t = 15 | | Safe_strip 16 | | Road 17 | | River 18 | end 19 | 20 | val num_cols : int 21 | 22 | (** The first and last rows are guaranteed to be [Safe_strip]s. *) 23 | val rows : Row.t list 24 | end 25 | 26 | (** This is a position in the grid system of the game, not in screen pixels. *) 27 | module Position : sig 28 | type t = 29 | { x : int 30 | ; y : int 31 | } [@@deriving fields, sexp] 32 | 33 | val create : x:int -> y:int -> t 34 | end 35 | 36 | (** All these images are 1x1. *) 37 | module Image : sig 38 | type t = 39 | | Frog_up 40 | | Frog_down 41 | | Frog_left 42 | | Frog_right 43 | 44 | | Car1_left 45 | | Car1_right 46 | | Car2_left 47 | | Car2_right 48 | | Car3_left 49 | | Car3_right 50 | 51 | | Log1 52 | | Log2 53 | | Log3 54 | 55 | | Confetti 56 | | Skull_and_crossbones 57 | [@@deriving sexp, variants] 58 | end 59 | 60 | module Display_list : sig 61 | (** The [Display_command] [(image, pos)] represents a command to draw [image] 62 | with its leftmost grid point at [pos]. 63 | *) 64 | module Display_command : sig 65 | type nonrec t = Image.t * Position.t [@@deriving sexp] 66 | end 67 | 68 | type t = Display_command.t list [@@deriving sexp] 69 | end 70 | 71 | module Key : sig 72 | type t = 73 | | Arrow_up 74 | | Arrow_down 75 | | Arrow_left 76 | | Arrow_right 77 | end 78 | 79 | module Event : sig 80 | type t = 81 | | Tick 82 | | Keypress of Key.t 83 | end 84 | 85 | val console_log : string -> unit 86 | -------------------------------------------------------------------------------- /04-frogger/suggested_frogger.mli: -------------------------------------------------------------------------------- 1 | open Scaffold 2 | 3 | module Direction : sig 4 | type t 5 | end 6 | 7 | module Frog : sig 8 | type t 9 | val facing : t -> Direction.t 10 | val position : t -> Position.t 11 | end 12 | 13 | module Non_frog_character : sig 14 | module Kind : sig 15 | type t = 16 | | Car 17 | | Log 18 | end 19 | 20 | type t 21 | 22 | val kind : t -> Kind.t 23 | val position : t -> Position.t 24 | 25 | (** In units of grid-points/tick. Positive values indicate rightward motion, 26 | negative values leftward motion. *) 27 | val horizontal_speed : t -> int 28 | end 29 | 30 | module Game_state : sig 31 | type t = 32 | | Playing 33 | | Won 34 | | Dead 35 | end 36 | 37 | module World : sig 38 | type t 39 | 40 | val frog : t -> Frog.t 41 | val nfcs : t -> Non_frog_character.t list 42 | 43 | val state : t -> Game_state.t 44 | end 45 | 46 | val create : unit -> World.t 47 | val tick : World.t -> World.t 48 | val handle_input : World.t -> Key.t -> World.t 49 | val draw : World.t -> Display_list.t 50 | val finished : World.t -> bool 51 | 52 | val handle_event : World.t -> Event.t -> World.t 53 | 54 | 55 | -------------------------------------------------------------------------------- /04-frogger/test-js-of-ocaml-install/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | dune build @DEFAULT 3 | echo "\nThe outputs (including index.html) go in the _build directory that's \ 4 | a couple directories up. Read more about what determined that here:\n \ 5 | http://dune.readthedocs.io/en/latest/usage.html#finding-the-root\n" 6 | echo "\nPoint your browser to\n ${PWD}/../../_build/default/03-frogger/test-js-of-ocaml-install/index.html" 7 | -------------------------------------------------------------------------------- /04-frogger/test-js-of-ocaml-install/dune: -------------------------------------------------------------------------------- 1 | ;; -*- scheme -*- 2 | 3 | (executables 4 | (names main) 5 | (preprocess 6 | (pps js_of_ocaml-ppx)) 7 | (libraries base js_of_ocaml)) 8 | 9 | (alias 10 | (name DEFAULT) 11 | (deps main.bc.js index.html)) 12 | -------------------------------------------------------------------------------- /04-frogger/test-js-of-ocaml-install/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Frogger 7 | 8 | 9 | 17 | 18 | 19 |
20 | 21 | -------------------------------------------------------------------------------- /04-frogger/test-js-of-ocaml-install/main.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | open Js_of_ocaml 3 | (* ## - method will get called as soon I deref. 4 | `##.prop_name := ` to set a property 5 | ##.prop_name to read (no deref) 6 | 7 | put a table here ; bg here ; each cell has an id 8 | *) 9 | 10 | let get_foo_div () = 11 | Option.value_exn ( 12 | (Js.Opt.to_option (Dom_html.document##getElementById (Js.string "foo")))) 13 | ;; 14 | 15 | let () = 16 | Dom_html.window##.onload := (Dom.handler (fun _ -> 17 | let foo_div = get_foo_div () in 18 | foo_div##.textContent := Js.Opt.return (Js.string "Hello, world!"); 19 | Js._true 20 | )); 21 | Dom_html.window##.onkeydown := (Dom.handler (fun key_event -> 22 | let foo_div = get_foo_div () in 23 | let key = Option.value_exn (Js.Optdef.to_option (key_event##.key)) in 24 | let () = 25 | match Js.to_string key with 26 | | "ArrowUp" 27 | | "ArrowDown" 28 | | "ArrowLeft" 29 | | "ArrowRight" -> foo_div##.textContent := Js.Opt.return key 30 | | _ -> () 31 | in 32 | Js._true)); 33 | let ticktock = ref "tick" in 34 | let _ = 35 | Dom_html.window##setInterval (Js.wrap_callback (fun () -> 36 | let foo_div = get_foo_div () in 37 | foo_div##.textContent := Js.Opt.return (Js.string !ticktock); 38 | ticktock := ( 39 | match !ticktock with 40 | | "tick" -> "tock" 41 | | "tock" -> "tick" 42 | | _ -> "error" 43 | ) 44 | )) 45 | 1000.0 46 | in 47 | () 48 | ;; 49 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | dune build @DEFAULT 3 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: IndiaFOSS 2023 OCaml Workshop 2 | 3 | This repo contains exercises and build instructions to help you get started 4 | developing in OCaml. 5 | 6 | * Installing build tools and libraries 7 | See [[https://github.com/ocamllabs/install-ocaml/blob/master/README.org][README.org in install-ocaml]] for instructions. 8 | * Exercises 9 | The [[file:02-exercises][exercises]] directory contains a number of exercises to get you started with 10 | OCaml. Each one has some expect-tests embedded in it. The workflow is: 11 | 12 | #+BEGIN_SRC bash 13 | cd 02-exercises/$problem_dir 14 | 15 | dune runtest # builds and runs inline tests 16 | # Look at test output and compiler errors, edit problem.ml, rerun: 17 | dune runtest 18 | #+END_SRC 19 | * Github 20 | Now you're done with the exercises, dive into monads and implement a part 21 | of GitHub's own =hub= CLI, using the github library from MirageOS. 22 | 23 | See the [[file:03-github][github README]] to get started! 24 | * Frogger 25 | Now you will implement a simplified clone of the classic arcade game Frogger. 26 | 27 | See the [[file:04-frogger][frogger README]] to get started! 28 | 29 | * Documentation and resources 30 | ** OCaml 31 | - [[https://dev.realworldocaml.org/toc.html][Real World OCaml]] 32 | - [[http://caml.inria.fr/pub/docs/manual-ocaml/][OCaml manual]] 33 | - [[https://docs.mirage.io][MirageOS API Documentation]] 34 | ** Jane Street libraries and tools 35 | - [[https://opensource.janestreet.com/][An overview of Jane Street's open source things]] 36 | - [[https://ocaml.janestreet.com/ocaml-core/v0.10/doc/][Documentation for Core]] 37 | ** dune 38 | - [[https://www.youtube.com/watch?v=BNZhmMAJarw][Video tutorial]] 39 | - [[https://dune.readthedocs.io/en/latest/][Manual]] 40 | 41 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.2) 2 | -------------------------------------------------------------------------------- /make_learn_ocaml_directory.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir ../learn-ocaml 4 | cp Makefile ../learn-ocaml/ 5 | 6 | dune clean 7 | cp -r 02-exercises/ ../learn-ocaml/01-exercises 8 | cp -r 03-lumines/ ../learn-ocaml/02-lumines 9 | 10 | dune build solutions/lumines/bin/lumines.exe 11 | cp _build/default/solutions/lumines/bin/lumines.exe ../learn-ocaml/lumines_demo.exe 12 | -------------------------------------------------------------------------------- /solutions/frogger/frogger.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | open Scaffold 3 | 4 | module Direction = struct 5 | type t = 6 | | Up 7 | | Down 8 | | Left 9 | | Right 10 | end 11 | 12 | module Frog = struct 13 | type t = 14 | { position : Position.t 15 | ; facing : Direction.t 16 | } [@@deriving fields] 17 | 18 | let create = Fields.create 19 | end 20 | 21 | module Non_frog_character = struct 22 | module Kind = struct 23 | type t = 24 | | Car 25 | | Log 26 | end 27 | 28 | type t = 29 | { horizontal_speed : int 30 | ; position : Position.t 31 | ; kind : Kind.t 32 | ; image : Image.t 33 | } [@@deriving fields] 34 | 35 | let create = Fields.create 36 | end 37 | 38 | module Game_state = struct 39 | type t = 40 | | Playing 41 | | Won 42 | | Dead 43 | end 44 | 45 | module World = struct 46 | type t = 47 | { state : Game_state.t 48 | ; frog : Frog.t 49 | ; nfcs : Non_frog_character.t list 50 | } [@@deriving fields] 51 | 52 | let create = Fields.create 53 | end 54 | 55 | let create_frog () = 56 | let position = 57 | Position.create 58 | ~x:(Scaffold.Board.num_cols / 2) 59 | ~y:0 60 | in 61 | Frog.create ~position ~facing:Direction.Up 62 | ;; 63 | 64 | let create_nfcs () = 65 | let max_speed = 1 in 66 | List.mapi 67 | Scaffold.Board.rows 68 | ~f:(fun idx row -> 69 | let make_nfc kind col direction_sign = 70 | let horizontal_speed = 71 | direction_sign * (1 + Random.int max_speed) 72 | in 73 | let position = Position.create ~x:col ~y:idx in 74 | let image = 75 | match (kind : Non_frog_character.Kind.t) with 76 | | Car -> ( 77 | let dir left right = if horizontal_speed < 0 then left else right in 78 | match Random.int 3 with 79 | | 0 -> dir Image.car1_left Image.car1_right 80 | | 1 -> dir Image.car2_left Image.car2_right 81 | | 2 -> dir Image.car3_left Image.car3_right 82 | | _ -> assert false) 83 | | Log -> ( 84 | match Random.int 3 with 85 | | 0 -> Image.log1 86 | | 1 -> Image.log2 87 | | 2 -> Image.log3 88 | | _ -> assert false) 89 | in 90 | Non_frog_character.create ~kind ~horizontal_speed ~position ~image 91 | in 92 | let make_one_row kind = 93 | let num_nfcs_per_row = 3 in 94 | let max_gap = 3 in 95 | let start_pos = Random.int Board.num_cols in 96 | let gap_to_leave_between_nfcs = 97 | match (kind : Non_frog_character.Kind.t) with 98 | | Car -> 1 + Random.int max_gap 99 | | Log -> 0 100 | in 101 | let sign = 2 * (idx % 2) - 1 in (* Alternating directions *) 102 | List.init num_nfcs_per_row 103 | ~f:(fun idx -> 104 | make_nfc 105 | kind 106 | ((start_pos + idx * (gap_to_leave_between_nfcs + 1)) % Board.num_cols) 107 | sign) 108 | in 109 | match row with 110 | | Safe_strip -> [] 111 | | Road -> make_one_row Non_frog_character.Kind.Car 112 | | River -> make_one_row Non_frog_character.Kind.Log) 113 | |> List.concat 114 | ;; 115 | 116 | let create () = 117 | World.create 118 | ~state:Game_state.Playing 119 | ~frog:(create_frog ()) 120 | ~nfcs:(create_nfcs ()) 121 | ;; 122 | 123 | let rec detect_collision (frog_pos : Position.t) nfcs = 124 | let is_colliding (nfc : Non_frog_character.t) = 125 | if Int.(<>) frog_pos.y nfc.position.y 126 | then false 127 | else 128 | let width = 129 | match nfc.kind with 130 | | Car -> 1 131 | | Log -> 1 132 | in 133 | (nfc.position.x <= frog_pos.x) && (frog_pos.x < nfc.position.x + width) 134 | in 135 | match nfcs with 136 | | [] -> None 137 | | nfc :: rest -> if is_colliding nfc then Some nfc else detect_collision frog_pos rest 138 | ;; 139 | 140 | let pos_is_in_river (pos : Position.t) = 141 | match List.nth_exn Scaffold.Board.rows pos.y with 142 | | Safe_strip | Road -> false 143 | | River -> true 144 | ;; 145 | 146 | let should_die frog_pos collision_result = 147 | let frog_is_in_river = pos_is_in_river frog_pos in 148 | match (collision_result : Non_frog_character.t option) with 149 | | Some { kind = Car; _ } -> true 150 | | Some { kind = Log; _ } -> false 151 | | None -> frog_is_in_river 152 | ;; 153 | 154 | let should_win (frog_pos : Position.t) = 155 | Int.(=) frog_pos.y (List.length Scaffold.Board.rows - 1) 156 | ;; 157 | 158 | let compute_new_game_state frog_pos collision_result = 159 | if should_die frog_pos collision_result 160 | then Game_state.Dead 161 | else if should_win frog_pos 162 | then Won 163 | else Playing 164 | ;; 165 | 166 | let tick (world : World.t) = 167 | match world.state with 168 | | Won | Dead -> world 169 | | Playing -> 170 | let new_nfcs = 171 | List.map world.nfcs ~f:(fun nfc -> 172 | let new_position = 173 | Position.create 174 | ~x:((nfc.position.x + nfc.horizontal_speed) % Scaffold.Board.num_cols) 175 | ~y:nfc.position.y 176 | in 177 | { nfc with position = new_position }) 178 | in 179 | let collision_result_before = detect_collision world.frog.position world.nfcs in 180 | let new_frog = 181 | let new_frog_position = 182 | let dx = 183 | match collision_result_before with 184 | | Some { kind = Log; horizontal_speed; _ } -> horizontal_speed 185 | | _ -> 0 186 | in 187 | Position.create ~x:(world.frog.position.x + dx) ~y:(world.frog.position.y) 188 | in 189 | { world.frog with position = new_frog_position } 190 | in 191 | let collision_result_after = detect_collision new_frog.position new_nfcs in 192 | let new_game_state = compute_new_game_state new_frog.position collision_result_after in 193 | World.create ~state:new_game_state ~frog:new_frog ~nfcs:new_nfcs 194 | ;; 195 | 196 | let clamp ~min ~max x = 197 | if x < min then min else if x > max then max else x 198 | ;; 199 | 200 | let handle_input (world : World.t) key = 201 | let num_rows = List.length Scaffold.Board.rows in 202 | let num_cols = Scaffold.Board.num_cols in 203 | match world.state with 204 | | Won | Dead -> world 205 | | Playing -> 206 | let new_frog = 207 | let new_pos, new_dir = 208 | let old_pos = world.frog.position in 209 | match key with 210 | | Key.Arrow_up -> 211 | { old_pos with y = clamp ~min:0 ~max:(num_rows - 1) (old_pos.y + 1)}, Direction.Up 212 | | Key.Arrow_down -> 213 | { old_pos with y = clamp ~min:0 ~max:(num_rows - 1) (old_pos.y - 1)}, Direction.Down 214 | | Key.Arrow_left -> 215 | { old_pos with x = clamp ~min:0 ~max:(num_cols - 1) (old_pos.x - 1)}, Direction.Left 216 | | Key.Arrow_right -> 217 | { old_pos with x = clamp ~min:0 ~max:(num_cols - 1) (old_pos.x + 1)}, Direction.Right 218 | in 219 | Frog.create ~position:new_pos ~facing:new_dir 220 | in 221 | let new_game_state = 222 | let collision_result = detect_collision new_frog.position world.nfcs in 223 | compute_new_game_state new_frog.position collision_result 224 | in 225 | World.create ~state:new_game_state ~frog:new_frog ~nfcs:world.nfcs 226 | ;; 227 | 228 | let draw (world : World.t) = 229 | let draw_frog_command = 230 | let frog_image = 231 | match world.state with 232 | | Dead -> Image.skull_and_crossbones 233 | | Won -> Image.confetti 234 | | Playing -> ( 235 | match world.frog.facing with 236 | | Up -> Image.frog_up 237 | | Down -> Image.frog_down 238 | | Left -> Image.frog_left 239 | | Right -> Image.frog_right) 240 | in 241 | (frog_image, world.frog.position) 242 | in 243 | let draw_nfc (nfc : Non_frog_character.t) = (nfc.image, nfc.position) in 244 | (List.map world.nfcs ~f:draw_nfc) @ [draw_frog_command] 245 | ;; 246 | 247 | let handle_event world event = 248 | match (event : Event.t) with 249 | | Tick -> tick world 250 | | Keypress k -> handle_input world k 251 | ;; 252 | 253 | let finished world = 254 | match World.state world with 255 | | Playing -> false 256 | | Won 257 | | Dead -> true 258 | ;; 259 | -------------------------------------------------------------------------------- /solutions/github/commands.ml: -------------------------------------------------------------------------------- 1 | (* Four possible subcommands for hub *) 2 | 3 | let show_issue ~user ~repo num : unit = 4 | let (issue : Github_t.issue) = 5 | let open Github.Monad in 6 | Github.Issue.get ~user ~repo ~num () 7 | >|= Github.Response.value |> Github.Monad.run |> Lwt_main.run 8 | in 9 | Printf.printf "%d %s\n\n%s\n" issue.issue_number issue.issue_title issue.issue_body 10 | 11 | let read f n s = 12 | let rec loop n s = 13 | let open Github.Monad in 14 | Github.Stream.next s 15 | >>= function 16 | | Some (item, next) when n > 0 -> 17 | f item; 18 | loop (pred n) next 19 | | _ -> 20 | return () 21 | in 22 | loop n s |> Github.Monad.run 23 | 24 | let print_issue {Github_t.issue_number; issue_title; _} = 25 | Printf.printf "%d %s\n" issue_number issue_title 26 | 27 | let list_issues ~user ~repo () : unit = 28 | let program = 29 | let open Lwt.Infix in 30 | Github_cookie_jar.init () 31 | >>= fun jar -> 32 | Github_cookie_jar.get jar ~name:"reasonconf" 33 | >>= function 34 | | None -> Lwt.fail (Failure "invalid token!") 35 | | Some auth -> 36 | let token = Github.Token.of_auth auth in 37 | read print_issue 10 @@ Github.Issue.for_repo ~token ~user ~repo () 38 | in 39 | Lwt_main.run program 40 | 41 | let list_prs ~user ~repo () : unit = 42 | let print_pr {Github_t.pull_number; pull_title; _} = 43 | Printf.printf "%d %s\n" pull_number pull_title 44 | in 45 | read print_pr 10 @@ Github.Pull.for_repo ~user ~repo () 46 | |> Lwt_main.run 47 | 48 | let checkout_pr ~user ~repo ?branch num : unit = 49 | let main = 50 | let open Github.Monad in 51 | Github.Pull.get ~user ~repo ~num () 52 | >>~ fun {Github_t.pull_head = {branch_ref; branch_user; branch_repo; _}; _} -> 53 | let branch = 54 | match branch with 55 | | None -> branch_ref 56 | | Some branch -> branch 57 | in 58 | let branch_slug = 59 | match (branch_user, branch_repo) with 60 | | (Some {Github_t.user_login; _}, Some {Github_t.repository_name; _}) -> 61 | Printf.sprintf "%s/%s:" user_login repository_name 62 | | _ -> "" 63 | in 64 | Printf.printf "Head is %s%s checkout to %s\n" branch_slug branch_ref branch; 65 | return () 66 | in 67 | Lwt_main.run @@ Github.Monad.run main 68 | --------------------------------------------------------------------------------