Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is point free code more efficient, or just terser?

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?

like image 250
nont Avatar asked Jun 27 '11 14:06

nont


2 Answers

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 maps:

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.

like image 143
C. A. McCann Avatar answered Nov 15 '22 06:11

C. A. McCann


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).

like image 20
deontologician Avatar answered Nov 15 '22 07:11

deontologician