Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to efficiently use the result of this calculation two times?

Tags:

haskell

I'm relatively new to Haskell and I'm currently working on my first project. I've written a function that returns something based on a call to another function, that calls this function again (so it's recursive).

function :: a -> a
function a
  | null (recursive call) = a
  | otherwise = head (same recursive call)

How can I only make the recursive call once and use the result twice (as a condition and as part of the 'return value')?

like image 832
Sacha Avatar asked Oct 15 '25 01:10

Sacha


2 Answers

function a
  | null recCall  = a
  | otherwise     = head recCall
 where recCall = recursive call

But this is not a good approach. Avoid using head, tail, !!. Generally there's something called “boolean blindness”: using boolean values means the program basically forgets the meaning of what you're basing your logic on, which means you need to re-introduce it, which makes your programs both more verbose and less reliable.

Better just use pattern matching on the recursion-result right away:

function a = case recursive call of
   [] -> a
   (h:_) -> h

This can also written with pattern guard syntax

function a
  | (h:_) <- recursive call  = h
  | otherwise                = a
like image 112
leftaroundabout Avatar answered Oct 18 '25 05:10

leftaroundabout


While the formulations suggested by @leftroundabout are almost certainly superior programming style, I'd point out that you'd be hard-pressed to find an example where GHC didn't recognize the repeated calls and lift them to a single call, so from the standpoint of code efficiency -- as opposed to good programming style -- there's usually not much point in trying to eliminate this kind of duplication.

This can be important in other contexts, if you find yourself trying to choose between a straightforward way of writing something that involves some unnecessary duplication versus a really awkward or messy way that eliminates any duplication. Write whichever one makes for better source code, code that's easier to read and maintain and understand, and trust that the compiler will avoid unnecessary duplicate calls.

For example, there are far too many people -- myself included -- who are tempted to rewrite a tree-searching function:

data Tree a = Leaf | Node a (Tree a) (Tree a)

find1 :: (Ord a) => a -> Tree a -> Maybe a
find1 x (Leaf) = Nothing
find1 x (Node y l r) = case compare x y of
  LT -> find1 x l
  GT -> find1 x r
  EQ -> Just y

with a helper to avoid unnecessarily passing the fixed parameter x:

find2 :: (Ord a) => a -> Tree a -> Maybe a
find2 x = go
  where go Leaf = Nothing
        go (Node y l r) = case compare x y of
          LT -> find2 x l
          GT -> find2 x r
          EQ -> Just y

If you think find2 is clearer than find1, by all means write it that way. If you think you're writing more efficient code, think again. Compile with:

ghc -O2 -fforce-recomp -ddump-simpl -dsuppress-all -dsuppress-uniques Tree.hs

if you'd like to check for yourself that the core generated for find2 is:

find2 = find1

The same holds for your example. I'm hard-pressed to come up with something realistic, but consider your version and @leftroundabout's versions of the following rather pointless function:

foo1 :: a -> a
foo1 a | null (foo1 [a]) = a
       | otherwise = head (foo1 [a])

foo2 :: a -> a
foo2 a = case foo2 [a] of
  [] -> a
  (h:_) -> h

foo3 :: a -> a
foo3 a | (h:_) <- foo3 [a] = h
       | otherwise         = a

Would it surprise you that the core generated for foo2 and foo3 is the following?

foo2 = foo1
foo3 = foo1
like image 36
K. A. Buhr Avatar answered Oct 18 '25 05:10

K. A. Buhr