diff options
| author | 2026-03-08 09:08:14 -0400 | |
|---|---|---|
| committer | 2026-03-08 09:08:14 -0400 | |
| commit | 11ba05b6ac0c7365ac000a23a8024c316c4983d8 (patch) | |
| tree | 584a51c708e616cb76ad354909b37f02137322f6 /README.md | |
| parent | 0.2.0 (diff) | |
first attempt at moving over to SRFI-259
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 100 |
1 files changed, 74 insertions, 26 deletions
@@ -7,12 +7,11 @@ > > -- Revisedⁿ Reports on the Algorithmic Language Scheme (n ≥ 3, 1986–) -HaScheme is a library that implements a subset of the R7RS's libraries -in a lazy way, using `force` and `delay-force`. When HaScheme is used -by itself, it is a lazy functional programming language with the same -syntax as Scheme, embedded within Scheme. +HaScheme is a library that implements a subset of the R⁷RS in a lazy +way. When HaScheme is used by itself, it is a lazy functional programming +language with the same syntax as Scheme, embedded within Scheme. -For instance, the following `map` implementation is both valid HaScheme +For example, the following `map` implementation is both valid HaScheme (importing `(hascheme base)`) and Scheme (importing `(scheme base)`): (define (map f list) @@ -33,25 +32,13 @@ code. HaScheme's datatypes are the same as regular Schemes. Hence lazy and non-lazy code can co-exist. It is easy to wrap eager Scheme procedures to be usable in HaScheme. -HaScheme should run in any R7RS-compatible system that distinguishes -promises from all other types, and that allows forcing non-promises. -There is no need to support implicit forcing. - -HaScheme uses `delay-force`, which is not available in R6RS. HaScheme -could be implemented on top of [SRFI-45][SRFI-45] if the representation -of promises was changed to a `define-record-type` datatype instead of -cons cells. - -See also [Lazy Racket][LazyRacket]. - -[LazyRacket]: https://docs.racket-lang.org/lazy/index.html -[SRFI-45]: https://srfi.schemers.org/srfi-45 - *Every* procedure in HaScheme is lazy. Values are forced in conditionals, 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. +HaScheme should run in any implemention of the R⁷RS. + Why use this? 1. To have fun playing around with functional infinite data structures. @@ -59,6 +46,12 @@ Why use this? 3. To show those dirty Haskellers that you don't need no stinkin' static type system. +See also [Lazy Racket][LazyRacket]. + +[LazyRacket]: https://docs.racket-lang.org/lazy/index.html + +HaScheme is licensed under the 0BSD license. + ## Restrictions and Implementation Notes 1. No `call/cc`. [Explanation](#multiple-values-and-continuations) @@ -74,7 +67,7 @@ Why use this? 7. Parameters are not supported because forcing a promise uses the parameters of the dynamic extent of the force, and not the dynamic extent of the delay. This makes them useless in this context. - This would be fixed by SRFI-226. + This would be fixed by SRFI 226. 8. No quasiquote. ## Fun (or Pain) with Laziness @@ -174,7 +167,7 @@ takes `n` forms, forces the first `n-1`, and returns the `n`th form. HaScheme doesn't have `call/cc`. `call/cc` is not a function because it does not return, so that's strike one for inclusion in a pure language. Reified continuations make sense in a call-by-value language, because -there is a definite evaluation order (outermost first), but a lazy +there is a definite evaluation order (innermost first), but a lazy language can execute any code at basically any time. A future implementation might be able to use SRFI-226's delimited @@ -185,10 +178,65 @@ Multiple values are specified as returning values to their continuation. Since HaScheme does not (conceptually) have continuations, multiple values have to be interpreted differently. But a bigger issue occurs because a promise is a single value. It cannot be decomposed into more -values without forcing the promise. +values without forcing the promise. Multiple value returns are simulated +using lists, although vectors could also work. + +## Why `delay` and `delay-force` Are Not Enough + +Scheme for a long time had `delay` and `force` that were never implemented +very well. It was only in the R⁷RS that they were implemented in a +safe-for-space way. However, the usual transformation does not handle +higher-order procedures correctly. -## License +For example, consider the transformation advocated in [SRFI 45][SRFI-45]. +The `map` function, defined as -0BSD for everything except for `(hascheme implemetation-support)`, which -implements a modified version of the SRFI-45 sample implementation for -systems that do not implement the requirements for promises here. + (define (map f lst) + (if (null? lst) + '() + (cons (f (car lst)) (map f (cdr lst))))) + +becomes + + (define (map f lst) + (delay-force + (if (null? (force lst)) + (delay '()) + (delay (cons (f (car (force lst))) + (map f (cdr (force lst)))))))) + +So far, so good. But let us define + + (define (add-n n) + (lambda (x) + (+ x n))) + +which then becomes + + (define (add-n n) + (delay-force + (lambda (x) + (delay-force (+ (force x) (force n)))))) + +If we evaluated, in normal Scheme, + + (map (add-n 5) '(1 2 3 4)) + +we would get `(6 7 8 9)` back. But if we evaluated this in our lazy +language, we would get an error because we tried to apply arguments to +a non-procedure. + +We could get around this by annotating each higher-order procedure with +`force`. But this violates one of the principles of HaScheme, which is that +the code should look natural. + +Instead, this library uses [SRFI 259][SRFI-259] tagged procedures to wrap +promises. This allows for arbitrary higher-order procedures expressed +in a natural way. + +Each `lambda` creates a procedure object that can be called with an +arbitrary number of arguments to create another procedure. Procedure +argument length is only checked when the procedure is forced. + +[SRFI-45]: https://srfi.schemers.org/srfi-45 +[SRFI-259]: https://srfi.schemers.org/srfi-259 |
