5 ms·
> such as map, fold, filter, pattern matching, control of side-effects, etc. map and filter have become natural parts of my thinking, fold too is getting there
by sundarurfriend 11y ago
> such as map, fold, filter, pattern matching, control of side-effects, etc.
map and filter have become natural parts of my thinking, fold too is getting there, and avoidance of side-effects even gives pleasant tingles to the math-guy in me, but I've never understood what pattern matching brings to the table. In my admitted limited understanding, they seem to be a syntactic sugar for if-elseif branching that ends up scattering the logic of a function to multiple blocks of code, while bringing no obvious benefit compared to them.
Since you mentioned your success explaining the justifications for these things, I thought I'd take advantage of that here. :) Could you explain what I'm missing with pattern matching?
- kinghajj 11y agoPattern 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.
- 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.
- jarcane 11y agoPattern matching isn't just conditional matching. It's also binding, and even some common operations. Compare a naive Haskell map: map f [] = [] map f (x:xs) = f x : map f xs To a Racket one made without using (match...): (define (map f lst) (cond [(empty? lst) '()] [else (cons (f (car lst)) (map f (cdr lst)))])) The former is far more terse, and also more directly resembles a definition of what map actually does. The latter is wordy, because all kinds of things have to be stated in explicit terms, and operations need to be performed manually instead of with simple destructuring binds. This is a mild example too, it gets far, far more useful when you're dealing with type/struct fields and the like.
- abecedarius 11y agoThat wasn't quite a fair comparison. map f xs = if empty xs then [] else f (head xs) : map f (tail xs) (I like pattern matching too.)
- jarcane 11y agoThe point isn't to be a "fair" comparison, but rather to compare the number of explicit operations involved. I'd note that your Haskell example there is largely equivalent to the Scheme one.
- abecedarius 11y agoYes, it's equivalent on purpose. Yet in Haskell it's only a couple tokens longer than the pattern-matching original which you called "far more terse". To fairly test a claim, one should change only the variables the claim is about.
- jnbiche 11y agoJust curious why you would use that imperative example with Racket, and not an imperative language like Python, or even Haskell? You parenthetically mention (match) (ha!) but to someone who doesn't know the languages, give the impression that Racket can't do pattern matching. Anyway, this is the analogous `map` in racket: (define (my-map f lst) (match lst ['() '()] [(cons hd tl) (cons (f hd) (my-map f tl))])) It's a 1-to-1 correspondence, and is arguably the idiomatic way to define `map` in Racket.
- deleted 11y ago[deleted]
- mbucc 11y agoThe benefit I see is that (at least in Erlang) pattern matching combines pre-condition asserts with programming the sunny path. For example, "/" ++ X = HttpRequest:get(path). will crash if PATH_INFO doesn't include leading slash. Writing just the sunny path and getting asserts "for free" (without any clutter) is a fun way to program. I also find that statement very readable. BTW, here's some Java code that does the same thing: String Path; if (HttpRequest.getPathInfo().startsWith("/")) { Path = HttpRequest.getPathInfo().substring[1:]; } else { String emsg = "no match of right hand side value \"%s \""; throw new IllegalStateException(emsg.format(HttpRequest.getPathInfo()); }