5

I'm trying to understand the theorem behind "call-by-need." I do understand the definition, but I'm a bit confused. I would like to see a simple example which shows how call-by-need works.

After reading some previous threads, I found out that Haskell uses this kind of evaluation. Are there any other programming languages which support this feature?

I read about the call-by-name of Scala, and I do understand that call-by-name and call-by-need are similar but different by the fact that call-by-need will keep the evaluated value. But I really would love to see a real-life example (it does not have to be in Haskell), which shows call-by-need.

4castle
  • 28,713
  • 8
  • 60
  • 94
vesii
  • 1,945
  • 8
  • 27
  • 1
    I can think of two possibilities: one is I can show you a reduction trace in a call-by-need language, say, working with an infinite list. This will require some familiarity with ML-style (Haskell) syntax, which is not easy to read without some experience. Another is that I can show you an implementation of call-by-need in a normal imperative language and how it might be used there, which can be instructive. Which would you like? – luqui Jan 18 '19 at 21:49
  • @luqui I do have experience with `SML` but not with `Haskell`. Also I don't afraid to learn the syntax of a PL. I would like to see a real PL which supports this feature please :) – vesii Jan 18 '19 at 21:54
  • Do you mean something like `print (take 5 (map (* 2) [1 ..]))`? – melpomene Jan 18 '19 at 22:27
  • @melpomene Maybe you can show me an example that shows the difference between `call-by-need` and `call-be-value`. Something like the example between `call-by-name` and `call-by-value` in Scala as was shown in the following thread: https://stackoverflow.com/questions/13337338/call-by-name-vs-call-by-value-in-scala-clarification-needed – vesii Jan 18 '19 at 23:32
  • @vesii Yes, that's exactly what my comment shows. – melpomene Jan 18 '19 at 23:54
  • 1
    This question appears to be off-topic for Stack Overflow as defined in the [help], because it's a general theoretic question, rather than a specific programming problem. It may be better suited to the [cs.se] site; see [Which computer science / programming Stack Exchange sites do I post on?](//meta.stackexchange.com/q/129598). – Toby Speight Jan 30 '19 at 11:59

2 Answers2

4

The function

say_hello numbers = putStrLn "Hello!"

ignores its numbers argument. Under call-by-value semantics, even though an argument is ignored, the parameter at the function call site may need to be evaluated, perhaps because of side effects that the rest of the program depends on.

In Haskell, we might call say_hello as

say_hello [1..]

where [1..] is the infinite list of naturals. Under call-by-value semantics, the CPU would run off trying to build an infinite list and never get to the say_hello at all!

Haskell merely outputs

$ runghc cbn.hs
Hello!

For less dramatic examples, the first ten natural numbers are

ghci> take 10 [1..]
[1,2,3,4,5,6,7,8,9,10]

The first ten odds are

ghci> take 10 $ filter odd [1..]
[1,3,5,7,9,11,13,15,17,19]

Under call-by-need semantics, each value — even a conceptually infinite one as in the examples above — is evaluated only to the extent required and no more.

Greg Bacon
  • 121,231
  • 29
  • 179
  • 236
  • Great answer! Can you please add an example which shows that `call-by-need` will evaluate only once? – vesii Jan 18 '19 at 23:57
  • 2
    @vesii That's harder to prove in Haskell with just code because it requires side-effects, which requires using some unsafe code that is typically only used in low level libraries. A more readily observable way to prove that it only evaluates once is to observe the memory usage & speed of the program when a value is used more than once. – 4castle Jan 19 '19 at 01:38
  • 1
    Or `Debug.Trace`! – luqui Jan 19 '19 at 04:43
  • @vesii every code snippet in this answer would work under call-by-name, without call-by-need. – Will Ness Jan 19 '19 at 10:06
  • @WillNess Yes that's exactly the thing I tried to tell. It does not show the difference between call-by-need and call-by-name. Are there any other languages that support call-by-need? – vesii Jan 19 '19 at 13:07
  • @WillNess It is true that call-by-name could handle the `say_hello` example because it never evaluates `numbers`. `take` and `filter` applied to an infinite list do. That makes the slight difference that the function at least tries to execute, but the program still runs off into an infinite loop. – Greg Bacon Jan 19 '19 at 13:35
  • @GregBacon I'd expect the `[1..]` in any call-by-name language to be implemented by what is essentially a generator. it won't ever attempt to calculate the full extent of the "infinite" list on its own. all it does is produce *next* element when asked to. – Will Ness Jan 19 '19 at 16:46
1

update: A simple example, as asked for:

ff 0 = 1
ff 1 = 1
ff n = go (ff (n-1))
  where
  go x = x + x

Under call-by-name, each invocation of go evaluates ff (n-1) twice, each for each appearance of x in its definition (because + is strict in both arguments, i.e. demands the values of the both of them).

Under call-by-need, go's argument is evaluated at most once. Specifically, here, x's value is found out only once, and reused for the second appearance of x in the expression x + x. If it weren't needed, x wouldn't be evaluated at all, just as with call-by-name.

Under call-by-value, go's argument is always evaluated exactly once, prior to entering the function's body, even if it isn't used anywhere in the function's body.


Here's my understanding of it, in the context of Haskell.

According to Wikipedia, "call by need is a memoized variant of call by name where, if the function argument is evaluated, that value is stored for subsequent uses."

Call by name:

take 10 . filter even $ [1..]

With one consumer the produced value disappears after being produced so it might as well be call-by-name.

Call by need:

import qualified Data.List.Ordered as O

h = 1 : map (2*) h <> map (3*) h <> map (5*) h
    where
    (<>) = O.union

The difference is, here the h list is reused by several consumers, at different tempos, so it is essential that the produced values are remembered. In a call-by-name language there'd be much replication of computational effort here because the computational expression for h would be substituted at each of its occurrences, causing separate calculation for each. In a call-by-need--capable language like Haskell the results of computing the elements of h are shared between each reference to h.

Another example is, most any data defined by fix is only possible under call-by-need. With call-by-value the most we can have is the Y combinator.

See: Sharing vs. non-sharing fixed-point combinator and its linked entries and comments (among them, this, and its links, like Can fold be used to create infinite lists?).

Will Ness
  • 62,652
  • 8
  • 86
  • 167