3 ms·
I've been looking for an example that shows how pattern matching can make code much both compact and readable. I think this does nicely. Here's an example in Sc
by andrewstellman 8y ago
I've been looking for an example that shows how pattern matching can make code much both compact and readable. I think this does nicely. Here's an example in Scala:
(1 to 100).map(i => (i % 3, i % 5) match {
case (0, 0) => "FizzBuzz"
case (0, _) => "Fizz"
case (_, 0) => "Buzz"
case _ => s"$i"
}).foreach(println)
Compare that to the rest of the examples on the page. The only one that comes close in either readability or compactness (in my opinion) is the Rust example, mainly because it's syntactically almost identical, just a bit more verbose. I'm really excited that C# 8 will support similar syntax with _ discards.
- chucky 8y agoThe Haskell example is quite similar to the Scala/Rust example as well. I guess you could rewrite the Haskell version to match your Scala version pretty closely as well if you prefer doing the tuple construction and then matching on the tuple, like in your Scala version, instead of doing it directly inside the pattern match. Something like this, with the caveat that I haven't done any proper coding in Haskell in years and I don't have an interpreter installed to verify correctness. fb :: (Integer, Integer) -> String fb (mod3, mod5) | (0, 0) = "FizzBuzz" | (0, _) = "Fizz" | (_, 0) = "Buzz" | otherwise = show n main = putStrLn $ unlines $ map fb $ map (\x -> (x % 3, x % 5)) [1..100]
- steveklabnik 8y agoYou can do it like that in rust too, so I assume it works in Haskell.
- johnday 8y agoNot exactly write - you would use have to use either view patterns or a case...of statement rather than guards here. But the general gist is right.
- ngcazz 8y agoThe step in the composition chain breaking down the integers into ordered pairs of the remainders is classy. Well done. For added points we now need an ADT to represent the different possible results of the computation :)
- thibautg 8y agoFizzBuzz in Coconut looks similar: def fizzbuzz(n): case (n % 3, n % 5): match (0, 0): return "FizzBuzz" match (0, _): return "Fizz" match (_, 0): return "Buzz" else: return n |> str ( range(1, 100) |> map$(fizzbuzz) |> x -> '\n'.join(x) |> print )
- deleted 8y ago[deleted]
- war1025 8y agoMaybe I'm missing something, but how is that any more readable than: for val in xrange(1, 100): if val % 15 == 0: print "FizzBuzz" elif val % 5 == 0: print "Buzz" elif val % 3 == 0: print "Fizz" else: print val
- deleted 8y ago[deleted]
- setr 8y agoWell for one, the cases of a match statement are guaranteed to use the same variables and return the same output, in every case — the equivalent if-elseif-else does not offer the same guarantees, and has to be read in full to reach the same conclusion So the statements are equivalent in whole, but the difference is that a match statement requires less reading (as it holds more meaning)
- pmontra 8y agoI agree with you. Pattern matching is detrimental to readability in this case. Everybody can understand your example above. The pattern matching one leaves me a little O_o (pun intended) despite I'm using pattern matching everyday in Elixir. Sometimes boring code beats clever code.
- Someone 8y agoI think that’s more a matter of familiarity than of readability proper. The same argument could have been made against Arabic numerals, against the use of “=“ for assignment and “==“ for equality (“=“ had been used in mathematics for equality for ¿centuries?), etc. One could also argue that, by using the knowledge that “is divisible by 3 and 5” is equivalent to “is divisible by 15”, the code using %15 is cleverer than the example that just follows the problem description.
- andrewstellman 8y agoWell, yes, you're right – like beauty, readability is in the eye of the beholder. I've been writing a ton of Scala code lately, and my version is very Scala-like, so it looks better to me. That said, I think there are several advantages: • It's more compact, with fewer lines and fewer characters, but at least as readable • There's simple separation of concerns: it only has a single print statement – the whole range is transformed, then printed – which makes it easy to refactor later if I need to use those values for something other than printing • It's obvious that all of the cases are handled • In an amazing coincidence, it looks like code I write :) so I personally prefer it If you're implying that there are times that if/elif/else syntax is more readable than pattern matching, then yes, absolutely! There are times when one is preferable, and times when the other is. Pattern matching is especially nice when you need to include conditionals and types: vehicle match { case car: Car if (car.passengers > 2) => addToHovLane(car) case truck: Truck => reject("No trucks allowed") case _ => totalTraffic += 1 } There's a lot of casting going on there, and it's all handled in the pattern match. Converting this to if/else statements would require a lot of type tests and intermediate variables. Personally, I think that would make things harder to read. That said, sometimes (often!) the more compact code is, the harder it is to read. I think it's always worth taking a few extra characters to improver readability. So I only use pattern matching with types and discards when I think it makes the code more readable and more flexible (e.g. easier to refactor, improves separation of concerns). BTW, thanks for pushing back on this. I'm working on the 4th edition of Head First C# (O'Reilly), and C# added syntax very similar to this (borrowed from F#). Writing this reply gave me the opportunity to start thinking through how I want to teach it.