7 ms·
Pattern matching is not just sugar for an if-elseif chain, since compilers will check for exhaustiveness. This is especially important when used with sum types
by kinghajj 11y ago
Pattern matching is not just sugar for an if-elseif chain, since compilers will check for exhaustiveness. This is especially important when used with sum types (which are the primary motivator for this feature):
data Thing
= Foo String
| Bar Int Double
| Baz String String
case thing of
Foo s -> putStrLn s
Bar i f -> -- ...
Baz s1 s2 -> -- ...
If you later add another variant to Thing, the compiler will check that all pattern matches against it account for the new case. (Wildcard matches alleviate the need always to handle every case.) An if-elseif chain, however, would still compile after adding the new variant, so you must track down the usages manually.
- Cthulhu_ 11y agoAha, thanks for that bit, I didn't realize that. I thought Swift, which has a switch/case statement that does not have automatic fallthrough and which has to be exhaustive, was special in that regard. (Although in Swift you can in most cases just do a 'default' case too, not sure how that works with pattern matching)
- lmm 11y agoThis is why functional programmers get so grouchy - it's great that Swift has picked up this kind of functionality, but ML family languages have had it for 40 years now :/. Worse is when a feature gets dismissed as "incomprehensible" for years until it gets picked up by some fashionable language and then suddenly everyone's excited about it.
- jnbiche 11y agoPattern matching is awesome, and ML definitely gets the credit here for putting it into practice, but matching is arguably not even part of the functional paradigm. Lots of non-functional languages could greatly benefit from pattern matching and sum types. And indeed, when you look at a language like Nim and Swift, that's exactly what happened. Rust, too, although Rust is arguably a functional language.
- AnimalMuppet 11y agoRust is arguably a functional language? I'd love to hear the argument that says that it is...
- lmm 11y agoThe term "functional" today more-or-less means "a language like Haskell and the ML family" (I know this is not the original meaning), which Rust is.
- AnimalMuppet 11y agoI'm still trying to understand (not argue). How is Rust like Haskell and the ML family? My impression of Rust is that it's like C++ but with clear tracking of who owns what memory. [Edit: I see that the link from jnbiche is probably going to answer my question, though it's going to take me a while to digest it...]
- sanderjd 11y agoIt really depends on what you think are the features of Haskell that typify its "family". If you think it's typified by things like type inference, sum types, and typeclasses, then Rust will feel very Haskell-y, but if you think it's typified by higher kinded types, laziness, and functional purity, then it won't feel very Haskell-y at all. Personally, I think Haskell and C++ are the two biggest influences I feel when using Rust: it has a lot of Haskell's philosophy around data types and polymorphism, and a lot of C++'s philosophy around the life cycle of resources, and the cost of abstractions.
- AnimalMuppet 11y agoI think the life cycle of resources issue was a big reason why I was surprised to hear people say that Rust was Haskell-like.
- steveklabnik 11y ago
- Jtsummers 11y agoIn other languages with pattern matching you often have some sort of wildcard that you can match against. Either an explicitly named variable or, commonly, an underscore (`_`) if the value is unused. Example in erlang: foo(bar) -> baz; foo(X) -> bang. The X is an explicit variable that will match anything not already matched in previous patterns. It can also be used. (NB: Erlang pattern matching is the same or essentially the same for this discussion at each possible level, whether in a function declaration as above or a case statement or when receiving messages.) Here, we don't care about the thing we've named with an underscore: tail([_|Tail]) -> Tail. In Erlang, `[1|[2,3]]` is equivalent to the list `[1,2,3]`. So `[H|T]` as a pattern would have `H = 1` and `T = [2,3]`. Since we don't care about the head of the list, we name it with _. Optionally, in Erlang, you can give it a fuller name: tail([_Head|Tail]) -> Tail. The compiler knows that _Head is supposed to be unused and won't complain (warning about unused variables). tail([Head|Tail]) -> Tail. Would give a warning.
- akavi 11y ago> since compilers will check for exhaustiveness. That's not really required for pattern matching. Certainly makes it more useful, and most implementations have it, however.
- ffriend 11y agoTrue in Haskell, but not e.g. in Scala. In Scala, pattern matching is done using partial functions that are defined only for certain patterns. E.g. it's totally legal to write: obj match { case Foo(x) => ... case Bar(y, z) => ... // no Baz here }
- sampo 11y agoIn Scala you get the compiler to check for exhaustive pattern matching if you use case classes and sealed classes. So if you want exhaustiveness checks, you can have them.
- logicfiction 11y agoNot entirely true. You would get a warning if its not exhaustive matching for a sealed trait and you can make warnings into compiler errors with a compiler flag.