Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the theoretical loophole that allows F# (or any functional language) to apply a function mulitple times on the same input

In F# if I write

let p = printfn "something"

it will evaluate the expression once. Any subsequent references to p will evaluate to unit. From a theoretical perspective, the definition of a function, this makes sense. A function should only return the same result for the same input.

However if I want the side-effect to occur (i.e. the output to the screen), then I need the pass an argument to p. Typically this argument is the unit value.

let p () = printfn "something"

But why will F# evaluate the function each time, when the argument is the same each time the function is applied? Surely the same reasoning should apply as in the first case? The input to the function p doesn't change therefore there is no need to evaluate it more than once.

like image 424
sashang Avatar asked Nov 17 '21 00:11

sashang


1 Answers

The action printfn is, strictly speaking, not a function. It is, particularly, not a pure function, because it's not referentially transparent. This is possible because F# isn't a strictly functional language (it's a 'functional first' language). It makes no explicit distinction between pure functions and impure actions.

The return value of printfn "something" is () (unit), which means that p is bound to the unit value (). The fact that something is printed on the screen is a side effect of evaluating the expression.

F# is an eagerly evaluated language. That's why you see something printed on the screen as a side effect of binding printfn "something" to p. Once the expression is evaluated, p is only bound to () - the value.

F# doesn't memoize function calls, so when you change p to a function, it'll evaluate the function every time you call it with (). Since all functions can be impure, the compiler can't tell whether or not memoization would be appropriate, so it doesn't do that.

Other languages do this in different ways. Haskell, for example, is lazily evaluated, and also explicitly distinguishes between pure functions and impure actions, so it can apply different optimization in cases like these.

like image 166
Mark Seemann Avatar answered Sep 20 '22 05:09

Mark Seemann