diff options
| author | 2025-08-31 21:54:08 -0400 | |
|---|---|---|
| committer | 2025-08-31 21:54:08 -0400 | |
| commit | c63d2b7314ce350d743508a3ff56a2e195013f9f (patch) | |
| tree | 12e3bb53d46ca75e4b2c321aa28a1ec9ab39082c /README.md | |
| parent | hascheme init -- figured out some space leak issues (diff) | |
make conditionals procedures
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 74 |
1 files changed, 69 insertions, 5 deletions
@@ -1,14 +1,23 @@ # HaScheme -- Call By Name Scheme +> Scheme demonstrates that a very small number of rules for forming +> expressions, with no restrictions on how they are composed, suffice to +> form a practical and efficient programming language that is flexible +> enough to support most of the major programming paradigms in use today. +> +> -- Revisedⁿ Reports on the Algorithmic Language Scheme (n ≥ 3, 1986–) + This is a library that exports interfaces similar to the R7RS, except everything is call-by-need and not call-by-value: there is no need to -explictly use `delay` or `delay-force`. Procedures can be written in -ways that look almost identical to regular Scheme. The procedures -return promises which can be forced by non-lazy code. Hence lazy and -non-lazy code can co-exist. +explictly use `delay` or `delay-force` (in most scenarios). Procedures +can be written in ways that look almost identical to regular Scheme. The +procedures return promises which can be forced by non-lazy code. Hence +lazy and non-lazy code can co-exist. *Every* procedure in HaScheme is lazy. Values are forced in conditionals, -or explicitly using `!`. +or explicitly using `seq`. This allows for the call-by-value semantics +of Scheme to be turned into call-by-need semantics without any syntactic +cruft. ## Fun (or Pain) with Laziness @@ -45,3 +54,58 @@ Instead of writing `force`, the operator `!` is used: where `(! x)` is defined to just be `x` in Scheme. Now the code block above operates the same in Scheme and HaScheme. + +Ok, now we have fixed our space leak issues. Right? Let's try another +infinite list trick: a list of all natural numbers. + + (define naturals (list-tabulate +inf.0 (lambda (x) x))) + (! (list-tail naturals 1000000000)) + +This also leaks! This is because the promises are making new cons cells, +and storing them in `naturals`. We need to organize things to make sure +the program can clean up. + + (! (list-tail (list-tabulate +inf.0 (lambda (x) x)) 1000000000)) + +This will run in bounded space. + +## Call-by-Need and Conditionals + +Since call-by-need will only execute a function when needed, conditional +forms like `if` can be implemented as functions and not syntax. In fact, +HaScheme implements `if`, `and`, `or`, and the `cond`-like `cond*` as +functions, meaning one can pass them around as values. + +For instance: + + (define (map f l) + (cond + ((null? l) '()) + ((pair? l) (cons (f (car l)) (cdr l))) + (else (error "not a list" l)))) + +implemented with `cond*` is + + (define (map f l) + (cond* + (null? l) '() + (pair? l) (cons f (car l) (cdr l)) + #t (error "not a list" l))) + +Neat, right? Well, if we go to `list-tail` we have a problem: + + (define (list-tail list n) + (if (zero? n) + list + (list-tail (! (cdr list)) (- n 1)))) + +Since `if` is now a function, Scheme (our call-by-value host language) +will attempt to reduce `(! (cdr list))` every time, even when we don't +need to. We could go back to syntactic if, or we could add some wrapper +to the procedure. The easiest thing to do is `delay-force`. I + + (define (list-tail list n) + (if* (zero? n) + list + (seq (cdr list) + (list-tail (cdr list) (- n 1))))) |
