Hi Wolfgang!
Am So., 4. Sept. 2022 um 22:06 Uhr schrieb Wolfgang Corcoran-Mathe <
[email protected]>:
> Hi Marc, and thanks for your responses.
>
> On 2022-09-03 20:18 +0200, Marc Nieper-Wißkirchen wrote:
> > > That's all right, as long as you test 'promise?' before 'procedure?',
> thus:
> > >
> > > (cond ((promise? thunk)
> > > (force thunk))
> > > ((procedure? thunk)
> > > (thunk))
> > > (t (error "not a thunk")))
> > >
> > > or more simply:
> > >
> > > ((promise? thunk) (force thunk) (thunk))
> >
> > That would work if all procedures returned by "lambda" and by all
> > other procedure constructors but "force" and "make-promise" and using
> > only the standard exports of R7RS to construct them are guaranteed to
> > be disjoint from possible values on which "promise?" returns true. I
> > may be wrong but I currently don't see where R7RS makes this
> > guarantee.
>
> Is there any serious danger of unexpected behavior resulting from the
> above code, though? If an implementation’s ‘promise?’ is true of
> (some) thunks, then I’d expect (force thunk) to evaluate to (thunk).
> If it instead returned ‘thunk’, say, then it would be a problem for
> us, but it would also be a poor implementation. Thus I don’t see any
> practical objections to this kind of dispatch.
>
This is just one sensible way of implementing promises (when they are not a
disjoint type).
Another way would be where `make-promise` is just
(define make-promise
(lambda (obj) obj))
and we would have something like
(define promise?
(lambda (obj) #t))
(define force
(lambda (obj)
(if (%true-promise obj) (%true-force obj) obj)))
See the remark on page 19 of R7RS where it says that "it may be the case
that there is no means by which a promise can be operationally
distinguished from its forced value".
In other words, the small language leaves a lot of underspecified when it
comes to promises. We can tighten the specification in the large
language. But then we have to agree on things like implicit forcing, etc.
Such a discussion does not belong to SRFI 235, though.
With just the small language, we can't write sensible ad-hoc polymorphic
procedures that handle promises differently.
> You might argue that the promise version of ‘if-procedure’, etc.
> belong in a different library, perhaps a library of useful
> delayed-evaluation forms. Maybe so. To me, they seem similar
> enough to the thunk versions to warrant unifying them.
>
The following comment is under the hypothesis that they can be unified in
an ad-hoc polymorphic procedure.
Whenever we specify ad-hoc polymorphic procedures, we should be aware of
three major drawbacks:
- Extra runtime overhead is generated because of the runtime dispatch
needed.
- Type-inference by optimizing compilers is thwarted.
- In a dynamically-typed language like Scheme, typing errors can go
unnoticed.
Thus, there must be a definite advantage when an ad-hoc polymorphic
procedure is specified. Just reducing the number of exported identifiers
(e.g. `lazy-or-procedure` vs `lazy-or-promise`) is not a good reason. It
better reason would be if it is expected that a procedure like
lazy-or-procedure wants to be called with a mixture of procedures and
promises. In the latter case, using a helper procedure
(define promise->thunk
(lambda (obj)
(assert (promise? obj))
(lambda () (force obj))))
maybe much better, though, as it documents the types in the Scheme program,
which misses static typing.
I can't say much more at the moment about the practical consequences, as I
don't yet see a convincing use case for the majority of the procedures
exported by SRFI 235. Most of them only seem to be of academic interest.
I tend to agree with you that promises are probably more interesting than
procedures. But when we want to make lazy evaluation more prominent in
Scheme, we have to go back to the basics and decide on whether to make
implicit forcing mandatory. In that case, something like `
lazy-or-promise` would not be needed; one would just write `or`.
Marc