I wrote the following code, which takes a bunch of points and draws them on the screen using the gloss library.
let s = blocks pes
pts = map (map mkPt) s {- stitches to points-}
lines = map Line pts {-points to lines -}
pict = Pictures lines {- lines to a picture -}
in do displayInWindow "My Window" (200, 200) (10, 10) white pict
It works fine, but it occurs to me that there's a repeated pattern: a chain of function calls, the result of each one feeding into the last argument of the next. So I refactored by removing the intermediate variables, reversing the order and chaining the functions with function composition (".") like so:
let pict = Pictures . (map Line) . (map $ map $ mkPt) . blocks $ pes
in do displayInWindow "My Window" (200, 200) (10, 10) white pict
Happily, this works just fine too. But I'm wondering if I'm straining readability, or if I'm just not used to reading & writing point free style code. Also, how do I reason about this code? Is the second version more efficient, or just terser? Is there anything I can do stylistically to make it clearer?
A few quick suggestions:
let pict = Pictures . (map Line) . (map $ map $ mkPt) . blocks $ pes
in do displayInWindow "My Window" (200, 200) (10, 10) white pict
You've got some superfluous stuff that can be removed outright:
let pict = Pictures . map Line . (map $ map mkPt) . blocks $ pes
in do displayInWindow "My Window" (200, 200) (10, 10) white pict
You're not avoiding parentheses anyway with the map (map mkPt)
term, so get rid of the $
:
let pict = Pictures . map Line . map (map mkPt) . blocks $ pes
in do displayInWindow "My Window" (200, 200) (10, 10) white pict
You can write the composition chain on multiple lines for clarity:
let pict = Pictures
. map Line
. map (map mkPt)
. blocks $ pes
in do displayInWindow "My Window" (200, 200) (10, 10) white pict
The do
block is superfluous because it only has one statement, and you can move the final application outside the definition:
let displayPict = displayInWindow "My Window" (200, 200) (10, 10) white
. Pictures
. map Line
. map (map mkPt)
. blocks
in displayPict pes
You can merge the two map
s:
let displayPict = displayInWindow "My Window" (200, 200) (10, 10) white
. Pictures
. map (Line . map mkPt)
. blocks
in displayPict pes
Sometimes it's also more readable for long chains to use the reversed composition operator from Control.Arrow
:
let displayPict = blocks
>>> map (Line . map mkPt)
>>> Pictures
>>> displayInWindow "My Window" (200, 200) (10, 10) white
in displayPict pes
But all of that is optional; season your code to taste.
On the subject of efficiency, I see no reason to think the two would be any different, once GHC's optimizer is through with the code.
Point free style can be good to very clearly show that a function is simply a series of transformations on an input. It's really easy to read something like:
foo = map show . doThis . doThat . etc
because it looks like typical point-free code, so someone familiar with it will be able to see exactly what's important, with no noise. Compare this to:
foo x = d
where
a = etc x
c = doThis b
b = doThat a
d = map show c
Obviously, this is a little contrived, but the idea is that the extra definitions need to be read and closely paid attention to in order to understand what foo
is really doing. Is a
used as an argument to more than one helper function? What about b
? Did the data flow through these helper functions in a way that was unexpected? Point free style in this case is saying "Things are just being transformed in a pipeline, there's no weirdness you need to think about"
In other cases, point-free style can really obfuscate things. Generally that's when you would break out the lets and wheres. So, terseness isn't really the only goal, reducing mental load is important as well. (As others have commented, point free style shouldn't have a performance difference in optimized code).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With