Pattern Matching – Make the Compiler Work for You
deliberate-software.com
deliberate-software.com
Statically typed languages like Haskell and Rust require the programmer to list all possible cases when matching on a value.
> Statically typed languages like Haskell and Rust require
> the programmer to list all possible cases when matching
> on a value.
This isn't a consequence of static vs. dynamic, it's just a question of philosophy. In fact, Rust didn't use to require you exhaustively list out all cases, though it does today in order to help catch errors at compile-time rather than at runtime.I should also mention that if yo don't want to list out all variants, that's easily done by using the _ pattern (the "ignore this" pattern):
match some_direction {
East => print("You're going east!"),
_ => print("You're going in some direction that isn't east!")
}http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse....
> Incomplete 'switch' cases on enum
> When enabled, the compiler will issue an error or a warning whenever it encounters a switch statement which does not contain case statements for every enum constant of the referenced enum nor a default case. A default case is assumed to cover missing case statements for any enum constant, so the compiler is silent in the presence of default cases unless the option "Signal even if 'default' case exists" (see below) is enabled.
EDIT: oddly, I thought the blog post's first example was Java, not C#. Man, I forget how similar the syntax is.
Fairly recent gcc (last few years) now has a -Wswitch-enum flag which says "warn me even when there is a default label" which is the safest option (I assume clang has something similar though I haven't actually checked).
I guess even more annotations are needed ;)
http://benjiweber.co.uk/blog/2014/05/03/pattern-matching-in-...
Still, I'd love to have first-class pattern matching in C# and of course algebraic data types...
Also, the enum in a switch is the least powerful thing F#'s pattern matching gives you. Consider the second to last example, where I turn three nested switch statements into a single match. Now the compiler checks on the _combination_ of those three for anything missing. That is far more powerful than any static analysis tool I've seen can detect. Think, any time you need polymorphism, instead of reaching for classes and interfaces, you can just have a "combined" match that not only checks that you have all the types, but that the conditional work each type was doing is "all there". That's some powerful stuff.
What that leads to is the inverse of traditional polymorphism, now the "behavior" for a type can reside where it's needed, all together right where used. Not only is this safer, but it's much more likely to be the axis on which it will change.
In my experience, the traditional interface and class polymorphism changes in such a way that I need to edit every class. Usually, it's an extra parameter one class needs, or a different return type. Changes like that mean editing every subclass. Instead, if I used "match" for my polymorphism, those changes would just be all together, next to each other.
What you lose with this inverted polymorphism is you no longer have a single place to see all the behavior for a single type. We've changed the grain of how the type is used to run such that seeing the behavior for a single function for all types is possible, but seeing all the functions for a single subclass requires going to several classes. The good news here is _you can choose which style you want for every single function_. You can even have this function be a match on the type in place, while this other lives on the subclass! Now you can choose the best axis based off the likely change pattern. Going to be changing the interface a lot? Make it a match. Going to be only changing the internals of a single subclass a lot? Put it in the interface. And it's easy to change one to the other, so you don't even have to get it right the first time!
That being the case, and seeing how powerful the match is at detecting missing edge cases, I'd suggest always start with match for polymorphism, then convert to an interface method when needed.
The true solution, that doesn't involve using a new language, is to include the data for the switch inside each of each element of the enum, instead of treating it like C enums. That forces you to define all you need for "German" and give it to a constructor; otherwise the compiler complains about it. Come on, it's one of the very few things from Java that I miss when I do C++.
By the way, in Spanish it's "cero" not "zero" :).
As to cero, yep, whoops!
public interface ILanguage {
String convert(int num);
}
Then you would subclass for each language:
public class SpanishLanguage implements ILanguage {
public String convert(int num) {
String ret;
switch (num) {
case 0: ret="cero";break;
case 1: ret="uno";break;
default: ret="~~~";
}
return ret;
}
}And the same for English, German or whatever you want. Yes, it gets too verbose, that's why I'd prefer just a translation table... in any case, you can abstract common code for languages that only implement translation for 1, 2 and others, and create more complex subclasses for different behaviours. But I digress...
Now that you have the interface, you would declare the enum as follows:
public enum Language {
SPANISH(new SpanishLanguage()), ENGLISH(new EnglishLanguage()), GERMAN(new GermanLanguage());
// This is a member instance.
private ILanguage translationImpl;
// And this is the one and only constructor.
public Language(ILanguage translationImpl) {
this.translationImpl=translationImpl;
}
public String convert(int number) {
return translationImpl.convert(number);
}
}If you do this, you need to supply the implementation of the language to the constructor, otherwise your code fails to compile. So you can't add FRENCH or JAPANESE after GERMAN(new GermanLanguage()); you need FRENCH(new FrenchLanguage()), JAPANESE(new JapaneseLanguage()) and so on. Of course you can do JAPANESE(null), but it's immediately clear that you're doing it wrong.
I'm completely aware that there is a lot of Java-ness (in the bad sense) in this code, so maybe in C# you can do it more elegantly. I haven't touched C# for a good 10 years...
EDIT: formatting.
public interface ILanguage {
String convert(int number);
}
public enum Language implements ILanguage {
English {
String convert(int number) {
switch(number) {
case 0: return "zero";
case 1: return "one";
default: return "...";
}
}
},
Spanish {
String convert(int number) {
switch(number) {
case 0: return "zero";
case 1: return "uno";
default: return "~~~";
}
}
}
}Miss their power ever since. Thankfully FP is finally spreading into the industry.