Live data from Hacker News

Effectful Haskell: IO, Monads, Functors

slpopejoy.github.io

51–60 of 68 posts

Re: Effectful Haskell: IO, Monads, Functors

#51
post #2

Naming is one area where Haskell's academic background is more curse than blessing. Numerous tutorials and blog posts would never have been written if only functor was named mappable and monad was named computation builder. They're named by analogy to category theory, but their usage in computer science is not at all similar.

I disagree.

There are some major benefits to the Haskell community's approach of naming abstractions after the math (where a suitable mathematical abstraction exists).

First, as has been mentioned, there are existing treatments of the objects in question, some interesting results there can be ported over to programming, and intuitions there lead new and sometimes useful places. Some newcomers will even be familiar with the concepts already - this is very few people for monad, but far more for monoid and semigroup. It avoids erecting an unnecessary wall between programming knowledge and mathematical knowledge.

Second, it changes the character of a particular kind of discussion: there is never ambiguity about whether a newly considered operation on a type "really" is "appending" - is it associative? does it have an identity? you've got a monoid. This means it's very clear what you can and cannot assume around a particular interface. Questions about whether "functors" are well thought of as "containers" or "mappable" or whatnot are clearly only questions of pedagogy.

Re: Effectful Haskell: IO, Monads, Functors

#52
post #30
post #27

Earlier quoted context omitted.

That's something you have to learn to do. It's like asking "where is the stack?" when you're just calling functions. If you don't develop some skill for seeing how your code implements underlying abstractions, you will be missing out. You can program without knowing what a stack is. But if you want to solve a problem using a stack, it helps to know that your function call stack can be 'overloaded' to solve stack prob…

What I meant was something different. The difference is that saying that "IO is a monad" is wrong in the mathematical sense too. IO is not a monad - the combination of (IO, bind, return) is the monad. It only makes sense to say the above in Haskell where there can be only one instance per type. The set of natural numbers N is not a monoid; {N, +, 0} is a monoid. The set may, at best, be "monoidal" (i.e. there exists…

"[T]he combination of (IO, bind, return) is the monad. It only makes sense to say the above in Haskell where there can be only one instance per type."

This is very important, and something that took me a bit to grok, and definitely got in the way of understanding for a while.

One thing you can do - and I don't know whether it makes sense to teach it this way - is think of the typeclass constraints as actual arguments (absent optimizations, that is literally the case anyway - GHC passes instance dictionaries). In that case, "the monoid" is that dictionary, and the type referenced is the carrier set, and a value of that type is an element of the carrier set.

Re: Effectful Haskell: IO, Monads, Functors

#54

Earlier quoted context omitted.

Their naming is similar enough to leverage existing research literature, and knowledge, though. Those with a background in math can map some of their existing knowledge. Those without a math background have a more precise vocabulary to understand the concepts and research further. Even if they are a bit foreign, I prefer the attempt to be precise.

It's worth observing that "background in math" here probably implies postgraduate study in a relevant field, which very few people will actually have done. The average new math graduate with a bachelor's degree might have heard of category theory but probably hasn't studied it during an undergraduate course. More generally, I think one problem the Haskell world has with attracting more developers is that it's dominat…

A crude analogy would be mathematicians exploring math vs physicists using math.

Re: Effectful Haskell: IO, Monads, Functors

#55
Something I've wondered about: How does Haskell not optimize away IO ()? In a pure, functional languages, a function that doesn't return anything can simply be eliminated — but of course it isn't.

I've always assumed that Haskell's compiler has a built-in rules about IO having side effects, but I never actually bothered to find out.

When I first started with Haskell, one of many small epiphanies was when I realized that the IO monad itself doesn't actually do anything. bind and return don't do anything except wrap and unwrap values, and there's nothing that checks if you are in some kind of "IO context" to permit things like file I/O. There's no magical "side-effectful computation chain engine" behind the scenes.

Re: Effectful Haskell: IO, Monads, Functors

#56

Something I've wondered about: How does Haskell not optimize away IO ()? In a pure, functional languages, a function that doesn't return anything can simply be eliminated — but of course it isn't. I've always assumed that Haskell's compiler has a built-in rules about IO having side effects, but I never actually bothered to find out. When I first started with Haskell, one of many small epiphanies was when I realized t…

IO is the type, not the value. Something that takes IO and returns IO wouldn't be optimized out for the same reason a negation wouldn't be optimized out simply because it takes an Int and returns an Int.

Re: Effectful Haskell: IO, Monads, Functors

#57
post #56

Something I've wondered about: How does Haskell not optimize away IO ()? In a pure, functional languages, a function that doesn't return anything can simply be eliminated — but of course it isn't. I've always assumed that Haskell's compiler has a built-in rules about IO having side effects, but I never actually bothered to find out. When I first started with Haskell, one of many small epiphanies was when I realized t…

IO is the type, not the value. Something that takes IO and returns IO wouldn't be optimized out for the same reason a negation wouldn't be optimized out simply because it takes an Int and returns an Int.

I was referring to IO (), which is a type that can have only one value, () — hence my thinking that a compiler should simply optimize the call to (), since there is no other logical value that it can return.

Re: Effectful Haskell: IO, Monads, Functors

#58
post #56

Earlier quoted context omitted.

IO is the type, not the value. Something that takes IO and returns IO wouldn't be optimized out for the same reason a negation wouldn't be optimized out simply because it takes an Int and returns an Int.

I was referring to IO (), which is a type that can have only one value, () — hence my thinking that a compiler should simply optimize the call to (), since there is no other logical value that it can return.

The type isn't the same as its parameter. An IO () is not () just like Maybe () isn't (), and [()] isn't ().

Re: Effectful Haskell: IO, Monads, Functors

#59

Earlier quoted context omitted.

I was referring to IO (), which is a type that can have only one value, () — hence my thinking that a compiler should simply optimize the call to (), since there is no other logical value that it can return.

The type isn't the same as its parameter. An IO () is not () just like Maybe () isn't (), and [()] isn't ().

I'm not sure I find that explanation satisfactory. This only implies that there is information not used by the compiler: The only conceivable value that the type IO () can have is still the value IO (). If the compiler knew this, then it could, and would, optimize it away. Is there a formal aspect of parameterized types that prevents this from — formally — happening?

Re: Effectful Haskell: IO, Monads, Functors

#60

Earlier quoted context omitted.

The type isn't the same as its parameter. An IO () is not () just like Maybe () isn't (), and [()] isn't ().

I'm not sure I find that explanation satisfactory. This only implies that there is information not used by the compiler: The only conceivable value that the type IO () can have is still the value IO (). If the compiler knew this, then it could, and would, optimize it away. Is there a formal aspect of parameterized types that prevents this from — formally — happening?

You are asking the right questions, it just means you're ready to go one step down the rabbit hole. You need to carefully, and slowly, read SPJ's Tackling the Awkward Squad. You need to allow yourself to skim over the parts that are perplexing on first-reading and take them on faith. With more understanding you'll find even the answer given there not particularly satisfactory (in fact the paper points it out explicitly, trust me you'll know when you're in the precise section).

At this point you'll either accept the state of affairs, or want to go deeper down the rabbit hole, at which point you become more an academic than a programmer per se.

In a very precise sense what groovy2shoes is saying is exactly right, and you need to understand that truly strange weirdos inhabit IO () whereas, as you point out, only one thing, modulo non-termination, inhabits ().

Post reply on HN