Stream: ideas

Topic: tutorial backpassing explanation


view this post on Zulip Anton (Nov 13 2023 at 13:25):

In response to this old topic, what do you all think about starting the backpassing explanation in the tutorial with a List.map (instead of await) example.

It would be strange to write real code like that but I think it noticeably improves cognitive load.
After the List.map example we can again talk about the await snippet above the backpassing section but now we have a learning curve that is less steep.

view this post on Zulip Richard Feldman (Nov 13 2023 at 15:19):

:thinking: is there something other than List.map we could use? It's a reasonable idea, I just don't love starting with a weird example :sweat_smile:

view this post on Zulip Brendan Hansknecht (Nov 13 2023 at 15:46):

Would it be more understandable if we used result directly instead of task? Technically they are the same API.

view this post on Zulip Brendan Hansknecht (Nov 13 2023 at 15:46):

But result and try may be more familiar

view this post on Zulip Anton (Nov 13 2023 at 15:49):

I think Result is definitely better than Task, but for those not used with functional programming, List.map is a lot easier to get I think.

view this post on Zulip Anton (Nov 13 2023 at 15:50):

I'm also definitely still open to for better alternatives to List.map

view this post on Zulip Brian Carroll (Nov 13 2023 at 15:58):

Yes List.map is the easiest to understand, but it's also true that if we put something in the tutorial, it will be seen as an example of idiomatic normal code. And this isn't.

I think it's easy to understand wanting to chain a sequence of operations that could fail. And Result.try is good for that.

view this post on Zulip Richard Feldman (Nov 13 2023 at 16:04):

I think Result.try is good in that it's a good fit for backpassing style, but I'm not sure if we want to introduce Result.try at that point in the tutorial to someone who's potentially new to FP :sweat_smile:

view this post on Zulip Declan Joseph Maguire (Nov 14 2023 at 10:31):

I don't think using an unnatural example is necessarily a bad thing, as long as it's overtly unrealistic, something that immediately and only makes sense as a demonstration of the underlying mechanic, even for a naive learner. I think I'd like to see a concise idiomatic use of backpassing, then explain it with a contrived but ultra-clear example, and finally a realistic example.

view this post on Zulip Eli Dowling (Nov 14 2023 at 19:53):

I do think the explanation could be better, it took me a couple of reads to really get it. I think being explicit about why you would want to use it and using async as the example is the best part of the current explanation. It following the async await part of the tutorial makes the flow in really good. Like at the end of that bit my main thought is "wow the async await syntax is going to be callback hell" and then "oh i guess this is trying to fix that" I don't think i would have had any hope of getting it if it wasn't for that.

view this post on Zulip Eli Dowling (Nov 14 2023 at 19:56):

I think the key thing is indicating in the code snippet that there is now this new context, that you are calling a new anonymous function each time. This may be best done visually.
Here are two examples of what i mean
image.png
image.png

view this post on Zulip Eli Dowling (Nov 14 2023 at 20:00):

Overall i think different people will understand it best differently, but one thing that would help everyone is a larger collection of examples. I think just having a little "quick fire section" that shows it being used with async await, results and the list.map example (acknowledging that the last one is a bit silly ofcourse) would really help, that's two that are legit uses within roc, and one that provides a fammilar example that users of other languages instantly understand.
These examples would ideally be followed or preceded by their equivalent versions using anon funcs.

view this post on Zulip Eli Dowling (Nov 14 2023 at 20:03):

I think by far the worst thing about the current explanation is that it doesn't include an example that shows nested callbacks

result<- await sometask
result2 <- await otherTask
"results \(result) \(result2)"

compared to

 await sometask (\result ->
    await otherTask  (\result2 ->
        "results \(result) \(result2)"
    )
)

Thereby actually showing why the syntax is useful

view this post on Zulip Anton (Nov 15 2023 at 09:58):

The visualizations are a good idea!
A nested example is shown somewhat above the backpassing section.


Last updated: Jun 16 2026 at 16:19 UTC