views:

555

answers:

6

After touching on Monads in respect to functional programming, does the feature actually make a language pure, or is it just another "get out of jail free card" for reasoning of computer systems in the real world, outside of blackboard maths?

EDIT:

This is not flame bait as someone as said in this post, but a genuine question that I am hoping that someone can shoot me down with and say, proof, it is pure.

Also I am looking at the question with respect to other not so pure Functional Languages and some OO languages that use good design and comparing the purity. So far in my very limited world of FP, I have still not groked the Monads purity, you will be pleased to know however I do like the idea of immutability which is far more important in the purity stakes.

+3  A: 

I'm very new to functional programming, but here's how I understand it:

In haskell, you define a bunch of functions. These functions don't get executed. They might get evaluated.

There's one function in particular that gets evaluated. This is a constant function that produces a set of "actions." The actions include the evaluation of functions and performing of IO and other "real-world" stuff. You can have functions that create and pass around these actions and they will never be executed unless a function is evaluated with unsafePerformIO or they are returned by the main function.

So in short, a Haskell program is a function, composed of other functions, that returns an imperative program. The Haskell program itself is pure. Obviously, that imperative program itself can't be. Real-world computers are by definition impure.

There's a lot more to this question and a lot of it is a question of semantics (human, not programming language). Monads are also a bit more abstract than what I've described here. But I think this is a useful way of thinking about it in general.

Edward Amsden
The problem though is that a programming language that can't deal with input or output, but depends on mechanism to achieve its goals, in my opinion is trying to make out it is something it is not. By using a sand boxed monad for your input and output to me is nothing special and is as impure as any other language that I have dealt with. Errors can still creep in with the impure side of monads, such as network latency, file corruption, etc. Also how can Haskell regarded as being a complete language when you can not write device drivers with it because of its purity problem.
WeNeedAnswers
This question seems like irresponsible flame bait so why not just say the opposite -- that only Haskell can do IO, really; all the other languages are crippled? So for example: ["When we program in mainstream imperative languages, we rarely have the opportunity to venture beyond the limits of ArrowChoice. Haskell can be an excellent imperative language precisely because monads allow it to eliminate the boundary between compilation and execution, hence be strictly more expressive than our usual toolset."](http://just-bottom.blogspot.com/2010/04/programming-with-effects-story-so-far.html)
applicative
nice comment :)
WeNeedAnswers
flame bait? Heck no, I been racking my head over monads for some time now, trying to grok the purity side, but then it hit me, Haskell is not pure and the monads make the case that no language can be pure. The maths involved are pure, but the functionality and implementation will never be pure, and therefore the errors will creep in.
WeNeedAnswers
+2  A: 

I think of it like this: Programs have to do something with the outside world to be useful. What's happening (or should be happening) when you write code (in any language) is that you strive to write as much pure, side-effect-free code as possible and corral the IO into specific places.

What we have in Haskell is that you're pushed more in this direction of writing to tightly control effects. In the core and in many libraries there is an enormous amount of pure code as well. Haskell is really all about this. Monads in Haskell are useful for a lot of things. And one thing they've been used for is containment around code that deals with impurity.

This way of designing together with a language that greatly facilitates it has an overall effect of helping us to produce more reliable work, requiring less unit testing to be clear on how it behaves, and allowing more re-use through composition.

If I understand what you're saying correctly, I don't see this as a something fake or only in our minds, like a "get out of jail free card." The benefits here are very real.

dino
When I say "get out of jail free card", I refer to the fact that nothing can be truly pure when your dealing with physical machines that operate in an empirical manner based on logic gates. I think that saying something is pure is a misnomer, as the same technique can be used in any language that supports functions and function passing as first class features. Don't get me wrong I love Haskell but I think that the statement "pure" is just misleading.
WeNeedAnswers
Part of the problem here is that you're treating Haskell as a way of telling physical machines what to do. It isn't, although it of course can do that. Haskell expressions have a denotational semantics -- a meaning that exists independently of whether they're executed on a machine, or by hand. Because the definition of Haskell isn't tied to machine-concepts like memory addresses, this is straightforward to understand, while a denotational semantics for, say, C, is a tricky business.
sclv
@sclv, exactly, I think you have hit the mark spot on there, in the minds eye and in a mind experiment where we deal with the perfect nature of maths and logic then yes Haskell is very much a great meta language. But and here is my problem, Monads are the interface between the real world and the perfect world of Haskell. The Monad is defined in the language of Haskell and errors can occur at this point, the same as any other computer based programming language, where most errors do and will occur because of the impure nature of reality.
WeNeedAnswers
@sclv I think Monads are an important language construct and should be spread liberally all over computer languages, and all languages could implement them. The programmer would then have to be disciplined enough to trust the math :)
WeNeedAnswers
@WeNeedAnswers: I stress, monads are not important to purity. In early years before importance of monads was discovered, Haskell used request/response lists, not monads, and was as pure as it is now. See http://homepages.inf.ed.ac.uk/wadler/papers/imperative/imperative.ps. It's the compiler who is the "interface between the real world and the perfect world of Haskell" - the magic is in `main = ...` line, where a description of IO action is made into a running program. Try using Maybe, [], State, Reader, Writer to get a feeling of monads; IO is a bad example.
sdcvvc
"Pure functional programming" is established term for programming without side effects, it does NOT mean errors won't occur, or that software you are writing will be bugfree. If you use Either monad, it allows you to throw and catch exceptions - when performing `>>=` it checks if it's `Left` (exception) or `Right` (normal value). It's pure, despite the fact it closely resembles exceptions in imperative programming. IO monad does the same. Try adding `Throw` and `Catch` to language from my answer. You seem to have fairly strong opinion about monads, yet haven't grokked them fully - that's bad!
sdcvvc
@sdcwc, ok, now I am really confused. I have bought into the idea of functional programming, moved my thinking across a couple of degrees, thrown out the patterns in OO, moved over to proof theory and the various aspects of maths that are the equivalent to design patterns all in the name of error free, thread safe programming and now your telling me that no matter how you deal with IO, there is no escaping errors? This is turning a bit schrodingers cat and the observable problem. I'm off to get me some hammers and a spanner and create a turin complete machine out of spark plugs ;)
WeNeedAnswers
@sdcwc I am currently looking into F# and Haskell, doing a compare and contrast on the two. I really like F#, but before that I was liking Haskell a lot (would be doing lisp but those brackets hurt my eyes). I am an imperative programmer by trade, but I am honestly trying to move over to functional programming quickstep because I see a load of advantages in that style, also I never truely bought into the programming aspects of OO.
WeNeedAnswers
+20  A: 

Take the following mini-language:

data Action = Get (Char -> Action) | Put Char Action | End

Get f means: read a character c, and perform action f c.

Put c a means: write character c, and perform action a.

Here's a program that prints "xy", then asks for two letters and prints them in reverse order:

Put 'x' (Put 'y' (Get (\a -> Get (\b -> Put b (Put a End)))))

You can manipulate such programs. For example:

conditionally p = Get (\a -> if a == 'Y' then p else End)

This is has type Action -> Action - it takes a program and gives another program that asks for confirmation first. Here's another:

printString = foldr Put End

This has type String -> Action - it takes a string and returns a program that writes the string, like

Put 'h' (Put 'e' (Put 'l' (Put 'l' (Put 'o' End)))).

IO in Haskell works similarily. Although executing it requires performing side effects, you can build complex programs without executing them, in a pure way. You're computing on descriptions of programs (IO actions), and not actually performing them.

In language like C you can write a function void execute(Action a) that actually executed the program. In Haskell you specify that action by writing main = a. The compiler creates a program that executes the action, but you have no other way to execute an action (aside dirty tricks).

Obviously Get and Put are not only options, you can add many other API calls to the Action data type, like operating on files or concurrency.

I didn't say anything about monads. Action is not a monad. Monads are only some combinators, like conditionally above, and are not important to purity.

The essential thing about purity is referential transparency. In Haskell, if you have f x+f x you can replace that with 2*f x. In C, f(x)+f(x) in general is not the same as 2*f(x), since f could print something on the screen, or modify x. The advantages are mentioned here: basically it allows better optimalizations, since the compiler has much more freedom.

sdcvvc
Referential transparency, I like that. Sounds a lot more intelligent than such Nazi terms as Pure and Impure. With terms such as the latter I feel that I am starring as an extra in Harry Potter. great answer.
WeNeedAnswers
@Dario: I don't see that. Put could be typed `a -> m a -> m a`, and Get `(a -> m a) -> m a`. Neither reminds me of `>>=` and `return`. You can make a monad out of Action if you do `data Action a = End a | Get (Char -> Action a) | Put Char (Action a)`. Then `End` is return, and `>>=` can be defined recursively for all three constructors. Another way is to use GADT: `data IO a where Return :: a -> IO a; Bind :: IO a -> (a -> IO b) -> IO b; Get :: IO Char; Put :: Char -> IO ()`. Then `IO ()` is equivalent to `Action`, so it's rather a monad restricted to (), not `Char` s.
sdcvvc
[strictly speaking, using GADT does not give a proper monad, since it does not obey monad laws. You have to consider equivalent actions as equal.]
sdcvvc
+4  A: 

For an expanded version of sdcwc's sort of construction of IO, one can look at the IOSpec package on Hackage: http://hackage.haskell.org/package/IOSpec

sclv
+4  A: 

It is important to understand that there is nothing inherently special about monads – so they definitely don’t represent a “get out of jail” card in this regard. There is no compiler (or other) magic necessary to implement or use monads, they are defined in the purely functional environment of Haskell. In particular, sdcvvc has shown how to define monads in purely functional manner, without any recourses to implementation backdoors.

Konrad Rudolph
+1  A: 

What does it mean to reason about computer systems "outside of blackboard maths"? What kind of reasoning would that be? Dead reckoning?

Side-effects and pure functions are a matter of point of view. If we view a nominally side-effecting function as a function taking us from one state of the world to another, it's pure again.

We can make every side-effecting function pure by giving it a second argument, a world, and requiring that it pass us a new world when it is done. I don't know C++ at all anymore but say read has a signature like this:

vector<char> read(filepath_t)

In our new "pure style", we handle it like this:

pair<vector<char>, world_t> read(world_t, filepath_t)

This is in fact how every Haskell IO action works.

So now we've got a pure model of IO. Thank goodness. If we couldn't do that then maybe Lambda Calculus and Turing Machines are not equivalent formalisms and then we'd have some explaining to do. We're not quite done but the two problems left to us are easy:

  • What goes in the world_t structure? A description of every grain of sand, blade of grass, broken heart and golden sunset?

  • We have an informal rule that we use a world only once -- after every IO operation, we throw away the world we used with it. With all these worlds floating around, though, we are bound to get them mixed up.

The first problem is easy enough. As long as we do not allow inspection of the world, it turns out we needn't trouble ourselves about storing anything in it. We just need to ensure that a new world is not equal to any previous world (lest the compiler deviously optimize some world-producing operations away, like it sometimes does in C++). There are many ways to handle this.

As for the worlds getting mixed up, we'd like to hide the world passing inside a library so that there's no way to get at the worlds and thus no way to mix them up. Turns out, monads are a great way to hide a "side-channel" in a computation. Enter the IO monad.

Some time ago, a question like yours was asked on the Haskell mailing list and there I went in to the "side-channel" in more detail. Here's the Reddit thread (which links to my original email):

http://www.reddit.com/r/haskell/comments/8bhir/why_the_io_monad_isnt_a_dirty_hack/

Jason Dusek
@Jason, thanks. Yes I think that you have summed up Haskell by saying that it treats the world separate to itself, when itself is made up of the same stuff AS the world. There are going to be issues in this case as the world is not pure, and therefore the stuff that Haskell is made of is impure. If Haskell was just a reasoning tool that was a mind program only, like some of the stuff that Bertrand Russell did, I would let the comments go. But and here is my issue, Haskell, even when its doing its pure stuff still has to operate in the bounds of the IO logic of chips and registers.
WeNeedAnswers
Why are there going to be issues? Could you clarify the issues that you see?The "bounds" of chips and registers are no bounds at all.http://en.wikipedia.org/wiki/Church%E2%80%93Turing_thesis
Jason Dusek
@Jason, chips are built using physical techniques that depend on binary logic circuits to make things work. Haskell is an abstract removed from the domain in its purest sense, but the issue is computers work using this low level binary logic. The abstraction from the hardware helps a lot with defining and solving problems but at the end of the day the problem will get converted to an Imperative state machine. Monads basically solve this icky problem of making haskell useful. But this only enforces the premise that no language can be deemed pure when built on such technology. Nice wiki article.
WeNeedAnswers