There are plenty of almost entirely pointless features that get implemented in major languages, like 'events' in C#. They add almost no value to the programmer. Pattern-matching would be really useful for avoiding rats' nests of control-flow, but until recently no major imperative language even seemed to consider adding them.
The extremely obscure Felix programming language has had pattern matching for years. [0] As nestorD says, Rust has them now, as does Kotlin. About time.
I was pleasantly surprised when I discovered Python 2 would pattern match tuples in function parameters...and then very disappointed when I discovered a few weeks later that Python 3 had removed even that minimal amount of syntactic sugar.
- with objects you can easily add new types, but it's difficult to add functions (methods) dealing with these types
- with sum types you can easily add new functions, but it's hard to add new variants in the sum type
Is it possible to overcome these limitations? I first read about the expression problem in the excellent post "The Expression Problem and its solutions"[1] on Eli Bendersky's blog (discussed here[2] on HN); as the title suggests it does present interesting ways to "solve" the expression problem.
However he also points out that the chosen solution in a a typical programming language (visitor pattern) quickly becomes unwieldy. The second solution (multimethods) is much nicer to deal with, but requires support from the language.
[0]: https://en.wikipedia.org/wiki/Expression_problem
[1]: https://eli.thegreenplace.net/2016/the-expression-problem-an...
It seems like these treatments tend not to get to the heart of the expression problem as seen in actual language tools, which is migration between language versions. Let's say you're on version 5 of a language and you want to migrate all your tools to support version 6 where there is a new expression type. Can your codebase clearly represent a situation where some tools have been migrated to support version 6 and others aren't done yet? Can you easily figure out what remains to be done? And once the migration is done, can we remove any traces of the previous version that we don't want anymore?
And how do we approach this if the AST is published as a library and each tool is a package written by a different team?
There might be other usages of sum types that are simpler, though.
Not only that it makes every possible event explicit and stand-out on its own, which is good for inline optimization and documentation (think about the catastrophic event handling in JS world), it also provides a standard, much more intuitive syntax using formal function delegate declaration (think type-safe function pointers), vastly different than what we do in JVM.
Before having lambdas in Java, we need to add an EventListener as a variable and adding an extra interface, so event handlers are insidious to write, that you have to write a new class, implement the specific interface, write some shim properties to store externally-living variables explicitly, and finally "new" that class as an instance, and add it to a specific event listener, which is not only verbose, and also costly, in terms of memory use (it has to be backed by vtables rather than simple functions) and time taken to implement it.
Well after the long-awaited introduction, Java finally have limited lambda support that just generates a class and it have some odd issues with, for example, enforced effectively final variable reference [0], but in C#, you have delegates and events almost from day 1 -- and it handles all that event mess nice and clean where nobody can still beat that simplicity and elegancy even till today.
[0]: https://stackoverflow.com/questions/34865383/variable-used-i...
I agree that's nice to have - essentially announcing events as special in the type system.
> good for inline optimization
Any optimisation here should be possible with an ordinary implementation of the observer pattern, no?
> it also provides a standard, much more intuitive syntax using formal function delegate declaration
I'm not convinced that it does. Without events, we can still write:
var h = () => { doStuff(); doOtherStuff(); };
subject.registerObserver(h);
> Before having lambdas in Java, we need to add an EventListener as a variable and adding an extra interfaceI suspect we're both right, then: events were introduced for a good reason, but now that C# has lambdas and such, they don't seem to add much.
And I find that we disagree. When I frame a question as, "How does this algorithm handle this value?" they frame it as, "How does this class behave in this algorithm?" Is the difference in how the types are treated in the algorithm best expressed as the concern of the class (via polymorphism) or as the concern of the algorithm (via pattern-matching)?
I write a lot of OO code with polymorphic methods and am not opposed to modeling things that way, but I think it's often not the best way. I feel like business logic that could be expressed coherently in a single place gets scattered across many classes, and to understand the algorithm you have to gather the logic from a bunch of different places and reconstruct it. Not only that, classes accumulate little fragments of logic that belong to disparate concerns that are supposed to be handled elsewhere. If you have polymorphism and not pattern matching, this is inevitable. If you have both, it can be avoided.
It is a common feature in languages derives from the ML family[0].
[0]: https://en.wikipedia.org/wiki/ML_(programming_language)
Needless distinctions like that make the fresh air a little less fresh...