[e-lang] Remaining semantic issues
Kevin Reid
kpreid at attglobal.net
Mon Jan 24 12:42:34 EST 2005
On Jan 23, 2005, at 14:48, Mark Miller wrote:
> Kevin Reid wrote:
>> This is a list of bugs which I believe will require changes to
>> Kernel-E semantics or structure.
>> *** SuchThatPattern discards information
>> [...]
>> 1. MatchBindExpr discards the failure exception, except in bindings
>> established by the pattern (of which there can be zero).
>> 2. DefineExpr discards the distinction between match failure and
>> throw()n exceptions.
>
> #2 is appropriate. DefineExpr should collapse these cases. I see no
> problem with this.
Yes; I only mean that it is not suitable for solving this problem.
> As for #1, see how the current expansion of quasi-literal patterns
> uses a guard to capture the optional ejector. Can this technique be
> used to address the issue?
It addresses only half of the issue. I now see that describing this as
being a problem with SuchThatPattern is misleading. Consider this:
(expansion edited to avoid line wrapping)
? def `foo @{bar :int}` := "foo baz"
# expansion: def pair__13 :__MatchContext ? \
# (def [q__15, optEj__17] := pair__13
# simple__quasiParser.matchMaker("foo @{0}") \
# .matchBind(__makeList.run(), q__15, optEj__17) \
# =~ [bar :int]) := "foo baz"
# problem: such-that expression was false
The epatt`bar :int` pattern is in a bare MatchBindExpr and so the
failure information is not propagated.
Here is an hand-written expansion which does propagate the failure:
def pair__13 :__MatchContext ?
(def [q__15, optEj__17] := pair__13
def ok__18 := [simple__quasiParser.matchMaker("foo @{0}") \
.matchBind(__makeList.run(), q__15, optEj__17),
null] =~ [[bar :int], breaker__19]
if (ok__18) { true } else {
throw.eject(optEj__17, Ref.optProblem(breaker__19))
}) := "foo baz"
This is ridiculously complex -- and the SuchThatPattern's expression,
when it exits normally, always returns true.
Attempting to restate the problem:
For non-kernel patterns which contain other patterns (map and quasi),
it is desirable to have a way of evaluating a subpattern which allows
straightforwardly capturing the failure exception, or propagating it to
an enclosing pattern.
Here's an idea for how to make a simpler expansion, which avoids
entirely the 'catch and rethrow'. I originally intended it as a
strawman to start discussion, but it now seems to be a reasonably clean
solution, except for the syntax.
def via (__curry(simple__quasiParser.matchMaker("foo @{0}").matchBind,
__makeList.run())) [bar :int] := "foo baz"
The syntax (which I am not necessarily suggesting we actually use) of
this pattern in the above example is:
via ( EXPR ) PATT
As Kernel-E:
ViaPattern(<EExpr>, <Pattern>)
Its effect is to evaluate EXPR.run(specimen, optEjector), then match
the result of the expression against PATT, failing or succeeding as
PATT does.
'__curry' is introduced merely so that the example is not changing the
interface of quasi-parsers. It would be roughly:
def curry {
match [verb, [target] + argsL] {
def curried {
match [=="run", argsR] { E.call(target, verb, argsL + argsR) }
}
}
}
After inventing this 'via' pattern, I now see that it is almost like a
generalization of ':' (guarded bindings). The differences are:
* ':' exists only as a parameter of Final/Slot/VarPattern, rather
than being itself a pattern.
* ':' on a VarPattern affects what values may be assigned to the
slot, whereas 'via' would be independent of its subpattern.
* ':' uses coerce/2 where 'via' uses run/2. (If these were unified,
then any guard could be directly used as the EXPR of a via, but other
cases might become more complex. With run/2, verb-currying could be
used to adapt a guard.)
Expansion of the map pattern using the via pattern:
# expansion of def ["keyA" => valA, "keyB" => valB] := [].asMap()
def via (__extractor("keyA")) [valA, \
via (__extractor("keyB")) [valB, \
via (__equalsMatch(0)) _]] \
:= __makeList.run().asMap()
# universalScope utilities
# if we instead use coerce/2 for via's verb, then this is useful
# as a guard too.
def __equalsMatch(value) {
def matchFunc(specimen, optEjector) :any {
if (!(specimen == value)) {
throw.eject(optEjector,
`not equal to ${E.toQuote(value)}: ${E.toQuote(specimen)}`)
} else {
return specimen
}
}
return matchFunc
}
def __extractor(key) :any {
def matchFunc(specimen, optEjector) :any {
return specimen.optExtract(key)
}
return matchFunc
}
There are various ways the functions used could be generalized or
unified.
Miscellaneous ideas for other syntaxes for the 'via' pattern, if we use
it (some of these are obviously undesirable, but they might provide
inspiration):
via EXPR PATT # avoid similarity to binding-hiding if/while
# - might be too ambiguous
via EXPR, PATT # some separation over the previous, English-like,
# but odd use of comma
PATT via EXPR # backwards evaluation order, but no syntactic
# ambiguity from adjacency or patterns beginning
# with expressions
PATT ~: EXPR # generalized-pattern version of :
PATT ¿ EXPR # like SuchThat, but backwards (silly)
PATT ?: EXPR
?( EXPR )? PATT
EXPR matching PATT
PATT <= EXPR
>> 3. Sealers are used to distinguish exceptions across turns.
>> [...]
>
> This is a big and scary proposal, but I continue to think it's a good
> direction. I encourage you to try an experimental implementation, and
> discuss the issues as you encounter them.
I will.
--
Kevin Reid <http://homepage.mac.com/kpreid/>
More information about the e-lang
mailing list