[Haskell-cafe] Readable Haskell
MarLinn
monkleyon at gmail.com
Sun Sep 20 18:37:28 UTC 2020
> main :: IO ()
> main = Q.getContents -- raw bytes
> & AS.parsed lineParser -- stream of parsed `Maybe Int`s; blank lines are `Nothing`
> & void -- drop any unparsed nonsense at the end -- [1]
> & S.split Nothing -- split on blank lines
> & S.maps S.concat -- keep `Just x` values in the sub-streams (cp. catMaybes)
> & S.mapped S.sum -- sum each substream
> & S.print -- stream results to stdout
There's still quite a bit that can be improved here.
First of all: comments are good. But whenever you write a comment, ask
yourself "could I choose a better name instead?" Make the code
self-documenting at all usage sites by choosing a good name once. It's a
good idea for every language, but this piece of code is a good example
of how to apply it. So, first step:
main ∷ IO ()
main = Q.getContents
& parseLines
& dropUnparsed -- Add the explanation/link definition side
& splitOnBlankLines
& catMaybes' -- why write "it's catMaybes", when you could just write "catMaybes"?
& S.mapped S.sum
& S.print
Do you need more functions this way to store the names? Yes. Which is a
good thing, because they might be reusable. Of course there's a limit;
if every function fits in half a line, you've probably gone too far.
Second step: Thinking from left to right is a remainder from thinking
imperatively. If you just turn around the top level of the code, the
reader is forced to a game of ping pong while reading, so it can even
make it harder to understand. So let's get rid of that (&) crowbar.
main ∷ IO ()main = S.print . S.mapped S.sum . catMaybes' -- by
the way, there's probablya better name for what it's actually doing.
"dropBlankLines", maybe? . splitOnBlankLines .
dropUnparsed . parseLines $ Q.getContents
There's more reasons why going against Haskell's natural grain is a bad
idea, and you provided the perfect hook to talk about it:
> (|>): a -> (a -> b) -> b
> (|.>): (a -> b) -> (b -> c) -> c
> (|$>) f a -> (a -> b) -> f b
> (|*>) f a -> f (a -> b) -> f b
Operators have the inherent problem that there aren't many symbols to
choose from. That means that almost all operators are overloaded. For
example, (|>) will be confused with the one from Data.Sequence. Yes,
there's unicode. I love unicode (see that sneaky little "∷" up there?)
so I've tried using Unicode in operators before, but one single person
using the project had their device set to a C locale and so my whole
library was search-and-replaced. It's still 1968 out there, so there's
like 10 symbols to choose from.
And even if you could use more symbols, operators still have the
inherent problem that they can't contain any letters. What exactly does
(>>?$>) mean? Or (|>||>)? Or (<<*>@)?
So why not rely on the operators that are widely used instead of
crowbaring new ones in just so that we can keep programming in C.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20200920/0c3599b6/attachment.html>
More information about the Haskell-Cafe
mailing list