We will need a case statement in Grace to cover both the kind of simple cases covered by switch statements in C-family languages (though with less error prone syntax) as well as typecase statements that will provide the introspection on types and internal structure information as Java’s instanceof and Smalltalk’s understands: and isKindOf: . We could also consider a richer style of pattern matching that takes advantage of ML and Haskell-style destructuring pattern matching, similar to that used by Scala and Newspeak.
The simplest use of a case statement would be the following (lifted from a Scala example):
1234567 method matchTest (x: Number) {x match {case 1 => “one”case 2 => “twootherwise => “lots”}}
The keyword “otherwise” could be replaced by “else” to keep down the number of keywords, but it is arguably a good idea for it to be distinct from the conditional alternative to help (human) parsing (but could be argued out of it).
Putting “match” second follows Scala, and looks like we are sending the match message to Number (in fact, match could be defined in Object or whatever we are calling Top) with a parameter which is a lexical closure, or something like it. On the other hand, putting match first follows most other Grace statements.
Going a step further
An interesting issue is whether or not to go farther with pattern matching. This would have power similar to that in Scala, and approach Newspeak, but it would work somewhat differently because our constructors work differently from theirs.
The idea is that classes that we would like to use pattern matching with would require a special method that would extract the values to be used in a pattern match. For the example with Some<T>, we might add:
1 method extract = [value] // this is considered a 1-tuple
Then we could match against an Option type like this:
12345 v match {case Some<T>[v'] => out.println(i ++ ": " ++ v')lastValue := vcase None => out.println(i ++ " is empty")}
A list data type might have an extractor:
1 extract() : [head,tail] // 2-tuple, not list...
where head and tail are instance variables. A match could then look like:
1234 listValue match {case List(hd,tl) => ... hd ... tl ...case Empty => ...}
The real question is, first, whether the benefits are worth the extra features here; and second, whether to stop there or head for a full Newspeak style generalised match protocol. One advantage of a full protocol is that other useful matches – notably regexp string matching – may be able to be incorporated into the same construct.
An alternative: pattern matching lambdas
Rather than introducing a special match statement with syntax to handle multiple cases, we have an alternative proposal where any block (with parameters) acts as a single pattern-match against those parameters. It is an error if the block is called with parameters that don’t match.
Then, a match exp case block case block “method” combines several pattern-matching lambda-blocks into a full match. For example:
123456789101112 match(x)// match against a literal constantcase { 0 => "Zero" }// typematch, binding a variable '''looks like normal parameter passing case'''case { s : String => println(s) }// match against an existing name - requiring parens like Scalacase { (pi) => println("Pi = " ++ pi) }// destructuring match, binding variables...case { Some(v) => println(v) }
This pattern-matching-lambda proposal was inspired by the realisation that, in Grace, every block (lambda expression) is potentially a type-match. This is because any lambda could be called from a dynamically-typed context, and so Grace will need to implement that type-match anyway. It turns out that Haskell, at least, also supports destructuring in lambda expressions.
Of course, both the lambda design and the multiple case syntax give Grace anonymous partial function definitions — that’s a good thing. The choice here is between a special syntatic construct for handling a series of cases that is used only within a “match” statement, or generalising lambda blocks to support matching against a single case and having a match construct that just glues a series of lambdas together. Pattern match lambdas are potentially more flexible, but it’s not clear which is easier to implement.
Questions
- How powerful should matches be:
- separate value match and type match statements?
- single match statement that handles both value and type matches?
- …also including destructuring matches?
- …also including a full OO matching protocol allowing first-class patterns and user-level matching?
- Do we stay with the Scala/Newspeak design – simple method definitions which can have a match statement inside it?
- or should we support multiple cases and matching for method definitions?
- or head out towards multimethods and predicate dispatch?
- Which alternative – a match statement or pattern-matching-lambdas?
This might sound like a radical plan, but I would make pattern matching as powerful as possible. Even going as far as supporting full regular expressions makes sense to me (since you’ll want regexp’s anyway) …
Eitherway, I’m a big fan of destructuring … it just saves hassle!
But, I don’t really get the pattern matching lambdas. Presumably the case statements basically become a series of if-statements which try to match the arguments of each lambda in succession. But, how does one “test” whether a lambda will match a given value or not?
Presumably the case statements basically become a series of if-statements which try to match the arguments of each lambda in succession.
well yes – the last code example above shows how the case statement would look – here’s another simple one:
. But, how does one “test” whether a lambda will match a given value or not?
So our current thoughts are that Grace lambdas (blocks) will have an apply method that applies the block:
Any block that has an “apply” method returning T will also have a “try” method returning Option. Where “apply” will raise an error if the parameters given to the block don’t match the block’s arguments, “try” will just return None. But no Grace programmer – especially not novices – should need to see this detail until they are ready to.
I think that the question of how to incorporate case into an o-o language — by which I mean one with procedural encapsulation — is a complex one that requires more thought.
In James’s post, I don’t yet understand the semantics of
Under what conditions does v match Some? One object-oriented answer would be “when the message send v.match(Some) returns true. At that point v is sent extract, and the result bound to v’. Is that the intent?
If it is, we can already write it like this
where
match()with()ifSo{}
is an ordinary message, and we are using the binding construct that we already have, lambda-binding, rather than introducing something new.Not that I’m opposed to introducing a new binder if it does something that is desirable, and which we can’t achieve otherwise.
I also believe that James mis-spoke in his reply above when he typed:
Any block that has an “apply” method returning T will also have a “try” method returning Option.
I think that he meant: Any block with a type assertion on its parameter …. I think that it will turn out to be more convenient to have it be an
apply()ifFail{}
message rather than a try message, but we shall see … that’s more a matter of style.Andrew asks: Under what conditions does v match Some? One object-oriented answer would be “when the message send v.match(Some) returns true.
Well that’s really the first question at the bottom of the main post – how powerful should match be? Sending v.match(Some) (or some.match(v) – match generally need to double-dispatch anyway) makes matching arbitrarily extensible: Regexps, for example, can be incorporated as another kind of pattern. Is that what we want in Grace?
At that point v is sent extract, and the result bound to v’. Is that the intent?
pretty much!
Any block that has an “apply” method returning T will also have a “try” method returning Option.
I think I was right: if “apply” is “(A,B) -> T” then “try” would be “(A,B -> Option(T)” (bearing in mind we haven’t even chosen a syntax for types in Grace!
I think that it will turn out to be more convenient to have it be an apply()ifFail{} message rather than a try message, but we shall see … that’s more a matter of style.
Yes it is a matter of style, and just an implementation detail: Newspeak does it with fail-blocks, Scala with options on return.
Any block with a type assertion on its parameter ….
We still want to be able to “try” a block with no types on its parameters – such a “try” will always succeed. If we go with lambda-matching, the question then becomes: if a programmer puts in explicit types, when do we raise (static or dyanmic) errors, and when does the block silently become a no-op.
Something like this should be raise an error:
(assuming students have nothing in common with machine tools).
But this could act as a filter within the for loop, printing just the GradStudents from a list of Students:
So it turns out Claus Reinke made a lambda-match proposal as a simplification to Haskell.
This is very similar to pattern matching lambdas above.