Hacker News new | past | comments | ask | show | jobs | submit login

Ah. You found Haskell hard because you were trying to learn it out of order. Demanding to understand what a monad is before learning how higher-order functions and higher-kinded polymorphism work in Haskell is like demanding to know what an Iterator is before learning what a variable is in Java.

The thing about "Monad" is that it's an absolutely trivial interface. You'll know you actually understand it properly when you go "oh, that's all." Is it hard to describe? Not when you understand the underlying concepts. But if you demand to skip them, you're only going to confuse yourself.




No, but a good assumption. I read several books and dozens of tutorials including parts of the 1000 page one that couple did like 6 years ago. Function application and so on definitely came before IO and the likes. It all just seemed like enormous mental effort for the simplest of things. That was my experience at least. It's possible I just don't have the best kind of brain for FP.

Your mileage will vary of course. This was just my experience. I know Haskell is powerful, but it wasn't right for me. I haven't met a scripting language I didn't like yet though, so perhaps my neurons are just wired for that now. I do enjoy the random APL or Forth problem though and I'm decent at SQL after using it often for a decade.


> parts of the 1000 page one that couple did like 6 years ago.

IMHO that book is bloated and overrated. Maybe it works for some people, but I find the explanations really weird and some of the exercises are just useless busywork without a point.

There are good parts in there (they do try to teach you how to properly think about types which is nice) but I really feel like you don't need to read 1000 pages to learn basic Haskell.


Fine. I also read Learn You a Haskell, the O'Reilly one and a myriad of other sources. In comparison, Python was a cake walk.


Oh I agree that Haskell is complicated to learn, I just happen to think that that book kinda makes it even worse.


Which makes it rather unfortunate that Haskell is a language where the recommended way of writing a program to e.g. read two numbers from the user, add them, and print the result, demands understanding "Monad".


Where do I have to 'understand "Monad"' to write this?

    main = do
      x <- readLn
      y <- readLn
      print (x + y)
Do I have to 'understand "Monad"' to write this?

    def main():
      x = input()
      y = input()
      print(x + y)


> Where do I have to 'understand "Monad"' to write this?

Literally every line?

Your example makes it seem so simple! "Do" for function declarations, "<-" for variable assignment... Haskell is just like python!

Alright, here I go!

  fact n = do
    n' <- n - 1
    if n <= 1 then 1 else n * fact n'
Uh oh...

  Couldn't match type `t' with `m t'
  Expected: t -> m t
  Actual: m t -> m t
What the heck is "m t"?


> What the heck is "m t"?

It's worse than that! What the heck is "->"?

But I don't particularly buy the argument. Alright, here I go:

    def main():
      x = input
      y = input
      print(x + y)
Uh oh...

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unsupported operand type(s) for +: 'builtin_function_or_method' and 'builtin_function_or_method'
What the heck is "Traceback", "<stdin>", "<module>", "TypeError", "operand", "builtin_function_or_method"? Did I have to understand all those to write the simple Python program?

The claim that you have to understand everything about a failure case in order to write simple programs doesn't seem valid to me. It doesn't resonate with how I've learned languages before. However, I may not have the best perspective because I'm very familiar with both Python and Haskell so I don't have the "beginners mind" any more. I would be interested to hear from someone who's a beginner (perhaps in both languages) whether they think the Haskell program requires more understanding than the Python one https://news.ycombinator.com/item?id=37402253


My point isn't that it errors. It's completely reasonable to expect even a beginner to do basic troubleshooting. My point is that the error specifically mentions monads. So will the documentation for `do` and `<-`. Anybody looking to use `IO` beyond a copy-and-paste level will be thrust headfirst into monad-land. This doesn't mean `IO` cannot be used at all without understanding monads, just that it's extremely explicit that something unique and interesting is happening when you do. In fact, I expect most people build their intuitions around monads by playing around with `do` notation.

This isn't the case in Python where input and output are standard functions. The syntax for function declaration and variable assignment just work.

If our requirements changed to now read and sum an arbitrary number of inputs together, I'd expect a beginner could change the Python program to do so. I'm skeptical they could make the required changes to the Haskell version (without understanding monads).


> My point isn't that it errors. It's completely reasonable to expect even a beginner to do basic troubleshooting. My point is that the error specifically mentions monads.

Right, and the Python errors mention all sorts of things a beginner is not expected to understand, like "builtin_function_or_method". I don't think "understanding builtin_function_or_method" is required to write the Python and I don't think "understanding Monad" is required to write the Haskell.

> If our requirements changed to now read and sum an arbitrary number of inputs together, I'd expect a beginner could change the Python program to do so. I'm skeptical they could make the required changes to the Haskell version (without understanding monads).

Where do I have to "understand monads" to write this?

    main = do
      iters <- readLn
    
      let loop n total =
            if n == iters
            then print total
            else do
              x <- readLn
              loop (n + 1) (total + x)
    
      loop 0 0
I had to understand that in Haskell looping is done via recursion (which is mindbending initially, if you come from imperative programming), but I don't see that I had to understand monads. FWIW the Python that I'd write is

    def main():
        iters = input()
    
        total = 0
    
        for _ in range(iters):
            x = input()
            total += x
    
        print total
This is simpler. But I could write a Haskell version that uses an IORef to get roughly the same code structure as the Python.

    main2 = do
      iters <- readLn
    
      total <- newIORef 0
    
      for_ [1..iters] $ \_ -> do
        x <- readLn
        modifyIORef' total (+ x)
    
      theTotal <- readIORef total
    
      print theTotal
I would say this requires understanding the mutability/immutability distinction to know why we use an IORef at all (but you'd get that with OCaml[1] too) and it still requires understanding do notation, but I don't see that it requires "understanding monads"!

Now, if I were writing this for real I probably would use a version with a state monad transformer so it was

    main3 = do
      iters <- readLn
    
      total <-
        flip runStateT 0 $ for_ [1..iters] $ \_ -> do
          x <- lift readLn
          modify' (+ x)
    
      print total
That would require some understanding of monads so that you can understand how they are transformed with monad transformers!

[1] https://www.cs.cornell.edu/courses/cs3110/2018sp/l/14-mutabl...


Is an error that takes looking things up better than just doing an unexpected thing? Because the mistake there seems pretty similar to the kind of syntax mistake in:

  >>> def factorial(n):
  ...   1 if n <= 1 else n * factorial(n - 1)
  ...
  >>> factorial(1)
  >>> 
Why is nothing happening?


> Is an error that takes looking things up better than just doing an unexpected thing?

100% yes! Took me quite a while to figure out what was wrong with your example. This is by far my biggest frustration with Python.

Perfect example, by the way. You've exactly captured the sort-of "semantic mistake" I was trying to convey: technically valid syntax but the meaning doesn't match the intent.

Fixing your example requires understanding how functions return values, a concept pervasive throughout Python. The first function you ever wrote probably returned something.

Fixing mine requires understanding how `do` and `<-` interact, which I remain skeptical can be done without exposing oneself to monads. My example should not use `do` at all, I'm actually surprised it can be made to compile with it.


> Fixing mine requires understanding how `do` and `<-` interact, which I remain skeptical can be done without exposing oneself to monads.

I think this is the major point of contention between us.

I don't think that understanding how to use `do` requires "understanding monads". My example was to demonstrate that understanding how to use `do` for the IO monad is not really much different from understanding how to write a sequence of statements in Python. There's one big difference: you have to use both `<-` and `let`, and you have to know that the former is used to get a value out of something of type `IO a` and the latter is just normal variable binding. But that still doesn't require "understanding monads"! It just requires knowing there's a special thing called `IO` that wraps things with effects.

I do think that understanding how to use `do` for arbitrary monads requires understanding monads! But that was not required to solve the problem lmm originally proposed, nor your extension.

I also do think that most pedagogical Haskell material focuses too much on "understanding fine details" and that applies particularly to monads. My point is that another approach is possible. That is simply to use `do` notation intuitively via analogy with imperative languages (which is a perfectly fine thing to do).


  fact n = do
    let n' = n - 1
    if n <= 1 then 1 else n * fact n'
Here you go. Not sure about Haskell but in PureScript it compiles. Use "<-" for functions which return a value in IO type constructor, otherwise use "let".


That's kinda the point though, right? There's something different about this `IO` thing that uses special syntax. You might be able to guess something and get it to compile, but should you?

  fact n = do
    let n' = do n - 1
    if do n <= 1 then do 1 else do n * fact n'


> Where do I have to 'understand "Monad"' to write this?

The recommended explanation of what <- is/does will be about "Monad".


You're moving the goalposts! You said 'the recommended way of writing a program to e.g. read two numbers from the user, add them, and print the result, demands understanding "Monad".'


I don't think it's "moving the goalposts" when you demand that you can understand, at least to a first-order approximation, what the code that you write is doing.

Yes, you could just treat IO as a black box and memorize a bunch of weird syntax rules, but you're going to have a hard time very quickly, especially when you change something and the compiler throws errors at you.

FWIW, I think that monads are a powerful abstraction, but they're certainly not incredibly natural and Haskell basically just throws them at you from day 1.


The recommended way of writing a program in most languages demands some level of understanding what you're writing. You would have to somehow come up with the "<-" parts of that program, and the recommended way of doing that is to understand Monad.


> The recommended way of writing a program in most languages demands some level of understanding what you're writing

Sure, some. I think this discussion is about exactly what and how much, when it comes to Haskell.

> the recommended way of doing that is to understand Monad.

That doesn't ring true to me, not from how I learned Haskell and not by analogy with how I learned other languages. I don't remember having to "understand Monad" to write in "do" notation. Quite the opposite in fact. I vividly remember trying to "understand Monad" and being unable to, and then just giving up and trying to use do notation, finding it easy, completely unhelped by what I was trying to "understand", and regretting having spent all the time "understanding". But it's been a long time since I learned a language so maybe I'm mistaken. I would appreciate the point of view of others who have fresher eyes.

I will concede that people have a harder time learning Haskell than Python (although I don't think anyone understands the exact reasons) but you gave a very specific example of a program that is supposedly requires some deep understanding to write in Haskell. I wanted to give a counterpoint so people can decide for themselves. That's as far as my claim goes.


Sure, but if it's just for pedagogy reasons, there's plenty of things you can do in order to learn about HOFs, the type system, typeclasses, Monoid, Functor, etc. before reading in some input, and if that becomes necessary, a teacher can always provide some template with a disclaimer "don't worry about it, we'll cover that later" - same as they do it for Java that forces you to learn what classes are before you can write a program.

OTOH, the promise of Haskell is that while it takes longer to learn and getting used to, the payoff is that you can get rid of certain classes of bugs. Whether that's true or not is a different matter entirely, but a priori just because something is hard to learn it doesn't mean it's not worth it.





Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: