roc-lang / roc

A fast, friendly, functional language.
https://roc-lang.org
Universal Permissive License v1.0
3.86k stars 284 forks source link

Deprecate the <- backpassing operator #6829

Open smores56 opened 1 week ago

smores56 commented 1 week ago

Following from discussion in the Zulip chat.

With the addition of the ! operator (first PR and proposal) followed by the proposed addition of the ? operator (https://github.com/roc-lang/roc/issues/6828), there's not much value added by the <- backpassing operator anymore. Considering how hard it is for new users to learn, we plan on removing it from the language.

For the sake of a clean transition from a Roc with backpassing to a Roc without, I propose the following plan of action:

evanrelf commented 1 week ago

As a Roc beginner, I find value = task! to be much nicer than value <- task |> Task.await. I think adding a Rust-style ? syntax for Result would be awesome!

I do wonder what will be lost in deprecating the <- syntax, though. Both ! and ? are syntax sugar for the more general pattern of a monadic bind ("and then"):

# Roc
Task.await : Task   a b,   (a -> Task   c b)   -> Task   c b
Result.try : Result a err, (a -> Result b err) -> Result b err
-- Haskell
(>>=) :: Monad m => m a -> (a -> m b) -> m b

This is useful in other contexts, such as parsing. Monadic parser combinators are really wonderful in Haskell. It would be sad to lose this nicer syntax for non-blessed monads (even if we don't use the M word in Roc).

smores56 commented 1 week ago

Unfortunately, though monads can make for very nice abstractions, they tend to make code very hard to engage with for people outside of the FP space. It seems that the general sentiment in the Roc dev team is that we want to avoid allowing people to write code that is hard for most people to understand, which the backpassing operator can do.

The proposal in the Zulip chat about "map2-based record builders" could make tasks like parsing much simpler, by the way. In short, it seems like between record builders, ?, and !, we give Roc devs enough power to write concise and readable code without letting them go too overboard.

evanrelf commented 1 week ago

though monads can make for very nice abstractions

Agree! The most basic aspects of programming in Roc - effects and error handling - are built on them.

they tend to make code very hard to engage with for people outside of the FP space ... It seems that the general sentiment in the Roc dev team is that we want to avoid allowing people to write code that is hard for most people to understand

Agree! I don't think anyone should need to know about monads (the word needn't ever be mentioned!) when using Roc.

The proposal in the Zulip chat about "map2-based record builders" could make tasks like parsing much simpler

Agree! That makes writing applicative parsers (like weaver) much nicer. Unfortunately it doesn't do anything to help monadic parsers (the most common kind).

we give Roc devs enough power to write concise and readable code without letting them go too overboard

I think this is the only part I disagree with. Evidently, without syntax sugar monads aren't concise or readable in Roc. That's why ! and ? are great (and <- to a lesser extent).

I just think other monads come up often enough (whether people realize it or not) that it's worth ensuring the syntax remains concise and readable. I don't care if that's backpassing or something else.

It could be something as simple as making this valid syntax and not changing it with the formatter:

greet =
    Stdout.write "What's your first name? " |> Task.await \{} ->
    Stdin.line |> Task.await \firstName ->
    Stdout.write "What's your last name? " |> Task.await \{} ->
    Stdin.line |> Task.await \lastName ->
    Stdout.line "Hello, $(firstName) $(lastName)!"

That isn't ideal, but it's miles better than this (what you'd get in a backpassing-less world):

greet =
    Stdout.write "What's your first name? "
    |> Task.await \{} ->
        Stdin.line
        |> Task.await \firstName ->
            Stdout.write "What's your last name? "
            |> Task.await \{} ->
                Stdin.line
                |> Task.await \lastName ->
                    Stdout.line "Hello, $(firstName) $(lastName)!"

I'm using Task here, but imagine it's a different monad (parsing, logic programming, string builder, etc.) where there is zero syntactic support. This is what it'd look like, if anyone even bothered to write it.


In summary:

I 100% agree Roc users shouldn't need to know what monads are, but monads are useful regardless (evidently!), and removing all syntax support (whether sugar or formatting or whatever) would make Roc less concise and readable, either because the syntax would be horrible or because developers would have to use less concise and readable representations than monads.

evanrelf commented 1 week ago

Anyways, I don't mean to argue about this forever. Just wanted to make a good argument in favor of some kind of syntax support for monads. But I understand if Roc developers are not interested.

smores56 commented 1 week ago

Yeah, you are right about the value of supporting monads via a generic syntax, but it seems to explicitly be a anti-goal of Roc to not support it. The dev team prefers to localize these sorts of discussions to the Zulip chat, so if you want anyone besides me and a few others to hear your argument, then please throw something in the related Zulip thread!