[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)) {
           `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 

Miscellaneous ideas for other syntaxes for the 'via' pattern, if we use 
it (some of these are obviously undesirable, but they might provide 

   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)


   ?( EXPR )? PATT

   EXPR matching PATT


>> 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