Am Fr., 23. Nov. 2018 um 08:56 Uhr schrieb Mark H Weaver <
mhw@netris.org>:
Hi Marc,
Marc Nieper-Wißkirchen <marc@nieper-wisskirchen.de> writes:
> Am Mi., 21. Nov. 2018 um 04:38 Uhr schrieb Mark H Weaver <mhw@netris.org>:
>
> > Ellipsis identifiers are a bit more tricky, because unlike other
> > bindings, the user-visible ellipsis identifiers are not actually
> > substituted. We can't do that because ellipsis identifiers can be used
> > for other purposes, e.g. bound to ordinary variables or macros, and
> > these two ways of binding ellipsis identifiers should not shadow each
> > other.
Sorry, this was a weak argument. I've since remembered the _actual_
reason that it needs to be done this way:
In general, for any sensible binding form, bindings with different names
should not shadow each other. For example, a binding for 'E' should not
shadow a binding for 'F'.
If you try to model (with-ellipsis E BODY) as binding 'E', then it is
not a sensible binding form in this sense. Unlike any sensible binding
form, (with-ellipsis E BODY) should not only shadow outer bindings of
'E', but *any* outer ellipsis binding, whether '...' or 'F' or something
else. In other words, in this model, there can be only one binding of
this kind in a given lexical context.
Alternatively, it would be enough for all use cases that come to my mind if `(with-ellipsis E BODY)' besides binding `E' would shadow `...' and nothing else. E.g. under this alternative semantics, in the BODY of `(with-ellipsis E (with-ellipsis F BODY))' both `E' and `F' would function as ellipses, while `...' would not. In other case, `with-ellipsis' would work like the following macro transformer:
(lambda (stx)
(syntax-case stx ()
((k e body)
(with-syntax ((::: (datum->syntax k '...)))
#'(let-syntax ((::: <undefined>) (e <ellipsis>))
body)))))
This way, `with-ellipsis' would behave more like an ordinary (albeit unhygienic) binding construct.
Therefore, I think this is a bad model for 'with-ellipsis'.
I think it makes more sense to model (with-ellipsis E BODY) as
introducing a new lexical binding with a fixed name. CURRENT-ELLIPSIS
would be a good name if we wanted to make it part of the public API. In
this model, 'E' becomes the _value_ of the binding, instead of the
binding's name.
Does that make sense?
So CURRENT-ELLIPSIS would be the name of an identifier whose name is disjoint to all names a user could use for their identifiers? Or would you model CURRENT-ELLIPSIS as a parameter object, which would entail a different semantics?
Note that in this later model, it's not natural for (with-ellipsis E
BODY) to shadow a binding for 'E' or vice versa, because 'E' is not
actually the thing being bound by 'with-ellipsis'.
In theory, we could introduce additional mechanisms to force
(with-ellipsis E BODY) to shadow bindings for 'E' and vice versa. This
would entail changing *every* binding form to check if the identifier
being bound matches the current binding of CURRENT-ELLIPSIS, and if so,
somehow invalidating the ellipsis binding. It's not obvious what should
be done in this case.
We could do something like this, but in my view, it would be an ad-hoc
addition to the semantics in order to support the inadequate mental
model where (with-ellipsis E BODY) is thought of as binding 'E'.
What do you think?
I agree with you that trying to shadow bindings for `E' in a model in which `with-ellipsis' does not actually bind `E' but some other identifier is the opposite of being beautiful. So I don't think one should try to do this in this model.
We have, however, the semantics of R[67]RS, where `...' has an actual binding as auxiliary syntax. This allows to have more than one identifier acting as the ellipsis at the same time (just import `...' from `(scheme base)' or `(rnrs base)' under two different names). Whatever model for `with-ellipsis' is chosen, it should work well with `R[67]RS'.
It just comes to my mind that this rules out my model from above (the syntax transformer that rebinds `...'). :-)
So, here may be a better model: Each lexical scope has a flag ORIGINAL-ELLIPSIS?. In the top-level scope, the flag is set. As long as the flag is set, `syntax-case' and `syntax' check for the ellipsis by using `free-identifier=?' and comparing with the binding for `...' as exported by `(rnrs base)'. Now, `(with-syntax e BODY)' clears this flag in BODY and binds `e' to a special ellipsis value.
Alternatively, in the model where `with-ellipsis' should not bind anything, `ORIGINAL-ELLIPSIS?' becomes your `CURRENT-ELLIPSIS'. As long as it is unset, `syntax-case' and `syntax' check for the ellipsis by using `free-identifier=?' as above. When `CURRENT-ELLIPSIS' is bound to `e', the checking is done with `(bound-identifier=? e ---)'.
-- Marc