-1
foo :: a -> b -> c
foo = undefined

ghci> :t foo
foo :: a -> b -> c

ghci> :t foo . foo
foo . foo :: a -> b -> c

ghci> :t foo . foo . foo
foo . foo . foo :: a -> b -> c

And so on.

The types are the same. We also clearly see, just by looking at the types, that all three are equivalent. They have the same result.

But what about speed? For instance, will foo composed to itself a million times with . automatically (without optimization) run just as fast as foo? If not, will optimization (O1 or O2) make it so?

ljedrz
  • 20,316
  • 4
  • 69
  • 97
haskellHQ
  • 1,027
  • 6
  • 15
  • 3
    Are you specifically asking about `a -> b -> c` where we can see from the type that the only possible result is non-termination? – sepp2k Oct 01 '16 at 15:49
  • 3
    Any number of foo composed together will run nearly instantaneously and fail. Perhaps rethink your question? What about `succ . succ`? – Rein Henrichs Oct 01 '16 at 16:03

1 Answers1

3

Yes, they perform the same, but only by accident. In ghci:

> :i .
(.) :: (b -> c) -> (a -> b) -> (a -> c)     -- Defined in ‘GHC.Base’
infixr 9 .

Since it is infixr, foo . foo . foo parses as foo . (foo . foo), and so we can shortcut writing foo . foo . foo . ... a billion times with foldr. So:

> let foo :: a -> b -> c; foo = undefined
> foldr (.) id (replicate 1000000000 foo) ()
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
  error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
  undefined, called at <interactive>:1:5 in interactive:Ghci1

It returns almost immediately. On the other hand, the following two hang for a while:

> foldl (.) id (replicate 1000000000 foo) ()
> foldr (flip (.)) id (replicate 1000000000 foo) ()

However, I must stress again that this is a coincidence of GHC's implementation, not a guarantee made by the semantics of the language. It so happens that GHC's implementation of imprecise exceptions quickly notices that this will be an error (and throw's the left-most foo's error) in the case of foldr (.), but doesn't notice as quickly for foldl (.). And of course in the case where foo is not just an error, but has to do some actual computation, that computation will need to be performed as many times as it appears in the string of compositions -- GHC is pretty amazing, but not magical.

Community
  • 1
  • 1
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • While this sort of performance is not guaranteed by the language semantics, an implementation would have to be pretty weird to give very different results. In particular, the non-spine-strict specification of `foldr` virtually requires spine laziness, while the non-accumulator-strict specification of `foldl` virtually requires spine-eagerness. – dfeuer Oct 01 '16 at 20:48
  • @dfeuer Indeed, but even so it is just an accident: my real point was that this is quick only because it's throwing an exception. Any real code would of course need to run all iterations of `foo`. – Daniel Wagner Oct 02 '16 at 01:00
  • a constant function would also be quick with `foldr` but not `foldl`. And I think you could probably construct slightly less trivial examples. – dfeuer Oct 02 '16 at 01:20