Pattern matching in D

retard re at tard.com.invalid
Mon Mar 8 04:22:13 PST 2010


Mon, 08 Mar 2010 03:35:56 -0800, Walter Bright wrote:

> retard wrote:
>>> I've thought more than once about adding that, but it just seems
>>> pointless. I've never run into a use case for it. If you do run into
>>> one,
>>>
>>>      if (ar == [1,2,3]) ...
>>>      else if (ar == [1,2,4]) ...
>>>      else if (ar == [1,3,2]) ...
>>>
>>> will work just fine.
>> 
>> The same can be said about matching integer values:
>> 
>>>      if (ar == 1) ...
>>>      else if (ar == 2) ...
>>>      else if (ar == 3) ...
> 
> I don't agree, because switching on integer values is commonplace, and
> switching on array literals pretty much never happens.
> 
> (Note: switching on floating point values is worse than useless, as
> floating point computations rarely produce exact results, and supporting
> such a capability will give a false sense that it should work.)
> 
> 
>> I guess the point is just to unify many kinds of cases.
>> 
>> class Foo
>> class Bar : Foo { void doBarMethod() {} } class Bar2 : Foo { void
>> doBar2Method() {} }
>> 
>> if (cast(Bar)foo)
>>   (cast(Bar)foo).doBarMethod();
>> else if (cast(Bar2)foo)
>>   (cast(Bar2)foo).doBar2Method();
>> else
>>   writefln("Oh no!")
> 
> 
> I have to say, if you find code written that way, you're doing OOP
> wrong.

The "correct" solution, multimethods or the visitor pattern, is sometimes 
very expensive. They become very verbose when compared to simple pattern 
matching:

>> foo match {
>>   case b: Bar => b.doBarMethod
>>   case b: Bar2 => b.doBar2Method
>>   case _ => println("Oh no!")
>> }

The OOP argument is false here. Sometimes OOP isn't the best way to 
describe declarative data. It surely allows you to implement this, but 
the cost is a much larger verbosity.

The data type might be just a simple tagged union (in which case powerful 
optimizations can be used). Instead of writing

> enum node_type { sum_node, mul_node, ... }
>
> struct tree {
>   node_type type;
>
>   union {
>    // bla bla
>   }
> }

you can automatically eliminate the boilerplate code with this higher 
level construct. It's also much safer - unions are known to be a security 
loophole when used carelessly. Also with instanceof/cast() you may need 
to cast twice unlike in the match expression above.

>> 
>> (1, (1,2)) match {
>>   case (1, (1, _)) => println("nice tuple") case _ =>
>> }
>> 
>> def main(args: Array[Byte]) =
>>   args(0) match {
>>     case "-help" => println("Usage: ...") case "-moo" => println("moo")
>>   }
> 
> D already supports string switches.

I know that. The idea was to show how general pattern matching unifies 
the instanceof construct, integer switches, string switches and various 
kinds of algebraic data type manipulation such as tree/graph rewriting.

It's a much more generalized construct in all possible ways - and very 
intuitive to use. Some of the beauty is lost when you read the example 
written in Scala, but e.g. in Haskell the pattern matching closely 
reflects the structure of the algebraic data type.



More information about the Digitalmars-d mailing list