From 9ec2374a3d0025e1662dd4764d71d6625a5e8357 Mon Sep 17 00:00:00 2001 From: Arthur Paulino Date: Thu, 24 Oct 2024 17:47:58 -0300 Subject: [PATCH] Update manual * Mention the mutually recursive nature of `letrec` bindings and fix iteration counts and some hashes * Mention the fact that we can compose expressions with meta commands and briefly explain how they work --- src/03-lisp.md | 6 +++--- src/07-reference.md | 26 ++++++++++++++++++-------- src/08-repl.md | 45 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/03-lisp.md b/src/03-lisp.md index 9d629b1..83a9e79 100644 --- a/src/03-lisp.md +++ b/src/03-lisp.md @@ -180,7 +180,7 @@ But how do we name functions? ### Bindings We'll come back to recursive functions in a bit. -First, let's see how `let` allows us to introduce varible bindings. +First, let's see how `let` allows us to introduce variable bindings. ``` lurk-user> (let ((a 1)) a) @@ -275,7 +275,7 @@ lurk-user> 0 (+ n (sum-upto (- n 1))))))) (sum-upto 5)) -[54 iterations] => 15 +[55 iterations] => 15 ``` ## Higher-order functions @@ -292,7 +292,7 @@ lurk-user> (map f (cdr list)))))) (square (lambda (x) (* x x)))) (map square '(1 2 3 4 5))) -[76 iterations] => (1 4 9 16 25) +[78 iterations] => (1 4 9 16 25) ``` By the way, how was the list `(1 2 3 4 5)` produced so easily? diff --git a/src/07-reference.md b/src/07-reference.md index a655539..809be01 100644 --- a/src/07-reference.md +++ b/src/07-reference.md @@ -193,7 +193,7 @@ lurk-user> (current-env) lurk-user> (let ((x 1)) (current-env)) [3 iterations] => lurk-user> (letrec ((x 1)) (current-env)) -[3 iterations] => ))> +[4 iterations] => ))> lurk-user> ((lambda (x) (current-env)) 1) [4 iterations] => ``` @@ -379,7 +379,9 @@ lurk-user> ((lambda () (emit 1) (emit 2))) ### `let` -`(let ((var binding)...) body)` extends the current environment with a set of variable bindings and then evaluate `body` in the updated environment. `let` is used for binding values to names and modifying environments. See also the `def` REPL meta command. +`(let ((var binding) ...) body)` extends the current environment with a set of variable bindings and then evaluate `body` in the updated environment. +`let` is used for binding values to names and modifying environments. +See also the `def` REPL meta command. ``` lurk-user> (let ((x 1) (y 2)) (+ x y)) @@ -399,15 +401,23 @@ lurk-user> (let ((a 1) (b 2)) (emit a) (emit b)) ### `letrec` -`(letrec ((var binding)...) body)` is similar to `let`, but it enables recursion by allowing references to `var` inside its own `binding`. Generally, the binding is be a `lambda` expression representing a recursive function. See also the `defrec` REPL meta command. +`(letrec ((var binding) ...) body)` is similar to `let`, but it enables mutually recursive bindings. +See also the `defrec` REPL meta command. ``` lurk-user> (letrec ((x 1)) x) -[3 iterations] => 1 +[4 iterations] => 1 lurk-user> (letrec ((x 1)) (current-env)) -[3 iterations] => ))> ;; Thunks are the internal representation used for recursive evaluation -lurk-user> (letrec ((last (lambda (x) (if (cdr x) (last (cdr x)) (car x))))) (last '(1 2 3))) -[19 iterations] => 3 +[4 iterations] => ))> ;; Thunks are the internal representation used for recursive evaluation +lurk-user> +(letrec ((last (lambda (x) (if (cdr x) (last (cdr x)) (car x))))) + (last '(1 2 3))) +[20 iterations] => 3 +lurk-user> +(letrec ((odd? (lambda (n) (if (= n 0) nil (even? (- n 1))))) + (even? (lambda (n) (if (= n 0) t (odd? (- n 1)))))) + (odd? 5)) +[53 iterations] => t ``` Similarly to `let`, the body is interpreted as if there were a `begin` surrounding it. @@ -416,7 +426,7 @@ Similarly to `let`, the body is interpreted as if there were a `begin` surroundi lurk-user> (letrec ((a 1) (b 2)) (emit a) (emit b)) 1 2 -[7 iterations] => 2 +[9 iterations] => 2 ``` ### `u64` diff --git a/src/08-repl.md b/src/08-repl.md index ed7a60d..9f8e5be 100644 --- a/src/08-repl.md +++ b/src/08-repl.md @@ -2,6 +2,8 @@ Let's explore more of this tool we've been interacting with, the Lurk REPL. +## Meta commands + Until now, every Lurk expression we've evaluated was evaluated under an empty environment. Trying to evaluate a dangling `a` would trigger an error indicating an unbound variable. @@ -40,14 +42,41 @@ And it can also provide further help on specific meta commands. lurk-user> !(help def) def - Extends env with a non-recursive binding. Info: - Gets macroexpanded to (let (( )) (current-env)). + Gets macroexpanded to (let (( )) (current-env)). The REPL's env is set to the result. - Usage: !(def ) + Format: !(def ) Example: !(def foo (lambda () 123)) + Returns: The binding symbol +``` + +As hinted above, meta commands have return values. +That's because we can build expressions that contain meta commands. +The meta commands will be evaluated first, having their occurrences in the original expression replaced by their respective return values. +The resulting expression is then evaluated. + +``` +lurk-user> (+ !(def b 21) b) +b +[2 iterations] => 42 +``` + +When processing `(+ !(def b 21) b)`, `!(def b 21)` is evaluated, which adds `b` to the environment and returns the symbol `b`. +The resulting expression is `(+ b b)`, which, once evaluated, results in `42`. + +To double check which expression was evaluated, you can enter the debug mode with `!(debug)` (make sure to check `!(help debug)`!). + +This is what the debug mode shows: + +``` +?0: (+ b b) +?1: b + 1: b ↦ 21 +!1: b ↦ 21 + 0: (+ b b) ↦ 42 ``` -However, we need to go over a few abstractions in order to understand some meta commands you may encounter. +Now, let's go over a few abstractions in order to understand some meta commands you will encounter. ## Chaining callables @@ -75,22 +104,22 @@ lurk-user> (let ((counter (+ counter x))) (cons counter (commit (add counter))))))) (add 0))) -[6 iterations] => #c0x8b0d8bd2feef87f7347a8d2dbe7cc74ba045ec0f14c1417266e3f46d0a0ac5 +[7 iterations] => #c0x64fee21bad514ff18399dfc5066caebf34acc0441c9af675ba95a998077591 ``` And let's see what happens when we provide the argument `5` to it. ``` -lurk-user> (#c0x8b0d8bd2feef87f7347a8d2dbe7cc74ba045ec0f14c1417266e3f46d0a0ac5 5) -[13 iterations] => (5 . #c0x60b9491bb451ddac036e2b9e77256da893f505c3b480b477599b1bfcb6f334) +lurk-user> (#c0x64fee21bad514ff18399dfc5066caebf34acc0441c9af675ba95a998077591 5) +[14 iterations] => (5 . #c0x73c6bd6ffb74c34b7c21e83aaaf71ddf919bb2a9c93ecb43c656f8dc67060a) ``` We get the current counter result and the next callable (a functional commitment in this example). So let's provide the argument `3` to this next callable. ``` -lurk-user> (#c0x60b9491bb451ddac036e2b9e77256da893f505c3b480b477599b1bfcb6f334 3) -[13 iterations] => (8 . #c0x4570f0489268d7f99f28b99aaa00ac16fa422e5c67d9eb20da573a071f2873) +lurk-user> (#c0x73c6bd6ffb74c34b7c21e83aaaf71ddf919bb2a9c93ecb43c656f8dc67060a 3) +[14 iterations] => (8 . #c0x67dfd6673beec5f6758e3404d74af882f66b1084adabb97bc27cba554def07) ``` The new result is `8` and we also get the next callable, as expected.