in reply to Is it worth using Monads in Perl ? and what the Monads are ?
Disclaimer: This is a viewpoint of a 'failed', would be Haskell programmer, with all the jaundiced opinion that entails.
One simple description of "pure functional code" is: Every function always gives the same output, for the same set of inputs; and has no side effects.
Which sounds quite reasonable, and given a certain spin, very laudable, but here are a few (and just a very few), of the things that you can't do whilst remaining 'purely functional'*; things that most programmers in most programming languages take for granted.
*...without Monads
The function you call to get the input would produce a different output for the same inputs.
Ditto. And the state of the PRNG (or other source) would be left in a different state to when you called it--that's a side-effect).
Ditto.
The output medium would left in a different state--a side effect.
The call could return a failure: the disk was full; the socket was closed; the database had been modified by another program.
In essence, a purely functional program has to be completely self-contained at compile time. This is because the compiler has to be able to freely substitute the value of any given function wherever that function is used within the program. A purely functional program has no concept of sequence. They are defined in a declarative manner in terms of function definitions, whereby function A is defined in terms of some other function or functions (or itself), and a few constants. Once it is declared, its result (for any given set of inputs), is fixed forever. That means, in theory, the compiler can substitute its defined value at every point in the program where it is used, and in any order, and so reduce the program by substitution, until finally everything is reduced and the result is known.
In other words, the results of every purely functional program is decidable by the compiler, and its result is known by the time the code is compiled. There is no need to actually run the program ever because the result will never vary--it is a constant!
Besides, even if you did run it, the program could not output the result because that would have the side effect of changing the file, screen or wherever you output the result to. Ie. if you output the result twice--to say, the screen--the second time you output it, the output from the first time would still be on the screen meaning the result of doing it the second time would be different from the first. It would have had a side effect. And that's not purely functional.
Read on; it's a joke :)
Of course, that would make for a pretty useless programming language. For example, you could never write a purely functional compiler--that is, a compiler that is purely functional, rather than a compiler for a purely functional language--because you couldn't read the source code. So every program the compiler would ever compile would have to be embedded into the compiler. And you wouldn't be able to write out the compiled program, which would kind of negate the purpose of having it.
So, in order for a purely functional language to do anything useful, it has to incorporate some mechanism for performing IO. For getting data in and outputting results. And it has to introduce the concept of sequence. That is, it is necessary to read the data before you can perform the calculation upon that data; which in turn has to happen before you can output the results.
But to do that, it becomes necessary to distinguish those functions that are purely functional--give the same results every time and have no side effects--from those that interact with the world outside of the program, and so are not: read() will produce a different result each time you call it; write() will have a permanent (side) effect upon the world each time you call it. It needs to distinguish them so that the compiler can know which functions can safely perform its reduction-by-substitution magic upon; and which it cannot.
Ignoring the formal (and extremely obscure) mathematics behind this concept which I admit to not understanding at all and could never paraphrase, in simplistic terms a monad is a black box that represents (or contains) the world (or some small part of it), outside of a given, purely function program.
And a monadic function is a function to which you must pass one of these monads as one of its parameters, and which returns (a different) monad as its only result. The output monad is the (constant and unchanging) result of applying that function to the input monad. If you call that same monadic function a second time, you will be passing a different monad as input; and recieve another, different, new monad as its result, And each time you run the program, the initial monad representing the outside world will be different--time will have passed; things will have changed--and when you call the monadic function, the input monad will again be different from any other time it has been called, so it will be free to return a different monad to every other time it has, or will be, called.
And so the "same output for the same inputs" rule of functional programming has not been violated. And as every time you call, you pass a different monad in and a different monad out, any given monad only ever has one state, so there are no side effects. In this way, you have introduced the concept of sequence into a purely functional program, without violating the purely functional ethics(*).
In Haskell's terms, every function in a Perl program is monadic, carrying an implicit monad as an input parameter, and implicitly returning another as one of its return values. Passed into the 'main()' function at startup, and return (another) from whence it came--the OS? the world?--when the program terminates.
As Perl does not subscribe to the purely functional ethic, it doesn't need the obscure, high maths concept of monads to 'excuse' its use of state, side-effects and the real world.
*Of course, some of you may, like me, consider this a diversionary tactic--so much smoke & mirrors--to obscure and defend from the charge that you cannot have a purely functional language and do anything useful.
You may also consider that need to latch onto such an obscure and ethereal concept to explain the ethics of a programming language would simply go away if it was admitted that you cannot have a purely functional language. That, if you accept that some parts of any given program lend themselves to being coded in a (purely) functional style; whilst other parts do not, then you can use functional techniques where they are advantageous and not where they lead to clumsy or awkward coding (or their reliance on obscure and difficult to understand concepts).
However, to a non-mathematician like me, the concept of imaginary numbers is difficult and obscure, but there is no denying their usefulness in allowing some things to be described and calculated, that would be very difficult, if not impossible, without them. And just as I have to accept the concept of imaginary numbers and the rules that define them; without trying to visualise what they look like :) So it may be that there are benefits to doing the same thing for monads. Just accept that they follow a set of rules and in doing so allow X, Y & Z that would be much harder if not impossible to do without them.
The flaw that I see in most of the descriptions and treatise on monads I've seen, is that they fail to outline what X, Y & Z are.
One possibility that seems to make sense to me, is that monads are a way of informing the (Haskell) compiler which functions it can perform its reduction through substitution processes on at compile time, and which it cannot. In this view, they are simply (or not so simply) a flag applied to each function definition saying: This function has side effects; it can only be reduced at run time.
I have seen the argument that purely functional code is 'provable'. And therefore purely functional programs are more reliable than non-PFP. But there is an old & well-known saying that a chain is only as strong as its weakest link. And if a program requires input--and every useful program must, otherwise its result is a constant; indeed, the entire program would be a constant--then you cannot pre-prove it. You have to test it with a variety of inputs. And any non-trivial program will have far too many possible inputs to test exhaustively, so you will never be able to 'prove' any, non-trivial, useful program.
The counter argument to that is: If you segregate the program into its purely functional, provable parts; and its monadic, non-provable parts, then you can reduce the number of possible errors, and therefore the risks, and therefore the amount of testing required. And that's a pretty good argument. Reduced risk and reduced testing are very laudable, real-world goals.
However, in order to prove even the functional parts of the program, it becomes necessary to not only prove the algorithms they represent; but also that the code produced by the compiler, from the high level language descriptions of those algorithms; is also correct. That is, you have to prove that the code the compiler produces from the Haskell language source code descriptions you feed it, actually performs exactly what that description says it should.
And there's the rub. ......the Haskell language source code descriptions you feed it...". The compiler takes input. That input is infinitely variable. And we just got done saying that you cannot 'prove' a program that takes input; you can only test it.
So, it's a chicken and egg situation. If you had a provable implementation of a compiler that was built from provable descriptions of its algorithms, then you could use it to build (implement) programs from provable descriptions of provable algorithms.
Until then, you will need to test programs--statistically. And as long that is true, there will never be a 100% guaranteed, bug-free program.
So, the question becomes, are there (any) benefits of imposing this obscure view of procedural code--that's really all monads are (or perhaps; allow is a better term), that make the alienation and conceptual difficulties that their use introduces to the world of programming, worth it? Is provability of algorithm descriptions, without provability of the code that converts those descriptions into code, worth the costs?
|
|---|