FWIW: I don’t believe having a “high ceiling” and a “low bottom” are mutually exclusive goals. We’ve just focused more on “raising the roof” than “getting down with the boogie”… ahem… I think with more effort we can make the language a lot nicer for newcomers and still happily have crazy advanced features like DH.
I myself happily use Haskell all the time without using anything more than ADTs and monads. I only just recently delved into effects systems. I touched lenses once but I usually don’t need them.
…like in a programming-language introductory course: start with the basics, leaving the the more advanced features for later (or another course altogether). For example:
So how “newbie-friendly” is I/O in Haskell?
…perhaps other options for I/O should now be seriously considered.
One thing I’ve proposed is that we should stop saying monads, or at least be more clear as to what we’re referring to.
For instance, are we talking about monadic types (or type constructors, more strictly)? Are we talking about a particular monad instance? Are we discussing Monad, the typeclass? Maybe we’re talking about an essentially monadic form of programming, like JS promises? Or, perhaps, we’re actually discussing category theory.
Be specific; if we must talk about a type as a monad, call it the foo monadic type, if we’re talking about the instance, call it the Monad instance for bar, etc…
Going further than the suggestion in this link:
We should simply stop calling foo a monad whenever possible, and refer to it simply as a type when simply calling it by its name won’t do, and call it a monadic type when we are talking about the set of monadic types. This helps build intuition for the fact that there are many monads, and the commonality is their lawful instancing into the Monad typeclass, but are really quite different (unless you understand them as effect providers).
The other side effect is if learners get taught calling it a monad is wrong, or out of date, they also get instantly disconnected from the monad tutorial ecosystem, building intuition about monadic types at the same time we tell them to stop reading the freaking monad tutorials.
I don’t personally find Monads to be all that scary or difficult of a concept, and (if I may) I don’t consider myself to be particularly smart (im probably quite the opposite). Functors and Monads are just function transformers. I think you can teach the intuition for these concepts pretty easily just using some simple concrete examples like
fmap :: (a -> b) -> f a -> f b
-- concrete
maybeMapper :: (String -> Int) -> Maybe String -> Maybe Int
And so on with some dead simple examples. It’s easy to see the pattern happening here when it’s framed this way.
The only thing that tripped me up when I learned these was that I was so used to OOP, I had this notion of being “inside” of a class, and I couldn’t understand how I could use monad/functor functions without being “inside” of some type of class.
But I believe this difficulty always comes when learning a new concept, it’s no different than learning how to use variables for the first time, or for loops, or functions. I also recall having as difficult of a time (as monads) learning to use functions like foldl.
I have a friend who’s an accomplishment C++ programmer but a bit afraid (or atleast shy) of FP, and when I attempted to teach him monads, his confusion seemed to be more like “that’s it?” I don’t believe he ever internalized the concept but it was only because he felt like there was more to it than I had told him. It was like he was suspicious that I had lied to him and so refused to believe it. Or that it was so simple, it wasn’t worth remembering.
Given that most non-Haskellers think of ‘class’ in an OOP sense; that’s the word we should be avoiding/being careful in our usage just as much as ‘monad’.
Haskell classes don’t encapsulate state; aren’t instantiated per data item. (A data decl inside an instance is a whole 'nother thing.) How do you explain class/instance to non-Haskellers?
“Interface and implementation” is enough to start building a path. That this is not a completely precise mapping has never posed a problem. If they come from a language with “traits”, all the better.
So this stuff isn’t my biggest priority either, which would probably be GHC modularity and build system concerns, but I can only beat that drum so hard without pissing everyone off. In other words, I don’t think I am taking away energy from that my first priorities, but spending energy I would otherwise be blocked spending at all by focusing on more things at once, does that make sense? (For similar reasons, I work on Haskell and Nix things in parallel also.)
I am trying to do this GHC Proposal 448 amending stuff because I think it is is good for Depenent Haskell and (more importantly) it will clean up the language for those that don’t even care about new expressive power.
(Probably the most exciting part for me would actually be deprecating type families; I am quite annoyed about how they work today, and have worked on projects in Haskell which us authors collectively decided to put on hiatus because the language wasn’t good enough (which I personally think was in large part frictions from type families).)
The variable binding rules for pattern signatures indeed have nothing directly to do with signature patterns (though the fact that type families having capitalized names being dubious might bring the two topics closer together), but I view this stuff a puzzle where it takes a number of fine adjustments to get to where we want with a new design that is still coherent. The journey is quite complex!
The build system and modularity stuff that I said was my first priority itself isn’t the end goal either. Really, I don’t like working on end goals much because by the time the benefits are obvious it’s usually much easier to find volunteers. I like instead working on these “yak shaves of yak shaves” where the plot is not at all clear, but far fewer people are interested and thus it seems like me doing or not doing the thing has much greater impact.
(To add more perspective, if the modularity of GHC was really good, I honestly believe @int-index and the other good folks working on DH could just go bang out a pretty complete DH prototype with less work, and then the proposal process could be about reconciling a fork which would make the big picture much clearer. Instead, it is too difficult to just make the thing, and so we have to design and implement as we go, which makes it easy to get snagged on various bikeshed level things, and meanwhile there is no working protoype. So ideally the modularity stuff could just be done first, and then modularity + DH would be less work over all; but it is infeasible to just have the DH work stop and wait for a number of perfectly valid/understandable reasons, so then both much precede in parallel.
@int-index had a blog post a while back which discussed "should we do Dependent Haskell or just work on Agda, with the observation that actually those things converge in that it is easier to compile Agda to a more powerful and similar Haskell than an older Haskell. I think that’s correct, and it is a somewhat related observation. A GHC family of libraries that supports a number of different frontend and backends would be a very powerful piece of software, made wiser by more perspectives, and prevented from “overfitting” any one of them. SPJ himself has similarly long wanted Core to work perfectly well for Strict or Lazy languages.)
Mz. Pillmore, I liked your post because I agreed with what you said, and how it applies to my own motivations in complaining about “snobbery”; that is to say, I was having issues with parts of the Haskell community and snob was a way to summarize my criticisms. Of course, I ultimately realized that I was more at fault than others, but we now are having a small discourse on snobbery.
As far as snobbery goes, I think there’s two main aspects of it.
Based off this post, snobbery would be defined as a certain level of defensiveness, arrogance, closed-offness, and complacency in the Haskell community, when there are storm clouds on the horizon.
I think, to the Haskell community’s credit, we’ve realized that there are problems and are working on them.
The other part of the problem is that Haskell is intrinsically snobbish, in an Azuman-Kojevean sense, which is not entirely a negative.
Azuma Hiroki, in his book “Otaku: Japan’s Database Animals”, grasped onto a footnote the Marxist philosopher Alexandre Kojeve wrote in “Introduction to the Reading of Hegel”, talking about modernity, post-modernity, and so on. Kojeve posited that there were two possible end-stages of human development; what he saw as “animal[-ism]” in parts of the West, and what he misconstrued as the “snob” in Japan (Kojeve was just writing brief impressions after a tourist trip to Japan, and it’s been pointed out that his understanding of Japan was cursory and basically wrong).
The contrast goes: the animal is happy, productive, but is essentially instinctive. The snob, in contrast, still retains a concept of values, in the love of form (snob as aesthete), and takes these values seriously, going to such extremes as Seppuku.
Haskell’s existence as a “pure, functional programming language” and the purist instinct in much of the Haskell community thus suggests that Haskell is a “snob” language.
In a programming context, the “animal / snob” dichotomy might be the difference between someone who is following “best practices” guides in their own languages, essentially copy-pasting or vim-macroing buttons context fields, whereas the opposite might be someone who is trying to build or explore new abstractions, a much harder task.
More pragmatically, the snob values of Haskell can be deleterious in making it harder for newbies and outsiders to join and can make people with traditional production experience feel alienated.
In the NeoHaskell Discord, for instance, Nick has brought up incidents where people were too afraid to publish their libraries because they had an unsafePerformIO somewhere.
On the other hand, discarding the snob values of Haskell can both make Haskell not Haskell, as well as something useless.
I really do not want to spend more time on this posting, and I hope you’ll forgive me for this, because I’ve been stuck thinking about it and researching on it for around 24 hours, and I need to concentrate on trying to be productive to Haskell projects.
This more or less boils down to:
-How do make Haskell more welcoming and tolerant to newer users without sacrificing the good in Haskell’s snob values?
-The drawback of snobbery and purism in Haskell is a focus on the theoretical, the abstract, as opposed to the tangible, concrete, and real. While I’m one to quickly defend Haskell as a pragmatic language (lots of loopholes in Haskell’s semantics, Meta’s Sigma, your Pact contract language, Mercury’s Haskell backend, etc), Haskellers put a lot of value in abstract libraries in Haskell, to the extent that actually using these libraries to make things is undervalued.
Considering this, does Haskell need more “animal” (the term I originally used was monkey ) values, or can it simply crystallize them in having more “animal” users? If we want more “animal” users, how do we integrate them into the community without causing undue friction or destroying Haskell’s snob values?
Noticeably and usefully, I think others have already picked up on this conversation strand independently, so I hope I’m not necessary for discussing this topic.
P.S. @emilypi
Perhaps there was a misunderstanding between you and Nick as well? I saw part of what happened and was shocked that the ex-CTO of Haskell Foundation came into the NeoHaskell Discord and was taking a look around.
I noticed your comment about naivete, and the first thing that came to mind after your postings vanished, was that Nick had gotten upset and deleted all your messages.
I went and asked him about it, and he told me he had sent you a reply to your comments, perhaps via the warn-bot, as your comment was breaking server rules.
As it turns out, you have DMs on Discord blocked, so it’s possible Nick was being more conciliatory, and that when we had discussed your drop-in, he said that he considered your comments helpful and that you were welcome to come back.
Of course, I’ll point out that activity on the NeoHaskell server is relatively subdued right now, and that Haskell Foundation already has members there, so it might not be worth your time.
I know you’re not non-binary, I had gotten Mx confused with Mz, which some people use as a way of address for women because it doesn’t mention the person’s marital status.
At least I know how to address Hecate in a formal manner now.
I’m just trying to show the proper respect to the ex-CTO of Haskell Foundation, and the freaking problem with English is that every time you try to address people formally, it sounds sarcastic. And yeah, you’re ex-CTO, seasoned Scala dev and I’m pretty terrified of you.
This “terminological specificity” (and telling learners to stop reading all those ghastly monad tutorials!) could actually work, though it would probably have a better chance of working if the Haskell logo:
…didn’t place such an emphasis on the monadic interface:
…perhaps he didn’t have to: he would have most likely seen callbacks used in C++, so he would only had to realise that Haskell’s monadic binding operator needed one as a second argument to then understand what the monadic interface is - a variant of CPS, with the callback continuation-passing being confined to (>>=), (>>), catch, et al :
-- monadic
m :: M T
h :: U -> M V
(>>=) :: M a -> (a -> M b) -> M b
-- CPS
m' :: (T -> M b) -> M b
m' = (m >>=)
h' :: U -> (V -> M b) -> M b
h' x = (h x >>=)
So for IO:
-- monadic
getChar :: IO Char
putChar :: Char -> IO ()
(>>=) :: IO a -> (a -> IO b) -> IO b
-- CPS
getChar' :: (Char -> IO a) -> IO a
getChar' = (getChar >>=)
putCharK' :: Char -> (() -> IO a) -> IO a
putCharK' c = (putChar c >>=)
…though most would define putCharK' as:
putCharK' :: Char -> IO a -> IO a
putCharK' c = (putChar c >>)
So in order to understand the monadic interface, new Haskellers merely need to understand continuations and CPS - that shouldn’t be too difficult…
I remember my own mental phases back when I was first learning Haskell and Monads. I always think we’re painting an incomplete picture when we say "monads are just bind :: m a -> (a -> m b) -> m b". What this statement misses is the fact that Monads in Haskell are typically used in a way that makes a crucial use of closures. I.e.
do a <- someAction
b <- someOtherAction something (aFunction a)
c <- yetAnotherAction b (anotherFunction 42 a (a ++ b))
So, when you say monads are just (>>=) :: m a -> (a -> m b) -> m b and then explain monads in terms of someAction >>= someOtherAction >>= yetAnotherAction, IMO it fails to deliver why monads are such a big deal in Haskell. Like most things in Haskell, first-class functions (and in particular closures) with a very lightweight syntax as well as Haskell’s type inference are actually doing most of the heavy lifting and not really (>>=) per se. So, I’d say it’s not really monads that’s a big deal in Haskell, but the way they mesh with the other features of the language, so when you explain them in terms of (>>=) only, that might sound disappointing. I’d say, in a way, your friend might not be entirely wrong to disbelieve you.
BTW, I suspect that this point I’m making about how monads interact with closures etc. isn’t just a casual remark about language ergonomics. I have a superficial understanding of category theory, but I suspect things like all Haskell monads being strong might be at play here. Most programmers probably couldn’t name it, but if they’re coming from a less expressive language, in effect the “strength” of our monads might actually be what’s surprising and mind boggling to them.
Alright, I’m not @vanceism7, but I’ll address a few of your points:
Perhaps the more traditional presentation of the monadic interface can be made available in a future Haskell standard:
class Functor f where
mapf :: (b -> c) -> f b -> f c
class Functor a => Applicative a where
unit :: b -> a b
⋮
class Applicative m => Monad m where
join :: m (m a) -> m a
⋮
m >>= k = join (mapf k m)
…back to the present: one possible reason for a fixation with (>>=) is that it’s the onlyMonad method allowed to receive the result of a “used” monadic value. I don’t exactly recall where (SO?), but I’ve seen someone suggest that if (>>=) is able to, then so should any other Haskell definition…
…with an early example of this being given by Andrew Gordon in his dissertation:
But I’ve also seen the use of closures also result in confusion, with a classic example being this “multi-edit” SO question: in one “edit”, I believe an argument is made that since the time-access closure is eventually evaluated within the imperative language anyway (e.g. the exec definition in Gordon’s example) then accessing the time directly, without a closure, is referentially transparent.
Closures: use with care…
Indeed, hence the use of the phrase “programmable semi-colon”. But as for that notion of “strength”…some can be more annoying than others:
I always felt that PureScript is a somewhat cleaner Haskell.
PureScript would also be the language I would recommend to anyone who wants to get into programming as a complete novice.
Now I wonder … one could, in theory, build an additional compiler for PureScript along with specific tutorials. That would address parts of the “accidental complexity” of Haskell without trying to re-invent the wheel, completely.
Personally, I would rather stress how evolved the Haskell ecosystem around GHC really is today. I just love the somewhat recent improvements with the haskell-language-server. I am also excited for the new syntax available to deal with records, thanks to GHC9.
So if I were to sit down, trying to address the list of concerns (thanks for sharing them here), I would probably set up some proprietary website that contains practical guides and market a book along. The book would be all about learning haskell and functional programming … and why not a discord server, too.
But even for a project like this: There is already Real World Haskell, free to read online and it allows for commenting, just check Chapter 2. Types and Functions .
I agree, its definitely a very shallow explanation. But I think its important as a starting point because it helps to build an intuition for what Monads essentially do. Do notation is used a bit differently than standard bind, but it also looks imperative enough that I don’t think its too difficult to explain as long as you don’t throw them into the depths of theory.
I feel like maybe so much of the problem with the beginners perception of Haskell is that it appears (atleast it did to me) that in order to use haskell successfully, you must also dive deep into the rabbit hole of category theory to try and understand how everything works under the hood. I didn’t make any real progress with using Haskell until I stopped trying to figure everything out and became satisfied with just learning the usage. That’s been my essential experience throughout the entirety of learning the language, from monads to folds to lenses to effects systems.
There’s the GHC Steering committee. Haskell isn’t GHC de jure, but it is de facto. Because of this, my sense is that the GHC steering committee feels reluctant to spearhead significant changes, particularly backwards incompatible ones. I think the sense is that it might be considered overstepping.
Unfortunately there doesn’t seem to be an active body governing Haskell-the-language. The Haskell foundation is doing valuable work, but is focusing its efforts elsewhere. There was an initiative to update Haskell-the language a few years ago (Haskell2020) but it fell through. Instead, we got GHC2021, a collection of recommended language extensions which kinda sorta fulfils part of the same role.