4 Replies - 1566 Views - Last Post: 08 November 2014 - 06:36 PM

Why is functional purity desirable?

Posted 26 April 2014 - 01:49 PM

I've only ever used procedural languages before, but functional languages seem intriguing. Functional programming seems like a powerful paradigm, but I haven't been able to see the point of "pure" functional languages like Haskell. I know that in pure functional languages, no side effects or mutable state are allowed, and this seems kind of impractical and misguided to me. IO is inherently based on state and side effects, and so are simulations that have to evolve their state over time. Sure, you can have globally evolving simulations by passing global state in and out of functions, and you can implement a global loop with a recursive function. But for all intents and purposes, your code still has mutable state, it's just viewed through a slightly different lens. So why not just allow side effects in the first place?

I've also been reading about monads in Haskell. As I understand it ( and I understand it poorly, so please correct me if I'm wrong), monads allow you to easily represent state and side effects, and actually hide the functional implementation of those features, so you don't have to worry about explicitly passing global state into functions. That makes all the fuss about functional purity seem pretty silly. Why go to all the trouble of representing state in a functional way when, in the end, your're just hiding the functional nature of the program and have simply re-implemented side effects on top of a functional language?

Re: Why is functional purity desirable?

Posted 26 April 2014 - 02:26 PM

Functional purity is required for laziness. If you have unrestricted side-effects in a lazy language, it will be very hard to make any program do what you want and your programs would be very prone to bugs. That's why Haskell needs to enforce functional purity.

Enforced purity also enables the compiler to optimize more liberally, but I'd say that's a secondary concern. And of course pure functions are more easily reasoned about and forcing every function to be pure prevents a situation where you think a function is pure when in reality it's not¹.

Note that most strict languages do not enforce functional purity and simply encourage you to avoid side-effects where possible. Without laziness that tends to work perfectly fine.

¹ That said that does not appear to be a big problem in practice. As some whose programmed a lot in impure functional programming language, it's usually pretty clear when a function is pure and when it is not (as long as you're only using functions from the standard library or written by people who stick to functional programming practice).

Re: Why is functional purity desirable?

Purity makes it easier to reason about code. It reduces complexity and the surface or "reach" of a program.

Imagine you want to create a function that returns the sum of 2 integers. You would define it's type signature something like this:

int add(int a, int B)/>/>/>/>/>/>/>/>

Now, your boss comes and tells you to check that this piece of code does its job correctly. What do you check?

If your programming language is impure, you have to verify all of these properties:1)The result of calling this function with two arguments, is actually the sum of those two arguments: This is the basic requirement of this function. This is the requirement you would create a unit test for.2)Parameter "a" is never changed once this function is called: When you add two numbers, you expect the original ones to remain unchanged.3)Parameter "b" is never changed once this function is called: Same as before, you don't expect "b" to change once you call "add( a,b )"4)When this function executes, no global variables are modified: Imagine that when you call this function, you have a bunch of global variables set in place. Like maybe an intermediate result you are processing, or maybe some configuration settings that are stored in a singleton object/element, etc. You don't expect any of these to be modified when just adding two numbers.5)When this function executes, no I/O operations are made: This function should just add two numbers, not do anything else. Would you expect this function to open a socket and access the network? Would you expect this function to read input from the user? Would you expect this function to access the file system for whatever reason? No you wouldn't.

Why would you want to check any of the above (except 1)? Well, imagine you discover there's something wrong with your (bigger) program. How would you try to find where the bug is? One possibility is verifying that the program does as intended, by reading it line by line, and verifying any function it calls. So you try verifying your program, and arrive at a "int res = add( a,b );" line, so you try to verify whether the bug is caused by this function call or not.
If the bug you found had something to do with an I/O problem, then you basically have to check whether (5) holds. If the bug you found had something to do with the overall result of the program, not only do you have to check whether (1), holds, but you also have to check whether (2),(3), and (4) holds, because all three of those affect how your program works too.
So all of those properties are important, your "add" function should have all of those.

If you are working with an impure programming language, you have to manually verify all of them. You have to write tests that verify them, and even at times it may be impossible to write such tests (how would you go writing a unit test that verifies property (5) for instance?).

However, if your programming language was pure, properties (2), (3), (4) and (5) ALWAYS hold true. You just saved yourself a headache, since you know "add" can be the source of your bug, ONLY when property (1) fails. And property (1) is VERY easy to test (just fire up a bunch of unit tests and you are ready to go).

The language being pure, made it easier to reason about this function. In this particular case, it made it easier to reason whether that function was the source of the bug or not. If your program is made of a bunch of calls to many similar functions, then a pure programming language makes it easier to reason about your whole program.
It also reduced complexity, because now, every single function only affects the result of it, based on the arguments you pass it. It's simpler, since it doesn't do anything else, and you are 100% sure it doesn't.
When you allow side-effects, the complexity holds, because you can never be 100% sure that a specific function you create does not have an unintended side-effect, so you still have to work with the possibility of those happening in mind. With a pure language, that doubt fully disappears.

Also, because those properties above always hold true, your code would, in the long run, have less and less bugs than the impure language counterpart.

Quote

But for all intents and purposes, your code still has mutable state, it's just viewed through a slightly different lens. So why not just allow side effects in the first place?

The only code that has mutable state is the code that interacts with IO. Every other code does not.

For example, if you are developing a specific library with a certain functionality, you may not need IO at all. For instance, you try developing a library that allows the user to use an efficient data structure to store data.
In that case, adding side-effects to the language would hinder that developer.

Quote

That makes all the fuss about functional purity seem pretty silly. Why go to all the trouble of representing state in a functional way when, in the end, your're just hiding the functional nature of the program and have simply re-implemented side effects on top of a functional language?

Because in those cases you make state explicit, and you can actually work with it.

For example, in Haskell, you have the "State" monad. This monad allows you to work with computations that have access to a "global" variable which you can retrieve and update. However, the only place where this global variable can be modified is in functions that have the "State" type in their signatures. If you have another function that does NOT have that type in their signatures, or has that type, but it works with a different variable (that has a different type), you can be 100% sure that your function will not touch nor modify that "global" variable at all. With a normal impure programming language, you can't be certain of that at all.
So even in places where you have to encode state in your program, pure programming languages still provide benefits, because said state or side-effects are explicit, so it's easier to reason about them, and easier to use, and make it much harder to fuck things up.