This is another youtube inspired question. I have 0 idea if there is a reasonable way to do it, but it was interesting none the less.
Base example:
name <- await (File.read "username.txt")
data <- await (Http.get name)
File.write "response.txt" data
Version in youtube comment:
File.write "response.txt" <- await (Http.get <- await (File.read "username.txt"))
Obviously the youtube comment is not valid, but I was wondering if there would be any merit in trying to make a form of pipelining that works with backpassing. Like ultimately I want to write:
File.read "username.txt"
|> Http.get
|> File.write "response.txt"
This just doesn't work because of needing to await. Backpassing makes a nice version of this, but it requires explicitly naming of every intermediate value. Is there any way/should we add a way to pipeline functions that would normally require backpassing? Is this even really possible? Any thoughts?
haskell uses the >>= as the infix version of await
Can we use await in combination with the pipe operator to hide names for intermediate values, at least when chaining single argument functions?
File.read "username.txt"
|> await Http.get
|> await (\d -> File.write "response.txt" d)
(I may be missing something. Still need to setup Roc.)
Currently no, The last argument to await is a lambda. So the pipe wouldn't make sense. There is no value to pipe.
I'm on thin ice talking about code I did not try.
However, if I understand correctly, the task will be piped to await as first argument. The result of await is another task piped to the next await as first argument.
I did not write a lambda for Http.get because it is already a one argument function, that (I believe) can be passed to await as second argument (as it is in my example, because the pipe operator fills in the first argument.)
Am I missing something?
I have to think about that more and test, maybe this is possible.
what is await?
and Task.await usually returns some Effect that needs to be matched on for Ok or Err
I was awaiting this question - are there docs/guides for how language mechanics like these so newcomers like myself can learn about the intended behaviour? I'm piecing stuff together from docs, the 5 videos and just experimenting so far
I don't mind writing the docs; I just don't know where to start
await is Task.await, just depends how you import it.
fair enough, there are some situations where you can pipe a Task but then other situations where you want to use the reverse lambda
I'm working on a language tutorial at the moment!
The signature depends on the platform that provides Task.await. I assumed the signature is like shown here:
https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md
I agree that just because we can omit names for intermediate values when chaining one argument functions, that does not mean we should. I think it's an interesting question when to use which syntax and why. To answer that, it helps to be aware of all the options.
Currently all platforms use a standard definition of Task. it probably will be a common and shared module in general
So I looked deeper into the await with pipelining example. I think this should work:
Stdout.line "What's your first name?"
|> await Stdin.line
|> await (\firstName -> Stdout.line "Hi, \(firstName)!")
It doesn't currently, but I think that it should.
The big issue with it is that a value is only available to the next Task and not farther down the line. ex:
Stdout.line "What's your first name?"
|> await Stdin.line
# Note how firstname would need to be consumed here but we don't want to use it yet.
|> await (\_firstName -> Stdout.line "What's your last name?")
|> await Stdin.line
# firstName would not be defined for this line.
|> (\lastName -> Stdout.line "Hi, \(firstName) \(lastName)!")
Figured out the correct syntax for the first example:
Stdout.line "What's your first name?"
|> await (\{} -> Stdin.line)
|> await (\firstName -> Stdout.line "Hi, \(firstName)")
Still has the same issue with not being able to use values later on in the function. Also, you still have to name things due to the lambda
Not being able to use intermediate values later down the pipeline when hiding their names is typical for the pipeline operator and not related to using it with tasks. If we need the name later we should not hide it - then we can use backpassing (or normal definitions when not using callbacks.)
When using pipelines with tasks, an explicit lambda is necessary on a callback only if that callback is not already a function with the correct type. So names can only be omitted in cases like above with Http.get. In practice, I would probably use backpassing instead of pipelines with explicit lambdas as callbacks.
In Elm, pipelines with tasks (where await is called andThen) are more ergonomic. Because of currying, explicit lambdas can be avoided more often in Elm. But I feel backpassing provides a good alternative in Roc, enabling us to use named intermediate results later.
I like how backpassing corresponds to the alternative of having interspersed definitions with explicit names when programming without callbacks (as shown in the doc I linked above.)
Last updated: Jun 16 2026 at 16:19 UTC