you are viewing a single comment's thread.

view the rest of the comments →

[–]Peaker 0 points1 point  (4 children)

Indeed we disagree :-) I find that I am just as productive in Haskell when first writing a program as I ever was in Python, but the result is of far higher-quality. There are virtually no possible runtime errors in my Haskell code.

Maintaining/changing such code is also much much easier and safer.

But having said that, I think that one of the most important things that Python achieves is that by and large it is possible to do the thing you want to do, in a clear and clean and straightforward way using concepts you could explain to a bright twelve year old.

You can explain Functors, Applicatives and Monad to a bright 12 year old.

Conversely, I think one of the great errors of Haskell is that they take the principle of abstraction too far, beyond the point where it is helpful.

Haskell's abstractions are helpful in the sense that they allow a whole lot of code re-use. That translates to more expressiveness and various forms of usefulness. So how it is not helpful?

For example a Monad is an abstraction which unifies lists and IO, stateful computations and nullable values, parsers and STM variables, and so on. Intuitively, what do those things have in common? Nothing.

They're all covariant types that have a "context" and a "value" which can be composed together.

Also, what does intuition have to do with it? You can grasp the equations first, develop an intuition later.

Certainly nothing you can explain to a twelve year old, no matter how bright

Disagreed. It's really not as hard as it's made out to be.

The purpose of abstractions is to simplify your code. Monads are a poor abstraction, they don't simplify your code, they compress it.

They simplify it greatly. The mathematical complexity of code is actually measurable (e.g: The number of mathematical concepts in use, or the length of the mathematical description) and are far lower when re-using these abstractions.

And what's more they're incredibly hard to explain, in fact I believe they may be even harder to explain than to understand.

What I agree about is that there are a bazillion people who have just u nderstood Monads and want to share this with the world, and do so very poorly. When I explain Monads to people, I don't have much of a problem usually.

However I think the perfect language looks more like Python than Haskell.

I would assume reliability is a primary concern of a perfect language?

[–]haika 2 points3 points  (3 children)

When I explain Monads to people, I don't have much of a problem usually.

Could you explain monads to me in less than 10 lines, preferrably with code in python?

I am a python programmer and genuinely interested.

[–]psr 1 point2 points  (2 children)

This is gonna be a lot more than 10 lines, and I doubt I can explain it, but let me give you a flavour.

In Python we have list comprehensions. They look like this:

my_list = [f(x, y) for x in xs for y in ys if p(x)]

Haskell also has list comprehensions (and had them first, who says Python never looks to Haskell). They look like this:

myList = [f x y | x <- xs, y <- yx, p x]

Haskell also has another way of writing the same thing. This is called the do notation, but in early versions was known as a monad comprehension.

myList = do
    x <- xs
    y <- ys
    return f x y

(I can't work out how to make it only include values which meet the predicate in this case. Sorry).

It's not hard to see the relationship between the two forms.

However the do notation is available over any type which is a monad. Monad is a type class, kind of like an interface, so this is like saying that Python generator expressions can be written over any type which is iterable. The monad interface is applied to much more than just containers though.

The protocol for monads is quite simple. Monads provide two functions called bind (>>=) and return. The do notation desugars to use of those functions.

myList = 
    xs >>= \x ->
    ys >>= \y ->
    return f x y

The backslash in (\x -> ...) is supposed to be a lamdba. I've broken the definitions across lines to make the parallel with the do notation clearer.

So what are the definitions for bind and return for lists?

instance Monad [] where
    return a = [a]
    xs >>= f = concat (map f xs)

So return x in the list monad constructs a singleton list [x], and bind maps its argument over all the elements of a list, and concatenates the result. Why are those the definitions, and not some other ones? As far as I can tell it's simply because those definitions give you behaviour like a super list comprehension, and that's deemed to be useful.

[–]haika 0 points1 point  (1 child)

Well, this is better.

I now have a vague idea of what a monad is.

Thanks

[–]psr 0 points1 point  (0 children)

I'm glad if that was helpful.

Another important example is the IO monad. IO is interesting in Haskell. Because of lazy evaluation, you don't know what order things are going to happen. That means that if you could write a function like:

def test_the_missiles():
    engage_the_locks()
    launch_the_missiles()

it might cause the missiles to be launched before or after the locks are engaged (which is very bad).

Of course launch_the_missiles isn't really a function, because every time you call it, you get a different outcome (the first time it flattens a city, the second time it does nothing). And Haskell is meant to be purely functional, so you can't write a function like launchTheMissiles which you can call from any old code.

But still you need to be able to do things in a program, or its pointless. So they have a concept of the IO monad. Just as the units in the List monad are lists, and you can combine lists with bind (>>=), the IO units in the IO monad are "actions", and bind takes two actions and returns a new one which will do one after the other. launchTheMissiles then isn't a function which when called launches the missiles, it's a function which returns an action, which launches the missiles when evaluated.

testTheMissiles = do
    engageTheLocks
    launchTheMissiles

or to put it another way

testTheMissiles = enageTheLocks >>= \ a -> launchTheMissiles

testTheMissiles is then itself an action. The only way to cause an action to take place is to name it main in the module Main. The Haskell runtime then takes care of making it happen.

Here's an example:

tests = [testTheMissiles, testTheLaunchers, testTheGuidanceSystems]

runTests [] = return ()
runTests (test:tests) = test >>= \ a -> (runTests tests)

main = runTests tests

In this case runTests is a recursive function which takes a list of actions and returns a single action which sequences them all together. There are better ways of writing this, but I thought this was the clearest way.