Stream: beginners

Topic: Monadic culture


view this post on Zulip Mateusz Zugaj (Mar 27 2024 at 16:47):

Hi all, I'm a total beginner to Roc, but I know some other functional programming languages. I was reading the FAQ and I got really curious about the cultural implications of monads.
From https://www.roc-lang.org/faq.html:

Considering that these are the only three options, an early decision in Roc's design—not only on a technical level, but on a cultural level as well—was to make it clear that the plan is for Roc never to support HKP. The hope is that this clarity can save a lot of community members' time that would otherwise be spent on advocacy or arguing between the two sides of the divide. Again, it's completely reasonable for anyone to have a different preference, but given that languages can only choose one of these options, it seems clear that the right choice for Roc is for it to never have higher-kinded polymorphism.

Obviously I am on the HKP of the divide that allows defining Monads inside the language. I am so deep in my bubble that I cannot understand why it makes a difference. I thought the divide was between functional and imperative paradigms, not between monads and antimonads. Could someone explain where this dismay for a language feature comes from, so I could better understand both sides of the coin? Thanks and best wishes.

view this post on Zulip Fabian Schmalzried (Mar 27 2024 at 17:22):

I think there are several points, but for me it is keeping the language small. Roc is a descendant of Elm, which also does not have HKP. What I love about both Roc and Elm is that it is so small that I can basically keep the whole language in my head. Also it's easier for beginners. I tried to learn Haskell, but that monad stuff was too strange to me. I just did not understand the purpose of it. After learning Elm, it was quite easy to see the common pattern, that is called a monad. Not a big difference in practice, if I use a special bind operator or a specific function for the type, that can also have a nice name.

view this post on Zulip Fabian Schmalzried (Mar 27 2024 at 17:25):

Also there was something about the type system not being always decidable with HKP? But I'm not sure about that.

view this post on Zulip Norbert Hajagos (Mar 27 2024 at 18:47):

For me it means a more accessible language. I got into Roc because it is a simple language and I think that is a big benefit. I had no functional background but was able to learn it easily. Basically if you have seen recursion and pattern matching, there isn't much extra learning to do with Roc. I spent like a whole day trying to understand Monads (because I got the impression that I need to do that before I can learn Haskell, or any functional programming). Didn't succeed, so didn't learn Haskell.
So I see the divide as "Mondas scare off non functional programmers, but make hardcore (to me that's the right word) functional programmers happy". Since the goal of Roc is to be widely adopted, it needs to appeal to non functional programmers.

view this post on Zulip Brendan Hansknecht (Mar 27 2024 at 19:15):

I'm a bit confused. I would consider Task a monad. So I would say that roc has monads, just not higher kinded types.

view this post on Zulip Brendan Hansknecht (Mar 27 2024 at 19:16):

Higher kinded types are required for generic interfaces that work with all monads, but they aren't required for monads. It just means that the bind-like methods are specific to each monad.

view this post on Zulip Brendan Hansknecht (Mar 27 2024 at 19:17):

For example Task.await is a specific implementation of bind.

view this post on Zulip Mateusz Zugaj (Mar 27 2024 at 21:10):

Brendan Hansknecht said:

Higher kinded types are required for generic interfaces that work with all monads, but they aren't required for monads. It just means that the bind-like methods are specific to each monad.

Yeah, by "monads" I meant being able to define the whole Monad typeclass/trait/interface inside the language, so that Task and Result and IO can use the same interface. I would explain it as the interface of doing stuff in a context, be it async context, context where an error can be thrown or the context of the standard input/output. Each statement is not only doing what it says it does, it also has an additional effect in the background context.

view this post on Zulip Norbert Hajagos (Mar 27 2024 at 21:15):

Noew I'm not sure if the message was for me Brendan, but I'll send it anyways. My point is that I don't need to know that it is a monad. I admit, it may not be the best mentality, but at this stage of my fp journey, I don't want to learn them. It is hard enaugh to shift my programming thinking to expressions with immutability where there were statements before. I'm sure it would be awesome to create my own functions that would work withe the proposed ! operator (if I understand correctly that would require HKP) instead of only Task.await (or was it attempt?) and Result.try having access to it, but I am just not there yet.

view this post on Zulip Isaac Van Doren (Mar 27 2024 at 21:25):

I think one of the main reasons is that with a Monad type class, there would likely be multiple versions of libraries that either use arbitrary monads or do not. These would not work well together and would fracture the eco system. Roc prefers a more direct style, and by default everyone will use that style if the other one is not an option.

view this post on Zulip Mateusz Zugaj (Mar 27 2024 at 21:27):

Norbert Hajagos said:
Yeah, the !operator and the await operator are doing a similar thing and I don't like code repetition. But thh practicality appears when you have to deal with two contexts at once, e.g. you have an async function that can throw an error. When you would return a Task in a Result it is always possible to turn it into a Result in a Task. The same with Result and IO actions that can fail and write it in the console. These examples could be implemented for arbitrary Monads, so you would get all such converters for free.

view this post on Zulip Mateusz Zugaj (Mar 27 2024 at 21:30):

Isaac Van Doren said:

Roc prefers a more direct style, and by default everyone will use that style if the other one is not an option.

Thanks for the answer, it makes sense that not everyone would implement a Monad typeclass if they wouldn't want to use it as a monad.

view this post on Zulip Brendan Hansknecht (Mar 27 2024 at 22:29):

Just to clarify, ! and Task.await are not doing a similar things. They are the exact same thing.

! desugars to |> Task.await.

An extra note. We are consider making ! a generic desugaring. As such, it really is equivalent to a higher kinded bind operator.

view this post on Zulip Brendan Hansknecht (Mar 27 2024 at 22:38):

But thh practicality appears when you have to deal with two contexts at once, e.g. you have an async function that can throw an error. When you would return a Task in a Result it is always possible to turn it into a Result in a Task. The same with Result and IO actions that can fail and write it in the console. These examples could be implemented for arbitrary Monads, so you would get all such converters for free.

100%. We require more explicit support and lose some forms of composability by not support higher kinded types. We also avoid a lot of compiler complexity and specialization costs. It is definitely a trade off.

Looking at your specific examples:


Last updated: Jul 06 2025 at 12:14 UTC