39

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 has 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.

WeNeedAnswers
  • 4,620
  • 2
  • 32
  • 47
  • 5
    Your comments below suggest that you've already answered your own question, and just want someone to affirm your conclusions. – Phil Miller Jun 25 '10 at 14:46
  • After reading all your comments, it appears you're saying that since Haskell is compiled to programs that run on physical machines, it can't be pure... This is inane and have absolutely nothing to do with monads, IO or even purity as defined by all computer scientists. If this is your opinion, there is no way to "prove" anything to the contrary, and no language can ever be pure by your twisted definition, even Lisp Machines were physical and thus exposed to external failure modes... – Jedai Jun 05 '12 at 18:54
  • Has nothing to do with monads, I agree, but has a whole lot to do with the claims made by Haskell of assigning itself pure through the associating of mathematical ideology. IO is not pure in the real world, never has been, never will. If I cut the telephone line, even if I was communicating via a Monad structure, the conversation will not take place. Thus Monads can not stop this from occurring or prevent it from happening, or even correct the event to make the input/output "pure". Monads will not correct http errors, API call errors or any other IO that deals with systems outside itself. – WeNeedAnswers Jun 05 '12 at 22:57
  • @Jedai, you do realise that for all your efforts in Haskell, the code gets converted to the icky nasty horrible world of machine code with all that nasty horrible bit shifting, register invoking, stateful nature that actual computers use? reality sucks. :) – WeNeedAnswers Jun 05 '12 at 23:05
  • 2
    @WeNeedAnswers The fact that all languages are ultimately translated to binary instruction to the CPU is fortunately irrelevant to the discussion of the respective merits of programming languages, or there would be no reason to discuss : despite that most sane programmers agree that developing a software in Python or in Assembler are different experiences... It seems you still don't realize that saying a language is "pure" has nothing to do with how it is executed in the end : see [Wikipedia](http://en.wikipedia.org/wiki/Purely_functional) for clarification. – Jedai Jun 06 '12 at 13:32
  • 1
    By the way, bit shifting, registers and so on are nothing nasty, and machine code isn't "icky" to most Haskeller, I would also like to know what you may mean by "mathematical ideology" : there's no such thing to the best of my knowledge. You seem resentful of the "claims" of Haskell to be purely functional, but that's just objective truth... whether you judge that a decisive advantage or even a positive point in favor of Haskell as a programming language is another question entirely (but is a matter of opinion and so out-of-topic for StackOverflow). – Jedai Jun 06 '12 at 13:48

8 Answers8

81

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 IO data type, like operating on files or concurrency.

Adding a result value

Now consider the following data type.

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

The previous Action type is equivalent to IO (), i.e. an IO value which always returns "unit", comparable to "void".

This type is very similar to Haskell IO, only in Haskell IO is an abstract data type (you don't have access to the definition, only to some methods).

These are IO actions which can end with some result. A value like this:

Get (\x -> if x == 'A' then Put 'B' (End 3) else End 4)

has type IO Int and is corresponding to a C program:

int f() {
  char x;
  scanf("%c", &x);
  if (x == 'A') {
    printf("B");
    return 3;
  } else return 4;
}

Evaluation and execution

There's a difference between evaluating and executing. You can evaluate any Haskell expression, and get a value; for example, evaluate 2+2 :: Int into 4 :: Int. You can execute Haskell expressions only which have type IO a. This might have side-effects; executing Put 'a' (End 3) puts the letter a to screen. If you evaluate an IO value, like this:

if 2+2 == 4 then Put 'A' (End 0) else Put 'B' (End 2)

you get:

Put 'A' (End 0)

But there are no side-effects - you only performed an evaluation, which is harmless.

How would you translate

bool comp(char x) {
  char y;
  scanf("%c", &y);
  if (x > y) {       //Character comparison
    printf(">");
    return true;
  } else {
    printf("<");
    return false;
  }
}

into an IO value?

Fix some character, say 'v'. Now comp('v') is an IO action, which compares given character to 'v'. Similarly, comp('b') is an IO action, which compares given character to 'b'. In general, comp is a function which takes a character and returns an IO action.

As a programmer in C, you might argue that comp('b') is a boolean. In C, evaluation and execution are identical (i.e they mean the same thing, or happens simultaneously). Not in Haskell. comp('b') evaluates into some IO action, which after being executed gives a boolean. (Precisely, it evaluates into code block as above, only with 'b' substituted for x.)

comp :: Char -> IO Bool
comp x = Get (\y -> if x > y then Put '>' (End True) else Put '<' (End False))

Now, comp 'b' evaluates into Get (\y -> if 'b' > y then Put '>' (End True) else Put '<' (End False)).

It also makes sense mathematically. In C, int f() is a function. For a mathematician, this doesn't make sense - a function with no arguments? The point of functions is to take arguments. A function int f() should be equivalent to int f. It isn't, because functions in C blend mathematical functions and IO actions.

First class

These IO values are first-class. Just like you can have a list of lists of tuples of integers [[(0,2),(8,3)],[(2,8)]] you can build complex values with IO.

 (Get (\x -> Put (toUpper x) (End 0)), Get (\x -> Put (toLower x) (End 0)))
   :: (IO Int, IO Int)

A tuple of IO actions: first reads a character and prints it uppercase, second reads a character and returns it lowercase.

 Get (\x -> End (Put x (End 0))) :: IO (IO Int)

An IO value, which reads a character x and ends, returning an IO value which writes x to screen.

Haskell has special functions which allow easy manipulation of IO values. For example:

 sequence :: [IO a] -> IO [a]

which takes a list of IO actions, and returns an IO action which executes them in sequence.

Monads

Monads are some combinators (like conditionally above), which allow you to write programs more structurally. There's a function that composes of type

 IO a -> (a -> IO b) -> IO b

which given IO a, and a function a -> IO b, returns a value of type IO b. If you write first argument as a C function a f() and second argument as b g(a x) it returns a program for g(f(x)). Given above definition of Action / IO, you can write that function yourself.

Notice monads are not essential to purity - you can always write programs as I did above.

Purity

The essential thing about purity is referential transparency, and distinguishing between evaluation and execution.

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.

Thanks to purity, a compiler has much more freedom and can optimize better. It can rearrange computations, while in C it has to think if that changes meaning of the program.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
sdcvvc
  • 25,343
  • 4
  • 66
  • 102
  • 9
    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 Jun 26 '10 at 14:23
  • I'm confused at what you mean by "In Haskell, if you have `f x+f x` you can replace that with `2*f x`"... huh? If it performs I/O, it's not the same thing is it? – user541686 Jun 19 '11 at 15:58
  • 20
    @Mehrdad: If it performs IO, then it's type IO Int (or similar) and you can't add it with (+), since (+) takes numbers. You have to write "perform f x, perform f x, add results" as in `do a <- f x; b <- f x; return (a+b)`. There's a higher order function for that: `liftM2 (+) (f x) (f x)`. – sdcvvc Jun 19 '11 at 18:19
  • "Thanks to purity, a compiler has much more freedom and can optimize better. It can rearrange computations, while in C it has to think if that changes meaning of the program." But Haskell can't rearrange the pure actions inside an IO do block, so what's the difference regarding C? It seems that the separation between evaluation and execution in Haskell is just artificial, because in practice when the IO is "given" to main, the execution will follow the sequence of IO actions, just like in any other language. So no rearrangement of this part of the code is possible, because it's not pure. – mljrg Nov 19 '14 at 23:20
  • 3
    @mljrg: You cannot rearrange an IO action `putStr "hello" >> putStr "world"` the same way you cannot rearrange list `[1,2]` to `[2,1]`. However, if `[1,2]` or `putStr "hello" >> putStr "world"` is a result of a complex computation, you can optimize that computation, and the principles guiding those optimizations are the same, no matter if you are computing a list or an IO action. For example, `f x + f x` can be optimized to `2 * f x`, while in C you would have to know that `f` has no side effects. – sdcvvc Nov 23 '14 at 10:55
  • 2
    @mljrg: Laws in Haskell do not have to make provisions like "this function has no side effects", since the effects like printing are first-class values (`Action` in my answer), not side effects of calling functions. In other words, the reason is not "because it's not pure" but because "it changes the ordering in a non-commutative operation `>>`". – sdcvvc Nov 23 '14 at 10:56
  • @sdcvvc: Edited. Please verify if that is what you meant. – Nawaz Jul 23 '17 at 16:33
9

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.

Community
  • 1
  • 1
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
6

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/

solidsnack
  • 1,631
  • 16
  • 26
  • 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 Jun 26 '10 at 08:46
  • 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 – solidsnack Jun 26 '10 at 19:44
  • 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 Jun 27 '10 at 18:03
  • 2
    That's an issue you made up for yourself. Whether the environment in which a program described in a language runs is 'pure' or not is completely irrelevant to the question whether that language is pure or not. – Cubic Jun 06 '12 at 10:24
  • @Cubic, it is very relevant. Through translation of the declarative to the imperative, errors can and will occur. You can wrap IO in as much Monad goodness to your hearts content, at the end of the day, those actual real life bits will have to become real, electrons will flow. No amount of posturing is going to improve the situation of a pure "hypothetical" language interacting with the real world, to get better results than the old imperative style. When you get down to the bit level, it all turns a murky kind of grey goop based on engineering physical principals, not blackboard maths. – WeNeedAnswers Jun 06 '12 at 13:20
  • @Cubic, I challenge you to design a computer system (hardware) based on Alonzo Church maths. By the way, it will have to be Turin complete and not use anything that even remotely uses imperative techniques. That means that values in registers can never change when set. mwah ha ha! – WeNeedAnswers Jun 06 '12 at 13:24
  • 4
    Lambda Calculus is pure and turing complete. Again, whether or not you can design a machine that is pure to "execute" the code is completely irrelevant to the question whether or not the language used to describe the program is pure. Your argument is about as valid as saying it is impossible to construct a square, because for that you'd have to be able to construct a diagonal of irrational length. – Cubic Jun 06 '12 at 17:56
  • 1
    @WeNeedAnswers Haskell doesn't have to be compiled to exist. You can compute values on paper. `main = putStrLn "Hello"`, has for value an action which *will* print "hello" if something was compiling it and *executing* it. The *dirty* hack of executing the action is outside of Haskell. In Haskell you can't execute an Action (IO), you can only compute one and hope that an external thing will execute it for you. – mb14 May 12 '15 at 17:00
  • 1
    @WeNeedAnswers I challenge you to build a computer based on Turing's maths -- or even a computer with 64 bits of address space. Compilers translate from models of computation to concrete machines -- all of them. – solidsnack May 12 '15 at 17:52
4

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
  • 1,786
  • 1
  • 15
  • 33
  • 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 Jun 25 '10 at 13:32
  • 2
    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 Jun 25 '10 at 14:36
  • 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 Jun 26 '10 at 08:49
  • 2
    @WeNeedAnswers You seem extremely confused... First all monads are perfectly "pure" apart from IO and ST where there might be discussion, the concept of monad itself is not in any way impure by essence. There is an argument that can be made that says that the IO Monad itself is pure and only produce imperative programs that are then executed by the runtime (written in C) but this is irrelevant because your preoccupation seems to be that "errors due to impurity can still creep in" rather than the theoretic purity of Haskell. – Jedai Jun 05 '12 at 18:37
  • Of course if you do IO in Haskell, you can have the same problems with it as any other language : Haskell isn't magic, it can't download instantly, make the network latency-free, correct a corrupted file or anything like that ! What it _can_ do that other languages don't is guarantee you that a piece of code is IO free, and so don't have any of those nasty issues you fear. The IO monad allows you to segregate IO code from the pure kernel of your program with your types, IO simply can't be done inadvertently : your IO code have to be in the IO Monad (so with type `IO a`). – Jedai Jun 05 '12 at 18:43
  • Thanks for contributing to this Jedai, all you have done is reiterate the problem domain and reintroduced the same argument that in mathematical terms, Monads are pure, but in reality they are not. I am not arguing the point that Monads aren't cool and hip, and in their correct place along with the infinity symbol, do aid in the reasoning of the unreasonable when chalked on a black board. However in the real world, as a day to day programmer, they don't do anything new. – WeNeedAnswers Jun 05 '12 at 22:46
  • @Jedai I just read your paragraph again. Haskell is not theoretical and this is the point I am making. Since it has joined us in the practical world, the nice soft world of the purity of theory Versus the stark reality of practical has reared its ugly head again. I love Haskell, its a great language. I just hate the way that its sold on the idea that because it uses the concepts taken from mathematical concepts, that instantly its a better language that can do programming better than anything else out there because it uses real maths as opposed to engineering principals. – WeNeedAnswers Jun 05 '12 at 23:15
  • @WeNeedAnswers Of course Haskell is theoretical ! If not what is it ? "Engineering principles" ? You think that's separate from Maths ? In what world are you living ? In mine, all engineering is just an application of Mathematics to reality to solve problems. Haskell enthusiasts never said that it was superior because it was "mathematical" and other languages aren't (all languages are Turing-equivalent and very much "mathematical"). They just think that the way you tend to express idea in Haskell (the purely functional code) is more amenable to reasoning than the imperative paradigm semantics. – Jedai Jun 06 '12 at 13:40
  • Note that Mathematics include logic and basic computation. If you are doing programming without that... well I don't want to touch your programs. – Jedai Jun 06 '12 at 13:41
  • 2
    @WeNeedAnswers I just saw your comments. Do you think it is impossible to tell what will be shown when you type "2+2=" on a calculator? Because calculators are not theoretical constructs, but practical real-world things made of atoms. That's complete nonsense. Haskell executed on a machine is nothing more than an advanced calculator. You can reason about what it will do. – sdcvvc Jun 13 '12 at 20:57
  • "So in short, a Haskell program is a function, composed of other functions, that returns an imperative program. The Haskell program itself is pure." Then, a C program is a pure description of a program that gets translated into an imperative program by a C compiler, right? – mljrg Jun 06 '20 at 11:31
4

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
  • 1,123
  • 9
  • 13
  • 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 Jun 25 '10 at 13:24
  • 11
    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 Jun 25 '10 at 17:28
  • @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 Jun 26 '10 at 08:58
  • @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 Jun 26 '10 at 09:00
  • @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 Jun 26 '10 at 12:32
  • 5
    "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 Jun 26 '10 at 12:33
  • @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 Jun 26 '10 at 14:15
  • @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 Jun 26 '10 at 14:18
4

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
  • 38,665
  • 7
  • 99
  • 204
1

Is Haskell truly pure?

In the absolute sense of the term: no.

That solid-state Turing machine on which you run your programs - Haskell or otherwise - is a state-and-effect device. For any program to use all of its "features", the program will have to resort to using state and effects.

As for all the other "meanings" ascribed to that pejorative term:

To postulate a state-less model of computation on top of a machinery whose most eminent characteristic is state, seems to be an odd idea, to say the least. The gap between model and machinery is wide, and therefore costly to bridge. No hardware support feature can wash this fact aside: It remains a bad idea for practice.

This has in due time also been recognized by the protagonists of functional languages. They have introduced state (and variables) in various tricky ways. The purely functional character has thereby been compromised and sacrificed. The old terminology has become deceiving.

Niklaus Wirth


Does using monadic types actually make a language pure?

No. It's just one way of using types to demarcate:

  • definitions that have no visible side-effects at all - values;
  • definitions that potentially have visible side-effects - actions.

You could instead use uniqueness types, just like Clean does...


Is the use of monadic types just another "get out of jail free card" for reasoning of computer systems in the real world, outside of blackboard maths?

This question is ironic, considering the description of the IO type given in the Haskell 2010 report:

The IO type serves as a tag for operations (actions) that interact with the outside world. The IO type is abstract: no constructors are visible to the user. IO is an instance of the Monad and Functor classes.

...to borrow the parlance of another answer:

[…] IO is magical (having an implementation but no denotation) […]

Being abstract, the IO type is anything but a "get out of jail free card" - intricate models involving multiple semantics are required to account for the workings of I/O in Haskell. For more details, see:


It wasn't always like this - Haskell originally had an I/O mechanism which was at least partially-visible; the last language version to have it was Haskell 1.2. Back then, the type of main was:

main :: [Response] -> [Request]

which was usually abbreviated to:

main :: Dialogue

where:

type Dialogue = [Response] -> [Request]

and Response along with Request were humble, albeit large datatypes:

pared-down definitions of request and response datatypes for dialogue-based I/O

The advent of I/O using the monadic interface in Haskell changed all that - no more visible datatypes, just an abstract description. As a result, how IO, return, (>>=) etc are really defined is now specific to each implementation of Haskell.

(Why was the old I/O mechanism abandoned? "Tackling the Awkward Squad" gives an overview of its problems.)

These days, the more pertinent question should be:

As Owen Stephens notes in Approaches to Functional I/O:

I/O is not a particularly active area of research, but new approaches are still being discovered […]

The Haskell language may yet have a referentially-transparent model for I/O which doesn't attract so much controversy...

atravers
  • 455
  • 4
  • 8
-2

No, it isn't. IO monad is impure because it has side effects and mutable state (the race conditions are possible in Haskell programs so ? eh ... pure FP language don't know something like "race condition"). Really pure FP is Clean with uniqueness typing, or Elm with FRP (functional reactive programing) not Haskell. Haskell is one big lie.

Proof :

import Control.Concurrent 
import System.IO as IO
import Data.IORef as IOR

import Control.Monad.STM
import Control.Concurrent.STM.TVar

limit = 150000
threadsCount = 50

-- Don't talk about purity in Haskell when we have race conditions 
-- in unlocked memory ... PURE language don't need LOCKING because
-- there isn't any mutable state or another side effects !!

main = do
    hSetBuffering stdout NoBuffering
    putStr "Lock counter? : "
    a <- getLine
    if a == "y" || a == "yes" || a == "Yes" || a == "Y"
    then withLocking
    else noLocking

noLocking = do
    counter <- newIORef 0
    let doWork = 
        mapM_ (\_ -> IOR.modifyIORef counter (\x -> x + 1)) [1..limit]
    threads <- mapM (\_ -> forkIO doWork) [1..threadsCount]
    -- Sorry, it's dirty but time is expensive ...
    threadDelay (15 * 1000 * 1000)
    val <- IOR.readIORef counter
    IO.putStrLn ("It may be " ++ show (threadsCount * limit) ++ 
        " but it is " ++ show val) 

withLocking = do
    counter <- atomically (newTVar 0)
    let doWork = 
        mapM_ (\_ -> atomically $ modifyTVar counter (\x -> 
            x + 1)) [1..limit]
    threads <- mapM (\_ -> forkIO doWork) [1..threadsCount]
    threadDelay (15 * 1000 * 1000)
    val <- atomically $ readTVar counter
    IO.putStrLn ("It may be " ++ show (threadsCount * limit) ++ 
        " but it is " ++ show val)
dev1223
  • 1,148
  • 13
  • 28
  • Ok, there is an escape door (mainly IORef and FFI) which allows Haskell to be impure, but this escape door could be closed if needed and Haskell will still be Haskell. Moreover, those function are tagged are impure and doesn't affect pure function. Pure functions are pure in haskell. – mb14 May 15 '15 at 13:57
  • Pure functions are pure everywhere. In C++, Java, Python ... And pure functional language is about banning impure functionality. And finally IO monad is one big escape door, not only IORef and friends. – dev1223 May 16 '15 at 15:09
  • The action is evaluated in a pure way. It is referentially transparent. The race condition happens due to scheduling during execution. Your argument is no better than: I can print a random number, thus the language need to be impure. Which is wrong as @sdcvvc's answer explains. Your action evaluates to "Create a mutable counter, fork limit threads, in each: read that counter increase by one and write new value to it" which is always the same. Executing it on a non-deterministic scheduler has race conditions. This isn't the language's fault. – jan.vogt Feb 19 '19 at 15:06