/ Readme.org
Readme.org
1 #+title: Ocaml Programming: Correct + Efficient + Beautiful 2 #+author: HaQadosch 3 #+date: [2023-12-02 Sat] 4 #+startup: indent 5 #+property: header-args :results output 6 #+link playlist https://www.youtube.com/playlist?list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU 7 #+link opam https://opam.ocaml.org/ 8 #+link docs https://ocaml.org/docs/your-first-program 9 #+link course https://cs3110.github.io/textbook/cover.html 10 * Org mode shortcuts 11 - tangle :: ~org-babel-tangle~, ~C-c C-v C-t~ 12 13 ** Config 14 - ~C-c C-c~ :: Refresh local setup for that file 15 16 ** Dates: 17 - ~C-c .~ :: active timestamps 18 creates an entry in the agenda 19 - ~C-c !~ :: inactive timestamps 20 doesnΒ΄t create an entry in the agenda 21 - link :: [[https://orgmode.org/orgguide.html#Timestamps][timestamps]] 22 23 ** Links: 24 - ~C-c C-l~ :: create link 25 - link :: [[https://orgmode.org/orgguide.html#Hyperlinks][hyperlinks]] 26 27 ** Tables 28 - ~C-c }~ :: display cells references 29 - ~C-c ?~ :: display ref for that cell 30 references are =@ROW$COLUMN= 31 - link :: [[https://orgmode.org/manual/References.html][table references]] 32 - formula :: =@I..II= 33 select all the rows between horizontal lines (hline) 1 and 2 34 =vmean(@I..II);EN= column range mean, treats empty values as null (0) 35 36 ** Whitespace 37 - ~M-x whitespace-mode~ :: Activates a better whitespace mode 38 - ~M-x auto-fill-mode~ :: Auto-fill, enforce max size limit 39 - ~M-q~ :: Force auto-fill on current paragraph 40 41 ** Feetnotes 42 - ~C-c C-x f~ :: Creates a footnote, see [[https://orgmode.org/guide/Creating-Footnotes.html][guide]]. 43 - ~C-c C-c~ :: to jump between definition and refs. 44 45 ** Todos 46 - ~C-c C-t~ :: cycling throught the states of the todos 47 - ~C-u C-c C-t~ :: Log the change of status 48 49 ** Clocking work 50 - ~C-c C-x C-i~ :: Clock in 51 - ~C-c C-x C-o~ :: Clock out 52 - ~C-c C-x C-d~ :: Display clocks 53 54 ** Includes files 55 Start with the directive ~INCLUDE~ and the location of the file 56 - ~C-c '~ :: Visit the included file 57 - ~C-c &~ :: Go back to the original doc 58 π‘ The included doc start at the hierarchy of the inclusion 59 See [[https://orgmode.org/manual/Include-Files.html][the manual]] 60 61 ** Code Block - Structure Template 62 - ~C-c C-,~ :: Insert structure template 63 See [[https://orgmode.org/manual/Structure-Templates.html][17.2 Structure Templates]] 64 and [[https://orgmode.org/manual/Structure-of-Code-Blocks.html][16.2 Structure of Code Blocks]] 65 66 * Emacs Shortcut 67 - Toggle display of quick help buffer :: ~C-h C-q~ or ~M-x cheat-sheet~ 68 69 * Install 70 ** Opam 71 Making a search via *guix* to see if I can install it easily 72 #+name: search opam2 :results script 73 #+begin_src shell 74 guix search opam 75 #+end_src 76 77 #+RESULTS: search opam 78 | name: | opam | | | | | | | | | | | | | | 79 | version: | 2.1.3 | | | | | | | | | | | | | | 80 | outputs: | | | | | | | | | | | | | | | 81 | + | out: | everything | | | | | | | | | | | | | 82 | systems: | x86_64-linux | | | | | | | | | | | | | | 83 | dependencies: | bubblewrap@0.8.0 | curl@8.4.0 | dune@3.6.1 | git-minimal@2.33.1 | | | | | | | | | | | 84 | + | ncurses@6.2.20210619 | ocaml-cmdliner@1.1.1 | ocaml-cppo@1.6.9 | ocaml-dose3@5.0.1 | | | | | | | | | | | 85 | + | ocaml-mccs@1.1+14 | ocaml-opam-file-format@2.1.4 | ocaml-re@1.10.4 | ocaml@4.14.0 | | | | | | | | | | | 86 | + | openssl@3.0.8 | python-wrapper@3.10.7 | rsync@3.2.7 | unzip@6.0 | which@2.21 | | | | | | | | | | 87 | location: | gnu/packages/ocaml.scm:850:2 | | | | | | | | | | | | | | 88 | homepage: | https://opam.ocamlpro.com/ | | | | | | | | | | | | | | 89 | license: | LGPL | 3 | | | | | | | | | | | | | 90 | synopsis: | Package | manager | for | OCaml | | | | | | | | | | | 91 | description: | OPAM | is | a | tool | to | manage | OCaml | packages. | It | supports | multiple | | | | 92 | + | simultaneous | compiler | installations, | flexible | package | constraints, | and | a | | | | | | | 93 | + | Git-friendly | development | workflow. | | | | | | | | | | | | 94 | relevance: | 32 | | | | | | | | | | | | | | 95 | | | | | | | | | | | | | | | | 96 | name: | ocaml-opam-file-format | | | | | | | | | | | | | | 97 | version: | 2.1.4 | | | | | | | | | | | | | | 98 | outputs: | | | | | | | | | | | | | | | 99 | + | out: | everything | | | | | | | | | | | | | 100 | systems: | x86_64-linux | i686-linux | | | | | | | | | | | | | 101 | dependencies: | | | | | | | | | | | | | | | 102 | location: | gnu/packages/ocaml.scm:822:2 | | | | | | | | | | | | | | 103 | homepage: | https://opam.ocaml.org | | | | | | | | | | | | | | 104 | license: | LGPL | 2.1+ | | | | | | | | | | | | | 105 | synopsis: | Parser | and | printer | for | the | opam | file | syntax | | | | | | | 106 | description: | This | package | contains | a | parser | and | a | pretty-printer | for | the | opam | | | | 107 | + | file | format. | | | | | | | | | | | | | 108 | relevance: | 11 | | | | | | | | | | | | | | 109 | | | | | | | | | | | | | | | | 110 | name: | ocaml-opam-monorepo | | | | | | | | | | | | | | 111 | version: | 0.3.5 | | | | | | | | | | | | | | 112 | outputs: | | | | | | | | | | | | | | | 113 | + | out: | everything | | | | | | | | | | | | | 114 | systems: | x86_64-linux | | | | | | | | | | | | | | 115 | dependencies: | ocaml-odoc@2.2.0 | pkg-config@0.29.2 | | | | | | | | | | | | | 116 | location: | gnu/packages/ocaml.scm:987:2 | | | | | | | | | | | | | | 117 | homepage: | https://github.com/tarides/opam-monorepo | | | | | | | | | | | | | | 118 | license: | ISC | | | | | | | | | | | | | | 119 | synopsis: | Assemble | and | manage | fully | vendored | Dune | repositories | | | | | | | | 120 | description: | The | opam | monorepo | plugin | provides | a | convenient | interface | to | | | | | | 121 | + | bridge | the | opam | package | manager | with | having | a | local | copy | of | all | the | source | 122 | + | code | required | to | build | a | project | using | the | dune | build | tool. | | | | 123 | relevance: | 10 | | | | | | | | | | | | | | 124 | | | | | | | | | | | | | | | | 125 126 There is *opam 2.1.3*. 127 #+name: install opam 128 #+begin_src shell 129 guix install opam 130 #+end_src 131 132 Cheking if the installation is successful. 133 #+name: check opam install 134 #+begin_src shell 135 opam --version 136 #+end_src 137 138 #+RESULTS: check opam install 139 : 2.1.3 140 *** Initialisation 141 ~opam init~ will create the =.profile= and set up the =.bashrc= with 142 your environment. See https://opam.ocaml.org/doc/Usage.html#opam-init 143 144 #+name: init opam 145 #+begin_src shell 146 opam init --bare -a -y 147 #+end_src 148 149 #+RESULTS: init opam 150 | No | configuration | file | found, | using | built-in | defaults. | | | | | | | | | | | | 151 | Checking | for | available | remotes: | rsync | and | local, | git. | | | | | | | | | | | 152 | - | you | won't | be | able | to | use | mercurial | repositories | unless | you | install | the | hg | command | on | your | system. | 153 | - | you | won't | be | able | to | use | darcs | repositories | unless | you | install | the | darcs | command | on | your | system. | 154 | | | | | | | | | | | | | | | | | | | 155 | | | | | | | | | | | | | | | | | | | 156 | <><> | Fetching | repository | information | ><><><><><><><><><><><><><><><><><><><><><> | | | | | | | | | | | | | | 157 | [default] | Initialised | | | | | | | | | | | | | | | | | 158 | | | | | | | | | | | | | | | | | | | 159 | User | configuration: | | | | | | | | | | | | | | | | | 160 | Updating | ~/.profile. | | | | | | | | | | | | | | | | | 161 162 The advice is to make sure =.profile= and =.bashrc= knows each other. 163 ** Version of Ocaml 164 Called a switch, it is very much like using ~nvm~ for node projects 165 See https://opam.ocaml.org/doc/Usage.html#opam-switch 166 167 Nothing has been installed before 168 #+name: check previous switch 169 #+begin_src shell 170 opam switch list 171 #+end_src 172 173 #+RESULTS: check previous switch 174 : # switch compiler description 175 176 The list returned is empty. To see what is available in the remote 177 repo: 178 #+name: list switches 179 #+begin_src shell 180 opam switch list-available ocaml-base-compiler 181 #+end_src 182 183 #+RESULTS: list switches 184 #+begin_example 185 # Listing available compilers from repositories: default 186 # Name # Version # Synopsis 187 ocaml-base-compiler 4.10.2 Official release 4.10.2 188 ocaml-base-compiler 4.12.0 Official release 4.12.0 189 ocaml-base-compiler 4.12.1 Official release 4.12.1 190 ocaml-base-compiler 4.13.0 Official release 4.13.0 191 ocaml-base-compiler 4.13.1 Official release 4.13.1 192 ocaml-base-compiler 4.14.0 Official release 4.14.0 193 ocaml-base-compiler 4.14.1 Official release 4.14.1 194 ocaml-base-compiler 4.14.2 Official release 4.14.2 195 ocaml-base-compiler 5.0.0 Official release 5.0.0 196 ocaml-base-compiler 5.1.0 Official release 5.1.0 197 ocaml-base-compiler 5.1.1 Official release 5.1.1 198 ocaml-base-compiler 5.2.0 Official release 5.2.0 199 ocaml-base-compiler 5.2.1 Official release 5.2.1 200 ocaml-base-compiler 5.3.0 Official release of OCaml 5.3.0 201 #+end_example 202 203 We can go with the version *5.3.0* 204 #+name: create switch 205 #+begin_src shell 206 opam switch create ocaml-book ocaml-base-compiler.5.3.0 207 #+end_src 208 209 #+RESULTS: create switch 210 <><> Installing new switch packages <><><><><><><><><><><><><><><><><><><><> π« 211 Switch invariant: ["ocaml-base-compiler" {= "5.3.0"}] 212 213 <><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><> π« 214 β¬ retrieved ocaml-config.3 (cached) 215 β installed base-bigarray.base 216 β installed base-threads.base 217 β installed base-unix.base 218 β installed ocaml-options-vanilla.1 219 β¬ retrieved ocaml-compiler.5.3.0 (cached) 220 β installed ocaml-compiler.5.3.0 221 β installed ocaml-base-compiler.5.3.0 222 β installed ocaml-config.3 223 β installed ocaml.5.3.0 224 β installed base-domains.base 225 β installed base-effects.base 226 β installed base-nnp.base 227 Done. 228 : # Run eval $(opam env --switch=ocaml-book) to update the current shell environment 229 230 As detailed in the output 231 #+name:s post switch update 232 #+begin_src shell 233 eval $(opam env --switch=ocaml-book) 234 #+end_src 235 236 #+RESULTS: s post switch update 237 238 Checking the installation 239 #+name: list switch 240 #+begin_src shell 241 opam switch 242 #+end_src 243 244 #+RESULTS: list switch 245 : # switch compiler description 246 : cs3110-2025sp ocaml-base-compiler.5.2.0,ocaml-options-vanilla.1 ocaml-base-compiler = 5.2.0 247 : default ocaml-base-compiler.5.3.0,ocaml-options-vanilla.1 ocaml >= 4.05.0 248 : β ocaml-book ocaml-base-compiler.5.3.0,ocaml-options-vanilla.1 ocaml-base-compiler = 5.3.0 249 250 ** Packages 251 Install those: 252 ~opam install -y utop odoc ounit2 qcheck bisect_ppx menhir ocaml-lsp-server ocamlformat~ 253 254 And now calling ~utop~ from an *interactive shell* opens a REPL with 255 OCaml. 256 ** Emacs mode 257 #+name: install merlin mode 258 #+begin_src shell 259 opam install merlin user-setup 260 opam user-setup install 261 #+end_src 262 263 This will write in the wrong config file =~/.emacs=. 264 Move all the config in =/.config/emacs/init.el= 265 Install *Tuareg* mode and *dune* from melpa. 266 * Project Creation 267 with Dune Scaffolding 268 ** Create a new project 269 #+name: dune project 270 #+begin_src shell 271 dune init project my-project 272 cd my-project 273 dune exec bin/main.exe 274 #+end_src 275 276 This will print "Hello, World!" 277 **** Alternatively... 278 Based on the [[https://ocaml.org/docs/your-first-program][ocaml doc]] you can also 279 #+name: alt dune project 280 #+begin_src shell 281 dune init proj hello 282 cd hello 283 dune build 284 dune exec hello # for watch mode, dune exec hello -w 285 #+end_src 286 ** File structure 287 =lib/= and =bin/= contain source code files, for libraries and 288 programs respectively. =_build/= is generated and shouldn't been 289 manually modified. =test/= is where tests resides: 290 - bin :: executable programs 291 - lib :: libraries 292 - test :: tests 293 294 ** Formatting 295 Dune does not install the format file. 296 In the root project, same folder as =dune-project=, 297 create the file =.ocamlformat= 298 299 The format of the file π should be like the below: 300 #+begin_src txt 301 version=0.26.1 302 profile=default 303 #+end_src 304 305 Possible profiles are: 306 - default, or conventional 307 - ocamlformat 308 - janestreet 309 310 The ~version~ is the version of =ocamlformat=. You can get it via the 311 command ~ocamlformat --version~. 312 313 Based on the [[https://dune.readthedocs.io/en/stable/howto/formatting.html][doc]] Dune first makes a diff and then let's you apply a 314 promote to validate. First step is: 315 - ~dune build @fmt~ :: Format and then display the diff 316 - ~dune promote~ :: Accept the format and apply it 317 318 To apply the formatting in one go, type ~dune fmt~, this is the 319 equivalent of ~dune build @fmt --auto-promote~. Make sure to revert or 320 auto-revert the buffers. 321 322 See Emacs install and config [[https://ocaml.org/p/ocamlformat/latest/doc/editor_setup.html#emacs-setup][in the manual]]. 323 324 You can setup a =.ocamlformat-ignore= to list the files that shoult 325 not be formatted. 326 **** Auto format on save 327 Add this in your =.config/emacs/init.el= 328 #+name: init.el 329 #+begin_src elisp 330 (use-package ocamlformat 331 :custom (ocamlformat-enable 'enable-outside-detected-project) 332 :hook (before-save . ocamlformat-before-save) 333 ) 334 #+end_src 335 ** Watch mode 336 #+name: watch mode 337 #+begin_src shell 338 dune build --watch 339 # or dune exec proj-name -w 340 #+end_src 341 ** Start a new library 342 #+begin_src shell 343 dune init lib yo ./lib 344 #+end_src 345 ** Create a new exec in the current project 346 #+begin_src shell 347 dune init exec new_bin ./bin 348 #+end_src 349 ** Create a new test in the current project 350 #+begin_src shell 351 dune init test new_test ./test 352 #+end_src 353 * Documentation 354 See [[https://cs3110.github.io/textbook/chapters/basics/documentation.html][2.5. Documentation]] *ocamldoc* can generate documentation based on 355 the comments. A good comment will be crucial. The recommended style is 356 #+begin_src ocaml 357 (** [random_int bound] is a random integer between 0 (inclusive) 358 and [bound] (exclusive). 359 Requires: [bound] is greater than 0 and less than 2^30. 360 Raises: [Invalid_argument "bound"] otherwise. *) 361 let random_int bound = ... 362 #+end_src 363 364 #+begin_src ocaml 365 (** [index s c] is the index of the first occurrence of 366 character [c] in string [s]. 367 Raises: [Not_found] if [c] does not occur in [s]. *) 368 let index s c = ... 369 #+end_src 370 371 See [[https://ocaml.org/manual/5.2/ocamldoc.html][the manual]] 372 373 To create the documentation, in the root folder 374 #+begin_src shell 375 cd project 376 mkdir doc 377 ocamldoc -html bin/main.ml -d doc/ 378 #+end_src 379 380 Documentation using *dune*. install *odoc* with ~opam install odoc~. 381 In the root folder do ~dune build @doc~, this will generate the 382 documentation in =_build/default/_doc/_html/index.html= 383 384 β οΈ Only the public facing libs will be documented. This doc is 385 generated on the fly in the =_build= folder. It will not be saved in 386 the repo.It's fine, it can be generated in the cicd. 387 388 * Modules and their Interfaces 389 390 ** Module 391 Each OCaml files defines a module. To create a module, you create a 392 file. Based on the default =hello= template, let's create a new file: 393 394 #+name: en.ml 395 #+description: very short library module 396 #+begin_src ocaml :tangle lib/en.ml 397 let v = "Hello, World." 398 #+end_src 399 400 This creates a module *En* inside the module *Hello*. To access the 401 string ~v~, you call it via ~Hello.En.v~. The ~main.ml~ file becomes 402 403 #+name: main with module 404 #+description: how to use variables define in a different module 405 #+begin_src ocaml :tangle bin/main.ml 406 let () = Printf.printf "%s\n" Hello.En.v 407 #+end_src 408 409 ~dune exec hello~ will display the string as defined in the module. 410 411 ** Interface 412 An interface file list the elements of the module available to the 413 outside world. 414 #+name: en.mli 415 #+description: interface for the en.ml module 416 #+begin_src ocaml :tangle lib/en.mli 417 val v : string 418 #+end_src 419 420 No matter what is being created in the module, only ~v~ is callable. 421 #+name: en.mli w/ hello 422 #+description: module en with a private variable 423 #+begin_src ocaml :tangle lib/en.ml 424 let hi = "Hello" 425 let v = hi ^ ", World." 426 #+end_src 427 Here ~hi~ is not visible in =main.ml=. 428 429 #+begin_src ocaml :tangle bin/main.ml 430 let () = Printf.printf "%s\n" Hello.En.hi 431 #+end_src 432 433 Running will cause a compilation error 434 #+begin_src bash 435 File "bin/main.ml", line 1, characters 30-41: 436 1 | let () = Printf.printf "%s\n" Hello.En.hi 437 ^^^^^^^^^^^ 438 Error: Unbound value "Hello.En.hi" 439 #+end_src 440 441 ** Multiple modules in one library 442 Let's add the French version in its own module. 443 #+name: fr.ml 444 #+description: module fr 445 #+begin_src ocaml :tangle lib/fr.ml 446 let v = "Bonjour tout le monde" 447 #+end_src 448 449 #+name: main.ml with 2 modules 450 #+description: main is calling 2 modules 451 #+begin_src ocaml :tangle bin/main.ml 452 let () = Printf.printf "%s\n" Hello.En.v 453 let () = Printf.printf "%s\n" Hello.Fr.v 454 #+end_src 455 456 To run it, first build and then execute 457 #+begin_src shell 458 dune build 459 dune exec hello 460 #+end_src 461 462 * Ways of Working 463 ** Debugging 464 There are 3 possibilities to deal with wrong arguments being passed. 465 #+name: wrong arguments 466 #+begin_src ocaml 467 (** [random_int bound] is a random integer between 0 (inclusive) 468 and [bound] (exclusive). 469 Requires: [bound] is greater than 0 and less than 2^30. *) 470 let random_int_1 bound = 471 assert (bound > 0 && bound < 1 lsl 30); 472 1 473 474 let random_int_2 bound = 475 if not (bound > 0 && bound < 1 lsl 30) 476 then invalid_arg "bound"; 477 2 478 479 let random_int_3 bound = 480 if not (bound > 0 && bound < 1 lsl 30) 481 then failwith "bound"; 482 3 483 #+end_src 484 *** random_int_1 485 The ~assert~ is probably most useful when you're trying to debug your own code, 486 rather than choosing to expose a failed assertion to a client. 487 *** random_int_2 488 This is probably the most informative to the client, because it uses the 489 built-in function ~invalid_arg~ to raise the exception *Invalid_argument*. 490 In fact, that's exactly what the standard library implementation of this 491 function does. 492 The function definition becomes 493 #+name: random_int definition 494 #+begin_src ocaml 495 (** [random_int bound] is a random integer between 0 (inclusive) 496 and [bound] (exclusive). 497 @raise [Invalid_argument "bound"] unless [bound] is greater than 0 498 and less than 2^30. *) 499 let random_int bound = 500 if not (bound > 0 && bound < 1 lsl 30) 501 then invalid_arg "bound"; 502 1 503 #+end_src 504 505 Instead of being free to do whatever when ~bound~ is out of range, ~random_int~ 506 *must* raise an exception. 507 *** random_int_3 508 Raises the exception *Failure*. It might be useful is situation where the 509 precondition involves more than just a single invalid argument. 510 ** Failures 511 There are several predefined *exceptions* in the StdLib: 512 - Exit :: can be used to terminate an iteration, like a break 513 statement 514 - Not_found :: should be raised when searching failed because there 515 isn't anything satisfactory to be found 516 - Invalid_argument :: should be raised when a parameter can't be 517 accepted 518 - Failure :: should be raised when a result can't be produced 519 520 *** Catching Failures 521 with ~try with~: 522 523 #+begin_src ocaml 524 try random_int (-1) with Invalid_argument _ -> do_something_else 525 #+end_src 526 527 *** Keeping control of the workflow 528 Because handling exceptions interrupts normal control flow, using them 529 can complicate the tasks that requires a strict order. ~Fun.protect~ 530 ensure some actions are *always* executed. 531 532 #+begin_src ocaml 533 let safe_foo arg = 534 let finally () = always_bar in 535 let work () = try baz_might_fail arg with WhateverError _ -> failed_foo in 536 Fun.protect ~finally work 537 #+end_src 538 539 *** Handling failure directly in the data 540 https://ocaml.org/docs/error-handling#using-the-option-type-for-errors