Summary
In a recent discussion Kresimir Cosic said the following thing to me: "I think that subtyping a-posteriori is great.". When thinking about case classes ala Scala (which I believe was inspired by Haskell), I couldn't help but wonder, why not have case classes a-posteriori?
Scala supports the notion of case classes. Case classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching.
Here is an example for a class hierarchy which consists of an abstract super class Term and three concrete case classes Var, Fun, and App.
abstract class Term;
case class Var(name: String) extends Term;
case class Fun(arg: String, body: Term) extends Term;
case class App(f: Term, v: Term) extends Term;
Okay, that is fine, but why not have something like the following :
class Var { ... }
class Fun { ... }
class App { ... }
casetype Term {
Var;
Fun;
App;
}
AFAICT this method seems far more flexible and useful. I currently could use a case type, so I can't really see a good reason for not implementing it this way in Heron. Adding this to the languages is surprisingly simple.
> Is it just me, or does that look strangely like an > enumeration?
It's only a resemblance on the surface. In an enumeration, you define a set of named values which belong to a new type. In the proposed case type, you define a new type, which can accept any value of any of the new supertypes.
> Could you give an example how this might be used to solve > a problem? Thanks.
Sure, no problem.
In my interpreter I have statement objects. Every statement in Heron is made up of a series of terms and blocks. A term is a sequence of chars (e.g. a symbol, a literal or an identifier), while a block is a sequence of statements delimited by { }. So I want a term object which is either a string or a block object. So how do I represent that? In my opinion the simplest approach is to create a new type which is a subtype of both string and block. I don't want to explicitly modify a string class, so instead I use a-posteriori subtyping to create a new type. One way of thinking about it is as a kind of union type
So my code looks like the following (the syntax for the case type has already changed as one might expect for such a new idea).
class statement { public { def new_statement() : statement* { terminate_current_token(); return parent.new_statement(); } def new_block() : statement* { terminate_current_token(); block* p = new block; terms.append(p); return p.new_statement(); } def close_block() : statement* { assert(parent != NULL); return parent.current_statement(); } def append_char(char c) { if (is_wspace(c)) { terminate_current_token(); } else { switch (tt) { // TODO ... }; token += c; } } } private { def is_wspace(char c) : bool { return (c == ' ' || c == '\t' || c == '\n'); } def is_ident_char(char c) : bool { return (c within range('a','z')) || (c within range('A','Z')) || (c == '_'); } def terminate_current_token() { if (token.count() > 0) { terms.push(new token(tkn)); } tt = tt_empty; } } fields { token_type tt; token tkn; block* parent; stack[term*] terms; } }
Note: this is code under development, and is exploiting features which haven't been implemented yet. I am using a feature driven design methodology to evaluate unimplemented features (e.g. "within" and "enum").
> > And would you allow the creation of methods in a > > subtype? > > That is a good question. I see no reason why not. One > might be able to write a method such as: > >
That would be very cool because then you could limit the cases to the subtype itself. I kind of think you should actually make it the only place you could do this and allow this syntax:
def doThing() { switch (type) { // this type case (string) { // implicit cast to string this.doStringThing(); } case (block) { // implicit cast to block this.doBlockThing(); } } }
> In my interpreter I have statement objects. Every statement > in Heron is made up of a series of terms and blocks. A term > is a sequence of chars (e.g. a symbol, a literal or an > identifier), while a block is a sequence of statements > delimited by { }. So I want a term object which is either a > string or a block object. So how do I represent that?
I don't have much experience with interpreters, but this looks to me like a factory pattern. Make term a supertype of block and string, and then use dynamic cast to access specific functions of string and block. I might easily be wrong.