diff --git a/AGENTS.md b/AGENTS.md index ff33d59..c7641e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,4 +2,4 @@ If you are an AI assistant helping a student with this course, read [MENTOR.md](MENTOR.md) and follow it: act as a Socratic mentor, and do not write solution code for an exercise unless the student explicitly gives up on it. -Course maintenance (editing the text in `course/`, the stubs, solutions, tests, fixtures or scripts) is different work: MENTOR.md does not apply. See "For maintainers" in [README.md](README.md#for-maintainers). Check your changes with `scripts/verify.sh `, `scripts/check-stubs.sh `, `scripts/check-progression.sh `, `node scripts/smoke.mjs ` and `node scripts/check-links.mjs`. +Course maintenance (editing the text in `course/`, the stubs, solutions, tests, fixtures or scripts) is different work: MENTOR.md does not apply. See "For maintainers" in [README.md](README.md#for-maintainers). The course assumes general programming experience, not prior Scala or functional programming: explain FP ideas from scratch, and connect them to everyday programming in blockquotes labelled `> **From everyday programming:**`. Check your changes with `scripts/verify.sh `, `scripts/check-stubs.sh `, `scripts/check-progression.sh `, `node scripts/smoke.mjs ` and `node scripts/check-links.mjs`. diff --git a/CLAUDE.md b/CLAUDE.md index ff33d59..c7641e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,4 +2,4 @@ If you are an AI assistant helping a student with this course, read [MENTOR.md](MENTOR.md) and follow it: act as a Socratic mentor, and do not write solution code for an exercise unless the student explicitly gives up on it. -Course maintenance (editing the text in `course/`, the stubs, solutions, tests, fixtures or scripts) is different work: MENTOR.md does not apply. See "For maintainers" in [README.md](README.md#for-maintainers). Check your changes with `scripts/verify.sh `, `scripts/check-stubs.sh `, `scripts/check-progression.sh `, `node scripts/smoke.mjs ` and `node scripts/check-links.mjs`. +Course maintenance (editing the text in `course/`, the stubs, solutions, tests, fixtures or scripts) is different work: MENTOR.md does not apply. See "For maintainers" in [README.md](README.md#for-maintainers). The course assumes general programming experience, not prior Scala or functional programming: explain FP ideas from scratch, and connect them to everyday programming in blockquotes labelled `> **From everyday programming:**`. Check your changes with `scripts/verify.sh `, `scripts/check-stubs.sh `, `scripts/check-progression.sh `, `node scripts/smoke.mjs ` and `node scripts/check-links.mjs`. diff --git a/MENTOR.md b/MENTOR.md index 83cc6d1..1e9411e 100644 --- a/MENTOR.md +++ b/MENTOR.md @@ -2,7 +2,7 @@ You are helping a student work through **Pure Snake**, a self-paced functional programming course (see [README.md](README.md)). The student is learning by doing. Your job is to make them think, not to do the work for them. A mentor who hands over answers feels helpful and teaches very little; the course explicitly counts "AI mentor mode that dumps solutions" as a failure. -The student has taken one Scala course: they know `map`, `filter`, `fold`, case classes and pattern matching. They work alone, at home, in one of three tracks: **JavaScript** (`js/`), **Scala 3** (`scala/`) or **F#** (`fsharp/`). The course text is in `course/` (modules `00-setup.md` to `11-capstone-pong.md`, plus `method.md` and `reference.md`). +The student has general programming experience (variables, loops, functions, classes and objects, collections, callbacks, unit tests) but no assumed background in functional programming or in any particular language. Module 1 introduces `map`, `filter`, `reduce`, immutability and functions as values; Module 2 introduces sum types, `Option` and pattern matching. Do not assume they know these before then. They work alone, at home, in one of three tracks: **JavaScript** (`js/`), **Scala 3** (`scala/`) or **F#** (`fsharp/`). The course text is in `course/` (modules `00-setup.md` to `11-capstone-pong.md`, plus `method.md` and `reference.md`). ## Starting a session @@ -35,7 +35,7 @@ Then read the module's file in `course/` and the stub in `/src/` (its doc 8. **Encourage running the tests, often.** One module: JS `cd js && node --test "test/m03-*.test.js"`, Scala `cd scala && ./test.sh M03`, F# `cd fsharp && ./test.sh M03`. Everything (including the purity audit): `./test.sh` in the track folder. Ask the student to run them and tell you the result; do not claim what the tests would say without running them or seeing the output. -9. **Adapt to the track.** Talk about the student's language. In Scala, connect to what they already know (`copy`, `foldLeft`, `Option`, `for`-comprehensions). In JavaScript, watch for mutation that JS allows silently (`sort`, `push`, spreading only one level). In F#, watch for argument order (data last), PascalCase record fields, and the `uint64` helpers in Module 4's random generator (needed for the browser build). The modules have "From Scala" bridges you can point to. +9. **Adapt to the track.** Talk about the student's language. In Scala, help them with `copy`, `foldLeft`, `Option` and `for`-comprehensions as the modules introduce them; do not assume they met these elsewhere. In JavaScript, watch for mutation that JS allows silently (`sort`, `push`, spreading only one level). In F#, watch for argument order (data last), PascalCase record fields, and the `uint64` helpers in Module 4's random generator (needed for the browser build). In every track, connect new ideas to programming the student already knows (loops, objects with setters, callbacks, the game loop, unit tests and mocks, SQL, Git); the modules have "From everyday programming" bridges you can point to. ## Checkpoints diff --git a/README.md b/README.md index 01ab28a..9745bec 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ You can follow the course in **JavaScript** (the primary track), **Scala 3** or ## Who it is for -Bachelor's and master's students in computer science who have taken **one course in Scala** (or a similar functional language): you know `map`, `filter` and `fold`, case classes and pattern matching, maybe `Option`. You have probably never built an *interactive* program functionally. Strong JavaScript is not required; Module 1 bridges from Scala. +Bachelor's and master's students in computer science with **general programming experience**: you can work with variables, loops, functions, classes and objects, collections, callbacks or event handlers, and you have written some unit tests. **No prior functional programming is assumed**, and neither is any particular language: Module 1 introduces `map`, `filter`, `reduce`, immutable data and functions as values from scratch, and the text connects each new idea to programming you already know. Strong JavaScript is not required. The course is designed for working **alone at home**, either solo or with an AI assistant as a Socratic mentor (see [How to study](#how-to-study)). diff --git a/course/00-setup.md b/course/00-setup.md index 5ec75dd..e1c0702 100644 --- a/course/00-setup.md +++ b/course/00-setup.md @@ -1,7 +1,7 @@ # Module 0 — Setup and how to use this course > **Time:** ~1 h · **You'll edit:** nothing yet · **Tests:** JS `cd js && node --test "test/m00-*.test.js"` · Scala `cd scala && ./test.sh M00` · F# `cd fsharp && ./test.sh M00` -> **Prerequisites:** one course in Scala (or similar): you know `map`, `filter`, `fold`, case classes and pattern matching +> **Prerequisites:** general programming experience: variables, loops, functions, classes and objects, collections, callbacks or event handlers, and some unit tests. No prior functional programming is assumed. ## Why this module @@ -81,7 +81,7 @@ Here is the whole program you are going to build. Keep coming back to this pictu Read it from the top. The **pure core** is plain data and plain functions: given a state and an action, `update` computes the next state; given a state, `view` computes a list of shapes. Below that, **effects as values**: an `IO` is a *description* of something to do (like a recipe), and a `Stream` is a *description* of how to react to inputs over time. Building these descriptions changes nothing in the world. Finally, at **the edge**, a small runtime takes the one value `main` and actually runs it. -> **From Scala:** if you have heard of cats-effect or ZIO, this is the same idea in miniature. You will build `IO` and `Stream` yourself, from scratch, in about a hundred lines each, so there is nothing magic left. +> **From everyday programming:** you know the classic game loop, `while (running) { input(); update(); render(); }`, where each of the three calls reads and changes shared variables. This course keeps the same three parts but separates them: `update` and `render` become pure functions that return a new state and a picture, and only the loop itself, at the edge, touches the keyboard, the clock and the screen. You will build `IO` and `Stream` yourself, from scratch, in about a hundred lines each, so there is nothing magic left. This design is sometimes called *functional core, imperative shell* (Gary Bernhardt). We push it further than usual: even the shell is mostly pure, and the truly imperative part is one function. @@ -92,14 +92,14 @@ The course text is shared. Every code listing that you would write appears three | Track | Why pick it | Tests with | Runs in the browser via | |---|---|---|---| | **JavaScript** (primary) | No compiler, no types: you must hold the discipline yourself, which teaches the most. Zero dependencies. | Node's built-in `node --test` | plain ES modules | -| **Scala 3** | Closest to what you already know. The compiler helps you (exhaustive matches, immutable by default). | munit via `scala-cli` | Scala.js | +| **Scala 3** | A typed language that mixes objects and functions; familiar if you know Java or Kotlin. The compiler helps you (exhaustive matches, immutable collections by default). | munit via `scala-cli` | Scala.js | | **F#** | An ML-family language: lighter syntax, data-last functions and pipelines. A good second functional language. | Expecto via `dotnet` | Fable | -If you are unsure, take JavaScript. It is the primary track, and Module 1 bridges from Scala to JavaScript. The three tracks use the same names where they can. Where they differ, the text tells you the first time it matters, and `course/reference.md` has the full table. The most common differences: +If you are unsure, take JavaScript. It is the primary track, and Module 1 introduces the functional tools (`map`, `filter`, `reduce`, immutable updates, functions as values) in JavaScript first, with the Scala and F# spellings next to them. The three tracks use the same names where they can. Where they differ, the text tells you the first time it matters, and `course/reference.md` has the full table. The most common differences: - JS functions take the data first: `map(io, f)`. Scala uses methods: `io.map(f)`. F# takes the data last so that pipelines work: `io |> IO.map f`. - `flatMap` is called `bind` in F#. -- "No apple" is `null` in JS and `None` (an `Option`) in Scala and F#. +- "No apple" is `null` in JS and `None` (an `Option`, a value that is either `Some(x)` or `None`; Module 2 explains it) in Scala and F#. - Random seeds are `number` in JS, `Long` in Scala, `uint32` in F#. ### Installing the tools @@ -238,7 +238,7 @@ A green test does not prove your code is right, and a red test never means you a > - `Math.random`, `Date.now`, `System.Random`, `DateTime`: hidden inputs. The same call gives a different answer each time. > - `document`, `window`, `addEventListener`, `println`, `printf`: talking to the outside world. > -> Without `let` or `var` you cannot write a classic `for (let i = 0; ...)` loop. That is deliberate: you will use `map`, `filter`, `reduce`, recursion and small helper functions instead, which is what you know from Scala. +> Without `let` or `var` you cannot write a classic `for (let i = 0; ...)` loop. That is deliberate: you will use `map`, `filter`, `reduce`, recursion and small helper functions instead. If you have not used them before, Module 1 introduces each of them with small examples. > > The audit is a blunt text scan. It can be fooled, and it sometimes complains about harmless code (for instance a string containing `--`). It is a guard rail, not a proof. The real rule is the one behind it: **outside the edge, a function's result depends only on its arguments, and calling it changes nothing.** @@ -269,7 +269,7 @@ Every module has the same parts, in this order: 1. **Why this module**: the problem, usually what goes wrong in the stateful version. 2. **Learning objectives**: what you should be able to do afterwards. -3. **Concepts**: the ideas, with Scala bridges (blockquotes starting with **From Scala:**) and code in all three languages. +3. **Concepts**: the ideas, with bridges from programming you already know (blockquotes starting with **From everyday programming:**) and code in all three languages. 4. **Exercises**: the functions to write, with signatures in three languages, the tests and fixtures that check them, and pitfalls. 5. **Hints**: folded away; from Module 1 on, every coding exercise has a nudge, a stronger hint and a near-solution. Open them one tier at a time. 6. **Check your understanding**: questions you should be able to answer before moving on. Several ask you to predict an output or to say what would break if something changed. diff --git a/course/01-purity-and-immutability.md b/course/01-purity-and-immutability.md index 1e6199f..6681e9c 100644 --- a/course/01-purity-and-immutability.md +++ b/course/01-purity-and-immutability.md @@ -23,7 +23,7 @@ function step() { `step()` takes no arguments and returns nothing. Everything it does is a change somewhere else. To know what it does you need to know the current value of `snake`, `apple`, `score`, what `randomFreeCell()` will return this time, and who else is holding a reference to `snake.body`. To test it you have to set up globals, call it, and inspect the globals afterwards. To replay a game you would have to record every random number. -The fix is not a library. It is two habits you already practised in Scala: **write functions whose result depends only on their arguments**, and **never change data; build new data instead**. This module makes those habits precise, shows how they look in JavaScript (which does not enforce them) and in F#, and gives you a few small tools you will use in every later module. +The fix is not a library. It is two habits: **write functions whose result depends only on their arguments**, and **never change data; build new data instead**. This module makes those habits precise, shows how they look in JavaScript (which does not enforce them), Scala and F#, introduces the tools that replace loops (`map`, `filter`, `reduce`) and gives you a few small helpers you will use in every later module. ## Learning objectives @@ -32,6 +32,7 @@ After this module you can: - **explain** referential transparency and decide whether a given expression has it; - **distinguish** a constant binding (`const`, `val`, `let`) from immutable data, and shallow from deep freezing; - **update** records and arrays without mutation in all three languages (spread, `copy`, `with`); +- **replace** a loop with `map`, `filter` and `reduce` (a fold), and pass functions as arguments; - **implement** small list helpers (`range`, `dropLast`, `contains`) without loops or reassignment; - **compose** functions (JS `pipe`/`compose`, Scala `andThen`/`compose`, F# `|>`/`>>`) and predict the order in which they apply. @@ -62,9 +63,11 @@ const y = next() + next() // 1 + 2 = 3 1. **Same arguments, same result.** The result depends on nothing except the arguments (no globals, no clock, no randomness, no files). 2. **No observable effects.** Calling it changes nothing the rest of the program can see (no mutation of its arguments or globals, no printing, no drawing). -Why care? Because a pure function can be understood by looking at it alone. You can test it by calling it with inputs and checking the output. You can call it twice, or zero times, or cache the result, and nothing breaks. This is the *substitution model* of evaluation you may know from your Scala course: you evaluate a program by replacing names with their definitions, like algebra. It only works when everything is referentially transparent. +Why care? Because a pure function can be understood by looking at it alone. You can test it by calling it with inputs and checking the output. You can call it twice, or zero times, or cache the result, and nothing breaks. This is the *substitution model* of evaluation: you evaluate a program by replacing names with their definitions, like algebra (`add(2, 3) + add(2, 3)` becomes `5 + 5` becomes `10`). It only works when everything is referentially transparent. -> **From Scala:** in Scala you mostly worked with `val` and immutable `List`, so most of your functions were pure without you having to think about it. The compiler does not check purity in Scala, JavaScript or F#. In all three it is a discipline. In this course the tests and the purity audit help you keep it. +> **From everyday programming:** think of a unit test for a method that reads the clock, a global singleton or a database. Before you can test it you need mocks, fakes or dependency injection to take control of those hidden inputs. A pure function has no hidden inputs: everything it depends on is an argument, so the test passes the inputs and checks the output, nothing more. A spreadsheet is another familiar example: a cell such as `=A1 * 2` is a pure function of other cells, which is why the spreadsheet can recompute any cell at any time and always get the same answer. + +No compiler checks purity for you, in JavaScript, Scala or F#. In all three it is a discipline. In this course the tests and the purity audit help you keep it. ### Exercise in prose: pure or not? @@ -114,7 +117,9 @@ So there are two different things: | Scala `val` + `List`, case class | no | no | | F# `let` + list, record | no | no | -> **From Scala:** Scala separates these too. `val buf = ListBuffer(1)` cannot be reassigned, but `buf += 2` still mutates it. Immutability comes from the *data type* (`List`, case classes), not from `val`. F# is like Scala here, only stricter: `let` bindings cannot be reassigned at all unless declared `let mutable`, and lists and records are immutable. +> **From everyday programming:** Java's `final` and C#'s `readonly` behave like `const`. `final List names = new ArrayList<>()` cannot be made to point at another list, but `names.add("Ada")` still changes the list it points at. Immutability comes from the *data type*, not from the keyword on the name. + +The same holds in the other tracks. In Scala, `val buf = ListBuffer(1)` cannot be reassigned, but `buf += 2` still mutates it; the immutable `List` and case classes are what make the data unchangeable. F# is stricter: `let` bindings cannot be reassigned at all unless declared `let mutable`, and lists and records are immutable. ### Freezing in JavaScript @@ -148,7 +153,9 @@ If we never change data, do we copy everything all the time? No. Since nobody ca In JavaScript, `[newHead, ...rest]` creates a new array (copying the references, which is cheap for small arrays) but shares every point object. In Scala and F#, `newHead :: rest` is even cheaper: the new list is one new cell pointing to the old list, which is shared entirely. Data structures designed for this are called **persistent**: old versions stay valid after you "update" them. Sharing is only safe because nobody mutates; that is why immutability and persistence go together. -> **From Scala:** `List`, `Vector` and `Map` are all persistent. `xs :+ x` on a `Vector` returns a new vector that shares almost all of its structure with `xs`. F#'s `list` and `Map` behave the same way. +> **From everyday programming:** Git works this way. A commit is an immutable snapshot of your project. A new commit stores only what changed and points to the unchanged files of earlier commits, so it is cheap; and because no commit is ever modified, every old version stays valid and you can check it out at any time. + +In the Scala track, `List`, `Vector` and `Map` are all persistent: `xs :+ x` on a `Vector` returns a new vector that shares almost all of its structure with `xs`. F#'s `list` and `Map` behave the same way. ### Making a changed copy @@ -174,6 +181,10 @@ let ada = { Name = "Ada"; Hp = 10; Level = 3 } let hurt = { ada with Hp = ada.Hp - 4 } // { Name = "Ada"; Hp = 6; Level = 3 } ``` +> **From everyday programming:** in object-oriented code you might write `hero.setHp(hero.getHp() - 4)`. The object changes, and every part of the program that holds a reference to `hero` sees the new value, whether it expected to or not; "who changed this?" bugs come from exactly that. With a changed copy, `ada` stays as it was, and code that should see the hurt hero has to be handed `hurt`. You already know one type that works like this: strings in Java, C#, Python and JavaScript are immutable, so `s.toUpperCase()` returns a new string and leaves `s` alone. That is why you can pass strings around without worrying who else holds them. + +A Scala `case class` and an F# record are immutable records: their fields cannot be reassigned, so `copy` and `with` are the only way to get a changed version. + Note the naming convention: F# record fields are PascalCase (`Hp`, and later `NextDir`), where JS and Scala use camelCase (`hp`, `nextDir`). Watch out in JS: spread is shallow, like `freeze`. `{ ...game, snake: game.snake }` shares the same snake array. That is fine *as long as nobody mutates it*, which is exactly our rule. @@ -188,16 +199,32 @@ For arrays in JavaScript, learn which methods build a new array and which change ### Loops without reassignment -A `for` loop needs a counter that changes, so it needs `let`. Without `let` you do what you did in Scala: express the loop as a transformation of a whole collection (`map`, `filter`, `some`), as a fold (`reduce`), or as recursion. +A `for` loop needs a counter that changes, so it needs `let`. Without `let` you express the loop differently: as a transformation of a whole collection, as a fold, or as recursion. Three operations cover most of the loops you have ever written. Each takes a small function that says what to do with *one* element (the next section, "Functions are values", looks at passing functions around), does the looping itself, and returns a new array or value. The input array is left untouched. + +- **`map(f)`** calls `f` on every element and returns a new array of the results: same length, same order. `[1, 2, 3].map((x) => x * 10)` is `[10, 20, 30]`. +- **`filter(p)`** keeps the elements for which `p` returns `true`. A function that returns a boolean, like `p`, is called a *predicate*. `[1, 2, 3, 4].filter((x) => x % 2 === 0)` is `[2, 4]`. +- **`reduce(f, init)`**, in general called a **fold**, combines all elements into one value. It starts with an *accumulator* equal to `init`, and for each element computes the next accumulator as `f(acc, x)`. The last accumulator is the result. `[1, 2, 3].reduce((acc, x) => acc + x, 0)` computes `((0 + 1) + 2) + 3`, which is `6`. + +Here is one computation written both ways: ```js -// sum of squares of the even numbers, no loop and no let +// With a loop: a counter and a total that change on every step. +let total = 0 +for (let i = 0; i < xs.length; i++) { + if (xs[i] % 2 === 0) total = total + xs[i] * xs[i] +} + +// Without: the sum of the squares of the even numbers, no loop and no let. const sumEvenSquares = (xs) => xs.filter((x) => x % 2 === 0).map((x) => x * x).reduce((a, b) => a + b, 0) ``` -> **From Scala:** `reduce(f, init)` in JavaScript is `foldLeft(init)(f)` in Scala and `List.fold f init xs` in F#; `reduceRight` is `foldRight`. JavaScript's `reduce` *without* an initial value throws on an empty array, like Scala's `reduce`. Always pass the initial value. +Read the second version as a pipeline: keep the even numbers, square each one, add them up. Each step's input is the previous step's output, and nothing is changed along the way. Notice that the loop's `total` became `reduce`'s accumulator: any loop of the shape "start with a value, update it once per element, use the final value" is a fold, whatever it computes. -`Array.from({ length: n }, (_, i) => ...)` builds an array of length `n` by calling a function with each index. `Array.from` is worth looking up now: it is the JavaScript cousin of Scala's `List.tabulate`. +> **From everyday programming:** if you have written SQL, you know this style. `SELECT x * x FROM xs WHERE x % 2 = 0` is a `filter` (`WHERE`) followed by a `map` (`SELECT`), and wrapping it in `SUM(...)` is a fold. You say *what* you want, and the database does the looping. C#'s LINQ (`Where`, `Select`, `Aggregate`), Java streams (`filter`, `map`, `reduce`) and Python's list comprehensions are the same three ideas under other names. + +In the Scala track the three are `xs.map(f)`, `xs.filter(p)` and `xs.foldLeft(init)(f)`; in F#, `List.map f xs`, `List.filter p xs` and `List.fold f init xs`. JavaScript's `reduceRight` folds from the end of the array (Scala `foldRight`, F# `List.foldBack`). One trap: JavaScript's `reduce` *without* an initial value throws on an empty array (so does Scala's `reduce`). Always pass the initial value. ([Fold (Wikipedia)](https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29) has pictures of both directions.) + +`Array.from({ length: n }, (_, i) => ...)` builds an array of length `n` by calling a function with each index. `Array.from` is worth looking up now. (In the Scala track, `List.tabulate` does the same.) ### Functions are values @@ -224,6 +251,10 @@ let addThree n = n + 3 twice addThree 10 // 16 ``` +`twice` returns a new function that still has access to the `f` it was given, even after `twice` itself has returned. A function that captures values from the place where it was created is called a *closure*. A function that takes or returns another function, like `twice`, `map` or `filter`, is called a *higher-order function*. + +> **From everyday programming:** you have passed functions as values whenever you wrote a callback. `button.addEventListener('click', onClick)` hands the function `onClick` to the browser, which calls it later; `setInterval(step, 150)` does the same with a timer; a comparator passed to a sort method is a function that the sort calls whenever it needs to compare two elements. `map` and `filter` take callbacks in exactly the same way. The difference in this course is what the callbacks do: they return a result instead of changing something. + In F#, every function of several arguments is *curried* by default: `let add a b = a + b` has type `int -> int -> int`, and `add 3` is a function waiting for the second number. That is why F# library functions put the data last: `List.map f xs` can be partially applied as `List.map f` and used in a pipeline. This module's `contains(xs, p, eq)` takes a function as its third argument. Why not compare with `===`? Because in JavaScript `===` on objects compares *identity*: `{x: 1} === {x: 1}` is `false`. Passing `eq` lets the caller decide what "equal" means, for instance "same x and same y". @@ -263,7 +294,9 @@ let double x = x * 2 (double << inc) 3 // 8: backward composition, like compose ``` -> **From Scala:** `f andThen g` is F#'s `f >> g` and the JS `pipe` order; `g compose f` is F#'s `g << f` and the JS `compose` order. The F# pipe `x |> f` is `f x` written backwards, which makes long chains read top to bottom, like a chain of Scala method calls. +Side by side: Scala's `f andThen g` is F#'s `f >> g` and the JS `pipe` order; Scala's `g compose f` is F#'s `g << f` and the JS `compose` order. The F# pipe `x |> f` is `f x` written backwards, which makes long chains read top to bottom, like a chain of method calls `x.f().g()`. + +> **From everyday programming:** a shell pipeline such as `cat log.txt | grep ERROR | sort | uniq` is `pipe`: a value goes in on the left and passes through each command in reading order. Each command reads its input and writes a new output; none of them edits `log.txt`. This is why the Scala and F# tracks have a smaller Module 1: `pipe`, `compose` and immutable data are built in. Their tests cover `range`, `dropLast` and `contains`; the composition exercise is a short practice session in the REPL instead. @@ -390,7 +423,7 @@ When green, also run the full suite once (`./test.sh`) to check the purity audit
Exercise 1.1 (range) — nudge -You know a function in each language that builds a collection of a given length from the index. In Scala and F# there is even a ready-made range syntax. +Each language has a function that builds a collection of a given length from the index; for JavaScript, "Loops without reassignment" mentions it. In Scala and F# there is even a ready-made range syntax.
@@ -422,7 +455,7 @@ Which non-mutating method gives you "a piece of the array from here to there"? C
Exercise 1.2 (dropLast) — stronger hint -JS: `slice(start, end)` returns a new array and accepts negative indices, counted from the end. Try `[].slice(0, -1)` in a Node REPL. Scala: `dropRight` exists and is total; or pattern-match on the list. F#: pattern matching on the list is the clearest. Which three shapes can a list have that matter here? (empty, exactly one element, more than one) +JS: `slice(start, end)` returns a new array and accepts negative indices, counted from the end. Try `[].slice(0, -1)` in a Node REPL. Scala: `dropRight` exists and is total; or pattern-match on the list. F#: pattern matching on the list is the clearest. (A `match` checks which shape a value has and names its parts: `[]` is the empty list, `[ _ ]` a list of exactly one element, and `x :: rest` a list whose first element is `x`, followed by the list `rest`. Module 2 explains pattern matching in general.) Which three shapes can a list have that matter here? (empty, exactly one element, more than one)
@@ -545,7 +578,7 @@ export const deepFreeze = (obj, ancestors = []) => { 5. In F#, why is it `List.map f xs` (data last) while in JS our functions take the data first? What does the F# order make easy? 6. Your `dropLast` passes the tests. Would it still be correct if the tests did not freeze the input? How could you be sure it never mutates, other than through tests? 7. The purity audit bans `.sort(` but you may use `.toSorted()`. Explain in one sentence what the difference is, in terms of referential transparency. -8. Why does `contains` take an `eq` parameter in JavaScript, but in Scala you can pass `_ == _` and get structural equality for case classes? +8. Why does `contains` take an `eq` parameter in JavaScript? (In the Scala and F# tracks, `==` and `=` compare case classes and records field by field, so `_ == _` or `(=)` would do; Module 2 explains the difference.) ## In production @@ -563,6 +596,6 @@ export const deepFreeze = (obj, ancestors = []) => { - [Spread syntax (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) — copying arrays and objects with `...`. - [Array.prototype.toSorted (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted) — the non-mutating siblings of `sort`, `reverse` and `splice`. - [F# functions: composition and pipelining](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/functions/#function-composition-and-pipelining) — official docs for `|>`, `>>` and `<<`. -- [Scala 3 book: functions](https://docs.scala-lang.org/scala3/book/taste-functions.html) — a refresher on function values in Scala. +- [Scala 3 book: functions](https://docs.scala-lang.org/scala3/book/taste-functions.html) — function values in the Scala track's syntax. - [Persistent data structure (Wikipedia)](https://en.wikipedia.org/wiki/Persistent_data_structure) and [Chris Okasaki, *Purely Functional Data Structures* (thesis)](https://www.cs.cmu.edu/~rwh/students/okasaki.pdf) — how immutable structures can be efficient. - [Rich Hickey, "Are We There Yet?"](https://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey/) — a talk on values, identity and time; why mutable objects mix up the three. diff --git a/course/02-the-game-as-data.md b/course/02-the-game-as-data.md index 662ebfd..d18e605 100644 --- a/course/02-the-game-as-data.md +++ b/course/02-the-game-as-data.md @@ -31,7 +31,7 @@ Take a sheet of paper and fifteen minutes. **Do not read past the "Stop here" li Your task: write down the type of "the state of a game of Snake", in the language of your track or in pseudo-code. Use these questions to guide you: 1. **What is the minimum information** you need to (a) draw the current screen and (b) compute the next state when time passes or a key is pressed? Is there anything you were tempted to include that can be *computed* from the rest? (Candidates: the snake's length, the set of occupied cells, pixel coordinates.) -2. **How do you represent the snake's body?** A list of cells? In which order? Moving the snake means "add a new cell at the front, remove the last". Which end of a list is cheap to add to in Scala's `List` and F#'s `list`? +2. **How do you represent the snake's body?** A list of cells? In which order? Moving the snake means "add a new cell at the front, remove the last". Which end of a list is cheap to add to? (Scala's `List` and F#'s `list` are singly linked lists, like the ones from a data structures course: each cell holds one element and a pointer to the rest.) 3. **How many directions do you need to store?** Imagine the player presses two keys very quickly, both before the snake moves again. What do you need to remember to handle that well? (Hint: there is "the way the snake is going" and "the way the player has asked it to go".) 4. **Is there always an apple?** Think of the moment right after the snake eats it, and of a board completely filled by the snake. How do you represent "no apple"? 5. **Where does randomness come from?** The apple appears on a random free cell. If the generator's state lives outside the game state, is a function `nextState(state)` still pure? Could you replay a game? @@ -111,7 +111,9 @@ Now compare with your design, question by question. **3. Two directions.** `dir` is the direction the snake **last moved**. `nextDir` is the direction it **will move** on the next step, the most recent valid key press. A key press only changes `nextDir`; a step of the game moves the head in `nextDir` and then records it as `dir`. Why both? You will discover in Module 3 that if you only keep one, a fast player can make the snake reverse into itself. Keep that question in mind. -**4. Optional apple.** There is not always an apple: at the very start (before the first one is placed), and when the snake has filled the whole board. JS uses `null` for "no apple"; Scala and F# use `Option[Point]` / `Point option`, so the compiler forces every reader to handle the "none" case. The board-full situation becomes the status `Won` in Module 4. +**4. Optional apple.** There is not always an apple: at the very start (before the first one is placed), and when the snake has filled the whole board. JS uses `null` for "no apple"; Scala and F# use `Option[Point]` / `Point option`. An `Option` is a small sum type (see "Algebraic data types" below) with two cases: `Some(p)`, which holds a point, and `None`, which holds nothing. You cannot use the point inside without first checking which case you have (usually with a `match`), so the compiler forces every reader to handle the "none" case. The board-full situation becomes the status `Won` in Module 4. + +> **From everyday programming:** `null` is the usual way to say "nothing here", and the `NullPointerException` (or `TypeError: Cannot read properties of null`) is the usual price: nothing reminds the reader that the value might be missing. Java's `Optional`, C#'s nullable types and TypeScript's `Point | null` with strict null checks are all attempts to make "might be missing" visible in the type, which is what `Option` does. **5. The seed is in the state.** The apple's position comes from a pseudo-random generator, and a generator is a little machine with an internal state (a 32-bit number, the *seed*). If that state lived in a global variable, every function that places an apple would read and change a global, so it would not be pure, and two runs could differ. By putting the seed **inside** the game state, "place an apple" becomes a pure function `state → state` that takes the seed out, uses it, and stores the new seed. Same starting seed, same game, every time. Module 4 is all about this. @@ -141,7 +143,9 @@ A **product type** holds several values *at the same time*: a `Point` has an `x` A **sum type** is *one of* several alternatives: a `Direction` is `Up` *or* `Down` *or* `Left` *or* `Right`. An `Action` is `Tick` *or* `Turn` (carrying a direction) *or* ... The number of possible values is the sum of the alternatives'. Each alternative is called a *case* (or *variant*, or *constructor*). -> **From Scala:** you know both. `case class` is a product, `enum` (or a `sealed trait` with case classes) is a sum, and `match` takes a sum apart. F# spells the sum as a *discriminated union* (DU): `type Shape = Circle of float | Square of float`. JavaScript has no sum types, so we encode them by convention, as *tagged unions*. +> **From everyday programming:** a class with fields is a product type. A plain Java or C# `enum` is a sum type whose cases carry no data. In object-oriented code, a sum type whose cases carry *different* data is usually an abstract base class with one subclass per case (`Shape` with subclasses `Circle` and `Square`), plus `instanceof` checks or the visitor pattern to tell them apart. A sum type says the same thing more directly: here is the complete list of cases, and nothing else can be a `Shape`. + +Each track spells them differently. In Scala, a product is a `case class` (an immutable record) and a sum is an `enum` (or a `sealed trait` with case classes). F# spells products as records and sums as *discriminated unions* (DU): `type Shape = Circle of float | Square of float`. JavaScript has no sum types, so we encode them by convention, as *tagged unions*. Here is the same small sum type, unrelated to the game, in all three languages: a traffic light, and a function that gives the next light. @@ -196,6 +200,8 @@ let area s = | Square side -> side * side ``` +The `match` in Scala and F# is called **pattern matching**: it takes a sum apart. It looks like a `switch` statement, with two differences. First, each case can *take the value apart*: `Circle(r)` both checks that `s` is a circle and gives its radius the name `r`. Second, the whole `match` is an expression that returns a value, so no `return` or `break` is needed. Patterns work on lists too: in F#, `[]` matches the empty list and `x :: rest` matches a list whose first element is `x`, followed by the list `rest`. (In Scala the same patterns are written `Nil` and `x :: rest`.) In JavaScript, a `switch` on the `tag` field plays the role of `match`, and you read the data out of the object yourself. + Note the JS lookup-table trick in `nextLight`: an object literal used as a function from a fixed set of strings to values. It is often the clearest way to write a function over a nullary sum type. The important difference between the languages is **exhaustiveness checking**. If you add `case Triangle` to Scala's `Shape` and forget to handle it in `area`, the compiler warns you. F# does the same. JavaScript will silently return `undefined`. In JS you must be your own exhaustiveness checker; the fixtures, which cover every case, help. diff --git a/course/03-pure-game-rules.md b/course/03-pure-game-rules.md index ed760e5..79e43a1 100644 --- a/course/03-pure-game-rules.md +++ b/course/03-pure-game-rules.md @@ -43,7 +43,7 @@ A function is **total** if it returns a result for every possible input. It neve What should `turn` do when the game is paused? Throw "cannot turn while paused"? Then every caller must catch, and a key press during pause would crash the game. Instead, our rules follow one simple convention: **when a rule does not apply, it returns the state unchanged.** `turn` while paused returns the same state. `advance` when the game is over returns the same state. This is what the fixtures `turn-ignored-when-paused`, `advance-game-over-unchanged`, `advance-won-unchanged` check. -> **From Scala:** this is the same reason you prefer `headOption` to `head`, and `Option` to exceptions: totality moves "what if it does not apply?" from a runtime surprise to an ordinary value. In a game, "nothing happens" is the right value surprisingly often. +> **From everyday programming:** the usual alternative is a method that throws an `IllegalStateException` when it is called at the wrong time, so that every caller has to check `canTurn()` first or wrap the call in `try`/`catch`. A total rule behaves more like a disabled button in a user interface: pressing it is allowed, and nothing happens. Totality moves "what if it does not apply?" from a runtime surprise to an ordinary value. In a game, "nothing happens" is the right value surprisingly often. A typical rule therefore starts with a **guard**: @@ -191,7 +191,7 @@ A fixture case is `{ name, args, expected }`. For `advance`, `args` is a one-ele When a fixture test fails, find the case by name, draw the board, and compute by hand what each phase (compute, decide, build) should give. Then compare with what your function returns. Usually one field differs, and the field tells you which phase is wrong. -> **From Scala:** in Scala, the enum values are qualified (`Status.Playing`, `Direction.Up`); add `import Direction.*` and `import Status.*` at the top of `Rules.scala` if you prefer the short names. Comparing a `Status` uses `==` (or `!=`). In F#, union cases are unqualified (`Playing`, `Up`), and record fields are PascalCase (`state.NextDir`). +In the Scala track, the enum values are qualified (`Status.Playing`, `Direction.Up`); add `import Direction.*` and `import Status.*` at the top of `Rules.scala` if you prefer the short names. Comparing a `Status` uses `==` (or `!=`). In F#, union cases are unqualified (`Playing`, `Up`), and record fields are PascalCase (`state.NextDir`). ### Using what you built diff --git a/course/04-randomness-without-side-effects.md b/course/04-randomness-without-side-effects.md index d948778..b4d87e4 100644 --- a/course/04-randomness-without-side-effects.md +++ b/course/04-randomness-without-side-effects.md @@ -52,7 +52,7 @@ seed0 --next--> (v1, seed1) --next--> (v2, seed2) --next--> (v3, seed3) ... That is the whole trick. The numbers look random (they pass statistical tests), but they are completely determined by the first seed. Where does the *first* seed come from? From the outside world, once, at the very edge of the program (in Module 9, the runtime asks the browser's `crypto` for one). Everything after that is pure. -> **From Scala:** `scala.util.Random` and `java.util.Random` are objects with a mutable seed inside: `rng.nextInt()` changes `rng`. Even `new Random(42)` only makes the *start* reproducible; the object is still mutated by each call, so `rng.nextInt() + rng.nextInt()` is not referentially transparent. We want the seed in our hands, as a value. +> **From everyday programming:** the random generators you have used, such as Java's `java.util.Random`, C#'s `System.Random`, Python's `random` module or `Math.random()`, are objects with a mutable seed inside: `rng.nextInt()` changes `rng`. Even `new Random(42)` only makes the *start* reproducible; the object is still mutated by each call, so `rng.nextInt() + rng.nextInt()` is not referentially transparent. We want the seed in our hands, as a value. ### mulberry32, in plain words @@ -205,7 +205,7 @@ Naming differences to note (the full table is in `course/reference.md`): You are going to implement `pure`, `map`, `flatMap` and `run` yourself in Exercise 4.2. The diagrams above are the specification. -### You have met this before: the State monad +### A pattern with a name: the State monad Look at the type again: `seed → [A, seed]`. Replace "seed" by any state type `S`: @@ -215,14 +215,17 @@ State = S -> (A, S) This is the **State monad**: a computation that reads a state, produces a value, and hands on a new state, with `pure` and `flatMap` to sequence such computations without writing the plumbing. Our `Random` is `State`. It is a pure, honest version of "a function that uses and updates a variable": the "variable" is threaded through explicitly, by `flatMap`, so nothing is hidden. -> **From Scala:** you have used `map` and `flatMap` on `List` and `Option`. Any type with a sensible `pure` and `flatMap` is a **monad**, and `Random` is one. "Sensible" means three laws hold: -> - **left identity:** `flatMap(pure(a), f)` behaves like `f(a)`; -> - **right identity:** `flatMap(r, pure)` behaves like `r`; -> - **associativity:** `flatMap(flatMap(r, f), g)` behaves like `flatMap(r, a => flatMap(f(a), g))`. -> -> "Behaves like" means "gives the same result when run with any seed". The tests check the two identity laws (`flatMap with pure on the left or right changes nothing`). +> **From everyday programming:** if you have chained JavaScript promises, you have used this pattern. In `fetch(url).then((res) => res.json()).then((data) => show(data))`, each `then` takes a callback that receives the previous step's result and may return the *next* asynchronous step; the promise does the plumbing (waiting, passing the result on) for you. `then` with such a callback is `flatMap` for promises. Arrays have a `flatMap` too, where each element produces an array and the results are joined: `[1, 2].flatMap((x) => [x, x * 10])` is `[1, 10, 2, 20]`. -Because `Random` has `map` and `flatMap`, Scala's **for-comprehension** works on it directly. A for-comprehension is only syntax: the compiler rewrites it into `flatMap` and `map` calls. +Any type with a sensible `pure` and `flatMap` is called a **monad**, and `Random` is one. (`map` then comes for free: `map(r, f)` behaves like `flatMap(r, (a) => pure(f(a)))`.) "Sensible" means three laws hold: + +- **left identity:** `flatMap(pure(a), f)` behaves like `f(a)`; +- **right identity:** `flatMap(r, pure)` behaves like `r`; +- **associativity:** `flatMap(flatMap(r, f), g)` behaves like `flatMap(r, a => flatMap(f(a), g))`. + +"Behaves like" means "gives the same result when run with any seed". The tests check the two identity laws (`flatMap with pure on the left or right changes nothing`). + +Nested `flatMap` and `map` calls get hard to read as they grow, so Scala and F# offer syntax that makes them look like a sequence of steps. In the Scala track this is the **for-comprehension**, and because `Random` has `map` and `flatMap`, it works on `Random` directly. Each line `a <- r` means "run `r` and call its value `a`", and `yield` gives the final value. It is only syntax: the compiler rewrites it into `flatMap` and `map` calls. **Scala** ```scala @@ -255,6 +258,8 @@ let twoDice : Random = JavaScript has no such syntax for user-defined types, so you write the nested `flatMap`/`map` calls. Once you see them as "and then, with the result...", they read naturally. +> **From everyday programming:** `async`/`await` in JavaScript, C# or Python is the same idea for promises and tasks. `const res = await fetch(url)` reads like an ordinary assignment, but behind the scenes it is a `then` whose callback is the rest of the function. A for-comprehension or a computation expression gives `Random` (or any monad) that same "looks like sequential code" syntax. + ### Drawing a number below `n` To pick one of `n` free cells, you need a number in `[0, n)`. `intBelow(n)` is `nextUint32` with its value taken modulo `n`. That is a `map`. diff --git a/course/05-folding-time.md b/course/05-folding-time.md index ebef97f..b347221 100644 --- a/course/05-folding-time.md +++ b/course/05-folding-time.md @@ -45,7 +45,9 @@ update : (GameState, Action) -> GameState This is the most important function signature in the course. It has a name in several communities: Redux calls it a **reducer**, Elm calls it `update`, and in automata theory it is the **transition function** of a state machine. Everything the game can ever do is described by this one function and the start state. -> **From Scala:** dispatching on a sum type is a `match`. In Scala, `case Action.Turn(dir) => ...` extracts the direction; in F#, `| Turn dir -> ...`; in JS, `switch (action.tag)` and then read `action.dir`. For now a final catch-all case (`case _`, `| _ ->`, `default:`) handles the actions that do nothing. In Module 10 you will replace it by explicit cases, so that the Scala and F# compilers warn you if you ever forget one. +> **From everyday programming:** if you have written an event handler that does `switch (event.type)` with one case per kind of event, or a Redux reducer, you have written this dispatch. The difference here is that each case *returns* the next state instead of changing fields. + +Dispatching on a sum type is pattern matching (Module 2). In Scala, `case Action.Turn(dir) => ...` extracts the direction; in F#, `| Turn dir -> ...`; in JS, `switch (action.tag)` and then read `action.dir`. For now a final catch-all case (`case _`, `| _ ->`, `default:`) handles the actions that do nothing. In Module 10 you will replace it by explicit cases, so that the Scala and F# compilers warn you if you ever forget one. ### A game is a fold @@ -101,6 +103,8 @@ Note the argument orders, which trip people up: JS `reduce(step, initial)` is ca A game of Snake is, literally, `actions.reduce(update, start)`. Time and input have become a list. +> **From everyday programming:** databases rely on the same idea. A database writes every change to a log (the *write-ahead log*) before it applies it; after a crash, it rebuilds its state by replaying the log over the last saved snapshot. A bank works the same way: your balance is a fold over your transactions, which is why your statement lists the transactions and not only the balance. + ### History is a scan A fold gives you only the final state. Sometimes you want every state along the way: to draw a replay frame by frame, to step backwards through a game in a debugger, or to find the first state where something went wrong. The operation that keeps every intermediate accumulator is called a **scan**: @@ -127,7 +131,7 @@ txs.scanLeft(100)(apply) // List(100, 150, 130, 135) List.scan apply 100 txs // [100; 150; 130; 135] ``` -> **From Scala:** `scanLeft` is in the standard library of Scala and F#. In JavaScript you can build a scan out of `reduce`, by making the accumulator the *list of states so far* instead of the latest state. Think about what the step function then has to do. (Building a new array on every step with `[...acc, next]` costs O(n) per step and O(n²) overall. For games of a few thousand steps that does not matter; persistent lists, as in Scala and F#, avoid it.) +`scanLeft` is in the standard library of Scala, and `List.scan` in F#'s. In JavaScript you can build a scan out of `reduce`, by making the accumulator the *list of states so far* instead of the latest state. Think about what the step function then has to do. (Building a new array on every step with `[...acc, next]` costs O(n) per step and O(n²) overall. For games of a few thousand steps that does not matter; persistent lists, as in Scala and F#, avoid it.) ### What the fold gives you for free diff --git a/docs/superpowers/specs/2026-10-28-pure-snake-course-design.md b/docs/superpowers/specs/2026-10-28-pure-snake-course-design.md index 4a94355..18d405e 100644 --- a/docs/superpowers/specs/2026-10-28-pure-snake-course-design.md +++ b/docs/superpowers/specs/2026-10-28-pure-snake-course-design.md @@ -9,22 +9,28 @@ as *values* (an `IO` type and event streams) that the student builds from scratc a single `run` call at the very edge of the program is the only place anything actually happens. -**Why:** students who've had one Scala course know `map`/`filter`/`fold` and case -classes, but rarely know how to apply them to an interactive, stateful system. The +**Why:** students with general programming experience write interactive programs +with mutable state, loops and callbacks, and rarely know how to build one from +pure functions and immutable data. The outcome we want is practical: by the end, a student can take a game written the usual way (`snake.currentPos = ...`, `apple.pos = ...`) and convert it into a purely functional system — and explain each step of how and why — because they have actually done it, not just read about it. +*Change, requested 2026-10-29:* no Scala background is assumed any more; bridges now start from general programming knowledge. + ## Audience -- Bachelor's/master's CS students with one prior Scala course. +- Bachelor's/master's CS students with general programming experience + (variables, loops, functions, classes/objects, collections, callbacks/event + handlers, unit tests, some OOP). No prior Scala or functional programming. - Working **alone at home**. Two supported modes: 1. **Solo** — the text, tiered hints and tests carry them. 2. **With an AI as a Socratic mentor** — the course ships instructions that make an AI assistant guide by questioning rather than handing over answers. - Assumed: basic JS syntax familiarity is *not* required to be strong; Module 1 - bridges from Scala. + introduces `map`/`filter`/`reduce`, immutability and functions as values from + scratch. ## Goals @@ -83,9 +89,10 @@ actually done it, not just read about it. once and language-neutral where possible; every code listing, signature and exercise appears in all three languages (JS / Scala / F#) next to each other. Where a concept is language-specific (Module 1, tagged unions in Module 2), the - module has a short per-track section. Bridges go from Scala (the shared - background) to each track — for F#, "the Scala thing you know is spelled like - this in F#". + module has a short per-track section. Bridges go from general programming + knowledge (loops, objects with setters, callbacks, the classic game loop, unit + tests and mocks, SQL, Git), in blockquotes labelled "From everyday + programming"; Scala and F# specifics are stated as track information. - **Canonical data model, but designed first by the student.** *[call]* Module 2 has the student sketch their own model and answer design questions *before* revealing the course's canonical model, which they then adopt and compare with @@ -104,19 +111,19 @@ actually done it, not just read about it. ## Module sequence -Each module contains: concept text with Scala bridges, learning objectives, +Each module contains: concept text with everyday-programming bridges, learning objectives, exercises with signatures in all three languages, tiered hints (nudge → stronger hint → near-solution), Socratic discussion questions, test suites per track, "further reading" links, and a short "in production" note (cats-effect, fs2, Elm, Elmish, RxJS, etc.). 0. **Setup & how to use the course** — tracks, running tests, solo vs. mentor mode. -1. **Purity & immutability in JS for Scala people** — referential transparency, - `const` vs. immutable data, `Object.freeze`, spread copies, functions as values, +1. **Purity & immutability** — referential transparency, `map`/`filter`/`reduce` + instead of loops, `const` vs. immutable data, `Object.freeze`, spread copies, functions as values, currying/composition (`pipe`, `compose`). 2. **The game as data** — `Point`, `Direction`, `Snake`, `GameState`; algebraic data - types as tagged unions in JS (mapping from Scala `enum`/case classes and F# - discriminated unions); smart constructors. + types as tagged unions in JS (and as Scala `enum`/case classes and F# + discriminated unions), `Option`, pattern matching; smart constructors. 3. **Pure game rules** — `step : State → State`: movement, growth, wall/self collision, illegal 180° turns, eating. 4. **Randomness without side effects** — seeded PRNG as `seed → [value, seed]`;