My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix cross-lecture dependencies in foundations notebooks 5, 7, 8

Add hidden preamble cells to define prerequisites that were previously
only available when lectures were read in order (take/drop in lecture 5,
tree type in lecture 7, prefix/dub/promote in lecture 8). Remove the
knownUnbound workaround from E2E tests now that all cells pass cleanly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+65 -57
+18 -8
site/notebooks/foundations/foundations5.mld
··· 1 1 {0 Lecture 5: Sorting} 2 2 3 + {@ocaml hidden[ 4 + let rec take i = function 5 + | [] -> [] 6 + | x :: xs -> if i > 0 then x :: take (i - 1) xs else [] 7 + 8 + let rec drop i = function 9 + | [] -> [] 10 + | x :: xs -> if i > 0 then drop (i - 1) xs else x :: xs 11 + ]} 12 + 3 13 A few applications for sorting and arranging items into order are: 4 14 {ul {- search 5 15 }{- merging ··· 56 66 statistically good random numbers is hard. Much effort has gone into those few 57 67 lines of code. 58 68 59 - {@ocaml[ 69 + {@ocaml x[ 60 70 let nextrandom seed = 61 71 let a = 16807.0 in 62 72 let m = 2147483647.0 in ··· 69 79 70 80 We can now bind the identifier [rs] to a list of 10,000 random numbers. 71 81 72 - {@ocaml[ 82 + {@ocaml x[ 73 83 let seed, rs = randlist (1.0, []) 10000 74 84 ]} 75 85 ··· 77 87 78 88 An insert operation does {m n/2} comparisons on average. 79 89 80 - {@ocaml[ 90 + {@ocaml x[ 81 91 let rec ins x = function 82 92 | [] -> [x] 83 93 | y::ys -> if x <= y then x :: y :: ys ··· 86 96 87 97 {e Insertion sort} takes {m O(n^2)} comparisons on average: 88 98 89 - {@ocaml[ 99 + {@ocaml x[ 90 100 let rec insort = function 91 101 | [] -> [] 92 102 | x::xs -> ins x (insort xs) ··· 131 141 132 142 {1 Quicksort: The Code} 133 143 134 - {@ocaml[ 144 + {@ocaml x[ 135 145 let rec quick = function 136 146 | [] -> [] 137 147 | [x] -> [x] ··· 175 185 176 186 {1 Append-Free Quicksort} 177 187 178 - {@ocaml[ 188 + {@ocaml x[ 179 189 let rec quik = function 180 190 | ([], sorted) -> sorted 181 191 | ([x], sorted) -> x::sorted ··· 210 220 211 221 Merge joins two sorted lists. 212 222 213 - {@ocaml[ 223 + {@ocaml x[ 214 224 let rec merge = function 215 225 | [], ys -> ys 216 226 | xs, [] -> xs ··· 241 251 242 252 {1 Top-down Merge sort} 243 253 244 - {@ocaml[ 254 + {@ocaml x[ 245 255 let rec tmergesort = function 246 256 | [] -> [] 247 257 | [x] -> [x]
+11 -7
site/notebooks/foundations/foundations7.mld
··· 1 1 {0 Lecture 7: Dictionaries and Functional Arrays} 2 2 3 + {@ocaml hidden[ 4 + type 'a tree = Lf | Br of 'a * 'a tree * 'a tree 5 + ]} 6 + 3 7 {1 Dictionaries} 4 8 {ul {- lookup: find an item in the dictionary 5 9 }{- update (insert): replace (store) an item in the dictionary ··· 30 34 only usable if there are few keys in use. However, they are general in that the 31 35 keys do not need a concept of ordering, only equality. 32 36 33 - {@ocaml[ 37 + {@ocaml x[ 34 38 exception Missing;; 35 39 let rec lookup a = function 36 40 | [] -> raise Missing ··· 74 78 75 79 {1 Lookup: Seeks Left or Right} 76 80 77 - {@ocaml[ 81 + {@ocaml x[ 78 82 exception Missing of string;; 79 83 let rec lookup b = function 80 84 | Br ((a, x), t1, t2) -> ··· 101 105 102 106 {1 Update} 103 107 104 - {@ocaml[ 108 + {@ocaml x[ 105 109 let rec update k v = function 106 110 | Lf -> Br ((k, v), Lf, Lf) 107 111 | Br ((a, x), t1, t2) -> ··· 139 143 140 144 {1 Aside: Traversing Trees (3 Methods)} 141 145 142 - {@ocaml[ 146 + {@ocaml x[ 143 147 let rec preorder = function 144 148 | Lf -> [] 145 149 | Br (v, t1, t2) -> ··· 181 185 each function constructs its result list and compare with how appends were 182 186 eliminated from [quicksort] in the Sorting lecture. 183 187 184 - {@ocaml[ 188 + {@ocaml x[ 185 189 let rec preord = function 186 190 | Lf, vs -> vs 187 191 | Br (v, t1, t2), vs -> ··· 267 271 268 272 {1 The Lookup Function} 269 273 270 - {@ocaml[ 274 + {@ocaml x[ 271 275 exception Subscript;; 272 276 let rec sub = function 273 277 | Lf, _ -> raise Subscript (* Not found *) ··· 318 322 319 323 {1 The Update Function} 320 324 321 - {@ocaml[ 325 + {@ocaml x[ 322 326 let rec update = function 323 327 | Lf, k, w -> 324 328 if k = 1 then
+30 -24
site/notebooks/foundations/foundations8.mld
··· 1 1 {0 Lecture 8: Functions as Values} 2 2 3 + {@ocaml hidden[ 4 + let prefix a b = a ^ b 5 + let dub = prefix "Dr " 6 + let promote = prefix "Professor " 7 + ]} 8 + 3 9 In OCaml, functions can be 4 10 {ul {- passed as arguments to other functions, 5 11 }{- returned as results, ··· 7 13 }{- but {e not} tested for equality. 8 14 }} 9 15 10 - {@ocaml[ 16 + {@ocaml x[ 11 17 [(fun n -> n * 2); 12 18 (fun n -> n * 3); 13 19 (fun n -> n + 1)] ··· 45 51 {m \tt fun\;x\;\rightarrow E} is the function {m f} such that {m f(x)=E}. 46 52 The function [fun n -> n*2] is a {e doubling function}. 47 53 48 - {@ocaml[ 54 + {@ocaml x[ 49 55 fun n -> n * 2 50 56 ]} 51 57 ··· 54 60 The expression [fun n -> n*2] has the same value as the identifier 55 61 [double], declared as follows: 56 62 57 - {@ocaml[ 63 + {@ocaml x[ 58 64 let double n = n * 2 59 65 ]} 60 66 ··· 62 68 adds an anonymous variable name to pattern match against. The following functions 63 69 are all equivalent, with the latter definitions bound to the [is_zero] value and the earlier ones anonymous: 64 70 65 - {@ocaml[ 71 + {@ocaml x[ 66 72 fun x -> match x with 0 -> true | _ -> false 67 73 ]} 68 74 ··· 71 77 A {e curried function} returns another function as its result. We use 72 78 the string concetenation operator [(^)] to illustrate how this works. 73 79 74 - {@ocaml[ 80 + {@ocaml x[ 75 81 (^) 76 82 ]} 77 83 ··· 79 85 arguments to the function definition. The following two definitions 80 86 are equivalent in OCaml: 81 87 82 - {@ocaml[ 88 + {@ocaml x[ 83 89 let prefix = fun a -> fun b -> a ^ b 84 90 ]} 85 91 ··· 91 97 92 98 Currying is an alternative, where we {e nest} the [fun]-notation: 93 99 94 - {@ocaml[ 100 + {@ocaml x[ 95 101 fun k -> fun n -> n * 2 + k 96 102 ]} 97 103 98 104 Applying this curried function to the argument 1 yields another function, in which [k] has been replaced by 1: 99 105 100 - {@ocaml[ 106 + {@ocaml x[ 101 107 let fn = fun k -> fun n -> n * 2 + k 102 108 ]} 103 109 ··· 114 120 115 121 This curried function syntax is nicer than nested [fun] binders: 116 122 117 - {@ocaml[ 123 + {@ocaml x[ 118 124 let prefix a b = a ^ b 119 125 ]} 120 126 ··· 144 150 curried, it may be used with the curried application syntax in some 145 151 expressions: 146 152 147 - {@ocaml[ 153 + {@ocaml x[ 148 154 List.hd [dub; promote] "Hamilton" 149 155 ]} 150 156 ··· 155 161 156 162 {1 Partial Application: A Curried Insertion Sort} 157 163 158 - {@ocaml[ 164 + {@ocaml x[ 159 165 let insort lessequal = 160 166 let rec ins x = function 161 167 | [] -> [x] ··· 180 186 181 187 Some examples of its use: 182 188 183 - {@ocaml[ 189 + {@ocaml x[ 184 190 insort (<=) [5; 3; 9; 8] 185 191 ]} 186 192 ··· 191 197 192 198 {1 map: the “Apply to All” Function} 193 199 194 - {@ocaml[ 200 + {@ocaml x[ 195 201 let rec map f = function 196 202 | [] -> [] 197 203 | x::xs -> (f x) :: map f xs ··· 204 210 [map]. If we did not have them, the first use of [map] in the above code block 205 211 would require a preliminary function declaration: 206 212 207 - {@ocaml[ 213 + {@ocaml x[ 208 214 let rec sillylist = function 209 215 | [] -> [] 210 216 | s::ss -> (s ^ "ppy") :: sillylist ss ··· 234 240 \end{pmatrix} 235 241 } 236 242 237 - {@ocaml[ 243 + {@ocaml x[ 238 244 let rec transp = function 239 245 | []::_ -> [] 240 246 | rows -> (map List.hd rows) :: ··· 339 345 340 346 {e Dot product} of two vectors---a {e curried function} 341 347 342 - {@ocaml[ 348 + {@ocaml x[ 343 349 let rec dotprod xs ys = 344 350 match xs, ys with 345 351 | [], [] -> 0.0 ··· 348 354 349 355 {e Matrix product} 350 356 351 - {@ocaml[ 357 + {@ocaml x[ 352 358 let rec matprod arows brows = 353 359 let cols = transp brows in 354 360 map (fun row -> map (dotprod row) cols) arows ··· 373 379 374 380 {1 List Functionals for Predicates} 375 381 376 - {@ocaml[ 382 + {@ocaml x[ 377 383 let rec exists p = function 378 384 | [] -> false 379 385 | x::xs -> (p x) || (exists p xs) ··· 389 395 Dually, we have a functional to test whether all list elements satisfy the 390 396 predicate. If it finds a counterexample then it, too, stops searching. 391 397 392 - {@ocaml[ 398 + {@ocaml x[ 393 399 let rec all p = function 394 400 | [] -> true 395 401 | x::xs -> (p x) && all p xs ··· 402 408 403 409 {1 Applications of the Predicate Functionals} 404 410 405 - {@ocaml[ 411 + {@ocaml x[ 406 412 let member y xs = 407 413 exists (fun x -> x=y) xs 408 414 ]} 409 415 410 416 {e Testing whether two lists have no common elements} 411 417 412 - {@ocaml[ 418 + {@ocaml x[ 413 419 let disjoint xs ys = 414 420 all (fun x -> all (fun y -> x<>y) ys) xs 415 421 ]} ··· 446 452 447 453 What does the following function do, and what are its uses? 448 454 449 - {@ocaml[ 455 + {@ocaml x[ 450 456 let sw f x y = f y x 451 457 ]} 452 458 ··· 470 476 (It is typically used as an alternative to exceptions.) Declare an analogue of the function [map] 471 477 for type ['a option]. 472 478 473 - {@ocaml[ 479 + {@ocaml x[ 474 480 type 'a option = None | Some of 'a 475 481 ]} 476 482 ··· 478 484 479 485 Recall the making change function of Lecture 4: 480 486 481 - {@ocaml[ 487 + {@ocaml x[ 482 488 let rec change till amt = 483 489 match till, amt with 484 490 | _ , 0 -> [ [] ]
+6 -18
test/e2e/foundations-notebooks.spec.js
··· 13 13 { file: 'foundations2.html', name: 'Lecture 2: Recursion' }, 14 14 { file: 'foundations3.html', name: 'Lecture 3: Lists' }, 15 15 { file: 'foundations4.html', name: 'Lecture 4: More on Lists' }, 16 - // foundations5 uses take/drop from earlier lectures without defining them 17 - { file: 'foundations5.html', name: 'Lecture 5: Sorting', knownUnbound: true }, 16 + { file: 'foundations5.html', name: 'Lecture 5: Sorting' }, 18 17 { file: 'foundations6.html', name: 'Lecture 6: Datatypes and Trees' }, 19 - // foundations7 uses tree type (Lf/Br) from foundations6 20 - { file: 'foundations7.html', name: 'Lecture 7: Dictionaries', knownUnbound: true }, 21 - // foundations8 uses dub from earlier context 22 - { file: 'foundations8.html', name: 'Lecture 8: Functions as Values', knownUnbound: true }, 18 + { file: 'foundations7.html', name: 'Lecture 7: Dictionaries' }, 19 + { file: 'foundations8.html', name: 'Lecture 8: Functions as Values' }, 23 20 { file: 'foundations9.html', name: 'Lecture 9: Sequences' }, 24 21 { file: 'foundations10.html', name: 'Lecture 10: Queues' }, 25 22 { file: 'foundations11.html', name: 'Lecture 11: Procedural Programming' }, ··· 107 104 await runAllCellsSequentially(page, { cellTimeout: 30_000 }); 108 105 109 106 const outputs = await getCellOutputs(page); 110 - const errors = findCellErrors(outputs).filter((e) => { 111 - if (e.mode === 'exercise' || e.mode === 'test') return false; 112 - // Skip Unbound errors in notebooks with known cross-lecture dependencies 113 - if ( 114 - nb.knownUnbound && 115 - (e.stderr.includes('Unbound value') || 116 - e.stderr.includes('Unbound constructor') || 117 - e.stderr.includes('Unbound type')) 118 - ) 119 - return false; 120 - return true; 121 - }); 107 + const errors = findCellErrors(outputs).filter( 108 + (e) => e.mode !== 'exercise' && e.mode !== 'test' 109 + ); 122 110 123 111 if (errors.length > 0) { 124 112 const details = errors