15

Let's say I have this function: (Haskell syntax)

f x = (x,x)

What is the work (amount of calculation) performed by the function?

At first I thought it was obviously constant, but what if the type of x is not finite, meaning, x can take an arbitrary amount of memory? One would have to take into account the work done by copying x as well, right?

This led me to believe that the work done by the function is actually linear in the size of the input.

This isn't homework for itself, but came up when I had to define the work done by the function:

f x = [x]

Which has a similar issue, I believe.

sashkello
  • 17,306
  • 24
  • 81
  • 109
Guido
  • 2,571
  • 25
  • 37
  • good question for http://cs.stackexchange.com/ – FlavorScape May 31 '12 at 00:14
  • Should I move it? (Assuming I can, I'm not really familiar with the site) – Guido May 31 '12 at 00:25
  • 1
    @Guido You can't move it, although it's not possible to move it to the destination I think it fits, too. IMHO it's best to leave it here. – fuz May 31 '12 at 10:37
  • Note that an expression like `(x,x)` could trigger evaluation of `x` twice depending on whether the monomorphism restriction is in effect or not. E.g. see this recent blog post: [http://ics.p.lodz.pl/~stolarek/blog/2012/05/towards-understanding-haskells-monomorphism-restriction/](Towards understanding Haskell’s monomorphism restriction) – ErikR Jun 01 '12 at 17:25
  • @Guido Are you Guido Van Rossum? – thefourtheye Oct 03 '13 at 07:13

2 Answers2

33

Very informally, the work done depends on your language's operational semantics. Haskell, well, it's lazy, so you pay only constant factors to:

  • push pointers to x on the stack
  • allocate a heap cell for (,)
  • apply (,) to its arguments
  • return a pointer to the heap cell

Done. O(1) work, performed when the caller looks at the result of f.

Now, you will trigger further evaluation if you look inside the (,) -- and that work is dependent on the work to evaluate x itself. Since in Haskell the references to x are shared, you evaluate it only once.

So the work in Haskell is O(work of x) if you fully evaluate the result. Your function f only adds constant factors.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • 7
    To further clarify: `(,)` in Haskell is a *boxed* tuple, which means that it is a construct that merely holds pointers. If you have a language where `(,)` creates an *unboxed* tuple, then yes, it will take extra work to clone `x` to both slots, if `x` is larger than a pointer, and the amount of work scales with the size of `x`. [GHC provides unboxed tuples](http://www.haskell.org/ghc/docs/7.4.1/html/users_guide/primitives.html#unboxed-tuples) `(#,#)` with various limitations. – Dan Burton May 31 '12 at 18:14
1

Chris Okasaki has a wonderful method of determining the work charged to function call when some (or total) laziness is introduced. I believe it is in his paper on Purely Functional Data Structures. I know it is in the book version -- I read that part of the book last month. Basically you charge a constant factor for the promise/thunk created, charge nothing for evaluating any passed in promises/thunks (assume they've already been forced / are in normal form [not just WHNF]). That's an underestimate. If you want an overestimate charge also the cost of forcing / converting to normal form each promise / thunk created by the function. At least, that's how I remember it in my extremely tired state.

Look it up in Okasaki: http://www.westpoint.edu/eecs/SitePages/Chris%20Okasaki.aspx#thesis -- I swear the thesis used be be downloadable.

  • In Haskell having a polymorphic argument would seem to invalidate Okasaki's method. (1) Avoid if possible. (2) I haven't fully analyzed this, it's just an intuition. – Boyd Stephen Smith Jr. Jun 07 '12 at 04:21