17

Since the purity of an input parameter is an unknown until runtime, is a function immediately considered impure if it takes a function as an input parameter?

Related: if a function applies a pure function that is defined outside of the function, but is not passed in as a parameter, is it still pure if it fulfills the criteria of having no side effects and output depends solely on input?

For context, I'm writing functional code in JavaScript.

Daenyth
  • 8,077
  • 3
  • 31
  • 46
Dancrumb
  • 571
  • 5
  • 14
  • As a trivial counter example, consider: `foo = function(function bar){ print(bar.toString()) }` – David says Reinstate Monica Oct 23 '15 at 19:42
  • 1
    @DavidGrinberg That's not a counter example, I think, and actually highlights a bigger issue; if you have functions that can be overridden, and can't guarantee that implementations are side-effect free, then you can't guarantee that most functions that take objects and call their methods are pure either. Maybe bar's toString() deletes some files from disk? – Joshua Taylor Oct 23 '15 at 19:51
  • 3
    @DavidGrinberg But, I think you're thinking in a good direction. `foo = function(function bar) { return 3; }` *is* pure, and takes a function as an argument. – Joshua Taylor Oct 23 '15 at 19:52
  • @JoshuaTaylor Fair point, I didn't think of that. But you've already essentially solved the issue. As an alternative fix, just call the 'root' `toString()` (ie the one you would find on Java's Object). – David says Reinstate Monica Oct 23 '15 at 20:06

5 Answers5

22

As long as all values used in the function are defined solely by its parameters, it's a pure function.

The facet that output is the same each time for the same input is controlled by whether the parameters are pure. If you assume the parameters (like a function argument) are also pure, then it is pure.

In a language like Javascript where purity isn't enforced, this means that it's possible to make an otherwise pure function have impure behavior by invoking an impure function passed as a parameter.

This effectively means that for languages that don't enforce purity (ie almost all), it's impossible to define a pure function which invokes functions passed as arguments. It's still useful to write them as pure as possible, and to reason about them as pure functions, but you have to exercise caution because the assumption that it's pure will be broken if you pass in the wrong arguments.

In my experience in practice this isn't usually a big deal - I find it rare to have impure functions be used as function arguments to pure functions.

Daenyth
  • 8,077
  • 3
  • 31
  • 46
  • Regarding your statement that "As long as all values used in the function are defined solely by its parameters, it's a pure function". What happens in the case of constants? If I have a function `areaOfCircle r => Math.Pi * r * r`, will `areaOfCircle` non-pure as it doesn't just use parameters? – David Arno Oct 23 '15 at 15:56
  • 2
    @DavidArno That's a fair point. According to referential transparency, referring to a *static* outside value would be no different than having it hardcoded, so it would still be pure. – Daenyth Oct 23 '15 at 17:08
  • 1
    "this means that it's possible to make a pure function have impure behavior" - by definition, a pure function cannot have impure behavior. You're making the mistake of thinking a function `f(f2)` that invokes `f2` does not transitively rely on anything `f2` relies on. A function that might invoke arbitrary passed-in functions is not pure. – user2357112 Oct 23 '15 at 19:29
  • @user2357112 You're right, I was being loose with the wording - is this edit better? – Daenyth Oct 23 '15 at 19:48
  • 2
    @Daenyth: Better, but it still assumes that the function has to invoke the passed-in function. It might be something like `function compose(f, g) {return function h(x) {return f(g(x));};}`, which is pure despite taking functions as arguments. – user2357112 Oct 23 '15 at 19:50
  • @user2357112: Excellent point. Edited – Daenyth Oct 23 '15 at 19:52
  • "*I find it rare to have impure functions be used as function arguments.*" - unless you are writing something that takes callbacks. Like basically *any* asynchronous or event-related function in JS. And if you're calling those callbacks not only strictly in the end (usage like an "output parameter"), or have multiple of them, things get worse very quickly. – Bergi Oct 23 '15 at 22:54
  • @Bergi That's a case where it can come up, but using callbacks in general is a pain. Using a monadic future api is much nicer for a ton of reasons, purity among them. – Daenyth Oct 24 '15 at 03:11
  • Indeed, there's a reason why we all love promises so much :-) But those only work for "return values", not all kinds of callbacks. E.g. when implementing some kind of event emitter (or promise library), you have to care about all kinds of unexpected stuff. – Bergi Oct 24 '15 at 07:57
  • 1
    "I find it rare to have impure functions be used as function arguments to pure functions." -- not a functional language, but certain C++ library functions have specific warnings that predicate arguments must be (some approximation to) pure. So in one sense this means it's not only rare, it never validly happens. But in another sense the fact they have to forbid it is because people do sometimes *want* to do it. For example they want to pass a `find` routine an impure predicate that returns "true" for the third matching item it encounters, or some such nonsense. – Steve Jessop Oct 24 '15 at 10:43
19

Since the purity of an input parameter is an unknown until runtime, is a function immediately considered impure if it takes a function as an input parameter?

No. Counterexample:

function pure(other_function) {
    return 1;
}

It doesn't matter whether other_function is a pure function, an impure function, or not a function at all. The pure function is pure.

Other counterexample:

function identity(x) {
    return x;
}

This function is pure, even if x is an impure function. identity(impure_function) will always return impure_function, no matter how many times you repeat the call. It doesn't matter whether identity(impure_function)() always returns the same thing; a function's return value's return value doesn't affect its purity.


In general, if a function might call a function it was passed as an argument, it's not pure. For example, a function function call(f) {f();} isn't pure, because even though it makes no mention of any global or mutable state, f might be something like alert that causes visible side effects.

If a function takes functions as arguments, but it doesn't call them or cause them to be called, then it can be pure. It might still be impure if it does some other impure thing. For example, function f(ignored_function) {alert('This isn't pure.');} is impure, even though it never calls ignored_function.

user2357112
  • 761
  • 6
  • 11
  • 5
    This response seems overly pedantic. We can infer from the question that the concern is over the function parameter being invoked. The existence of functions that can/do take other functions as a parameter without invoking them does not affect this question. – walpen Oct 23 '15 at 18:03
  • 13
    @walpen: The question makes no mention of invoking the argument. There's no reason to assume the questioner even realized a function might take another function as input without invoking it. It's important to point out hidden assumptions like this, rather than just assuming you were meant to assume them. – user2357112 Oct 23 '15 at 19:24
12

Since the purity of an input parameter is an unknown until runtime, is a function immediately considered impure if it takes a function as an input parameter?

Technically, yes, unless there is some way in your language to guarantee that the input function is also pure.

if a function applies a pure function that is defined outside of the function, but is not passed in as a parameter, is it still pure if it fulfills the criteria of having no side effects and output depends solely on input?

Yes. So let's focus on what matters here. Calling a function pure or not isn't in and of itself useful. Pure functions are useful because producing the same output for any input and not depending on state or having side effects is a very useful set of properties. It means that once your function has been run, you can "remember" the answer for that input and it will always be true. You also don't need to run the function again to generate side effects. And you can run that function in parallel (or out of order) with other functions and know that they won't have any hidden interactions that behave badly.

Those useful properties still hold if the function uses other pure read-only functions to do its work, regardless of how it references them.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
6

As Telastyn said: Technically, yes, unless there is some way in your language to guarantee that the input function is also pure.

That's not hypothetical, there are indeed good ways to guarantee this. At least in a strongly typed language.

Such a pure~ function you'd write in JavaScript as

function foo(f) {
   return f(1) + 2;
}

can be translated directly to Haskell:

foo :: (Int -> Int) -> Int
foo f = f 1 + 2

Now, in JavaScript you can do evil stuff like

js> foo (function(x) {console.log("muharhar"); return 0})
muharhar
2

This is not possible in Haskell. The reason being, something side-effect-ful like console.log() must always have a result type IO something, not just something alone.

GHCi> foo (\x -> print "muarhar" >> return 0)

<interactive>:7:12:
    Couldn't match expected type ‘Int’ with actual type ‘IO b0’
    In the expression: print "muarhar" >> return 0
    In the first argument of ‘foo’, namely
      ‘(\ x -> print "muarhar" >> return 0)’
    In the expression: foo (\ x -> print "muarhar" >> return 0)

For this expression to typecheck, we would need to give foo the type signature

foo :: (Int -> IO Int) -> Int

But it turns out I cannot implement it anymore then: because the argument function has IO in its result, I can not use it within foo.

<interactive>:8:44:
    Couldn't match expected type ‘Int’ with actual type ‘IO Int’
    In the first argument of ‘(+)’, namely ‘f 1’
    In the expression: f 1 + 2

The only way I could use an IO action in foo is if the result of foo has type IO Int itself:

foo :: (Int -> IO Int) -> IO Int
foo f = do
   f1 <- f 1
   return (f1 + 2)

But at this point it's clear from the signature of foo that it is not a pure function, either.

leftaroundabout
  • 1,557
  • 11
  • 12
  • 1
    Before you say "impossible", have a look at `unsafeIO` :-) – Bergi Oct 23 '15 at 22:57
  • 2
    @Bergi: that's actually not part of Haskell, but of its foreign function interface: to allow asserting that a function defined in some _other_ language is pure, which the Haskell compiler obviously can't infer from the type signature because other languages generally have no such thing as `IO`. Incidentally, it can also be used to cause mayhem by hiding side-effects in a “pure” function, but that's _really_ unsafe in Haskell because there's not really a reliable way to specify the evaluation order of pure functions. – leftaroundabout Oct 24 '15 at 07:31
  • Yeah, that's true. However, I think I've seen it being used to "hide" *beneficial* side effects in a "pure" function as well, where the safety of the approach had to be established before. – Bergi Oct 24 '15 at 07:52
  • @Bergi You mostly shouldn't be using `unsafeIO`; that's a last-resort escape hatch that sidesteps the type system guarantees, and therefore yours is not a good point. – Andres F. Mar 03 '16 at 13:50
0

No it is not.

If the passed function is impure AND your function calls the passed function then your function will be considered impure.

The pure/impure relation is a bit like sync/async in JS. You can freely use pure code from impure, but not the other way around.

Jencel
  • 217
  • 1
  • 9
  • This answer doesn't add anything that [wasn't already explained in this one](http://programmers.stackexchange.com/a/300711/16247)... Please review prior answers before restating things :) – Andres F. Mar 03 '16 at 13:52
  • What about the sync/async analogy? – Jencel Mar 03 '16 at 15:14