[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