I wrote a draft of an article about the new !
suffix - any feedback welcome! https://docs.google.com/document/d/1XKZEtOg5efJFnzXxwY2rPRE64H796lCrt7H_j0340P4/edit?usp=sharing
as previously discussed, I plan to post this on a personal site rather than as any kind of official announcement on roc-lang.org
Your javascript syntax example has a then
instead of an afterwards
for the last callback operation
thanks, fixed!
Somewhat off topic but the discussion in the article about the output format jogged my memory:
The best async primitive I've run across in any language is let-flow
in Clojure's aleph library. In Clojure the let
form lets you make a series of bindings and then use it within the body works a lot like let...in
in Elm. Aleph uses some macro magic to sort the bindings topologically and dispatches independent tasks in parallel. I think of it basically every time I read about an async await primitive which generally have semantics that precludes this type of optimization.
I understood that Roc desugared into callbacks and from that assumed it wouldn't be possible to get let-flow style optimizations but in reading this it occurs to me that the output is a data structure so this sort of optimization might be possible.
sort the bindings topologically and dispatches independent tasks in parallel
that's cool! we have record builder syntax for this use case - the syntax is implemented, but we don't have any parallel-capable platform runtimes to use it on yet :big_smile:
(that's because we need to make Task
a builtin to enable that, which hasn't happened yet)
Are there any blockers for making Task a builtin? Does it just need implementation?
I could see how it could be done with that syntax but it'd be nicer if it worked everywhere without having to think about it. If Task
is a builtin that the compiler knows about then the compiler could also know where task values are used. Extending the example syntax to include an id
per op and dependencies: id[]
would give platforms the information needed to dispatch in parallel but they could just head of line block like they currently do if they ignore the information.
That sounds very cool for a library, but not so much as the default behaviour for a language. Based on a sofware unscripted podcast episode (i think it was "making jit-ed code run faster") this seems like it would work a nice number of times, but when it wouldn't, it would be really bad, because you need to get around it somehow. I dont know how often that comes up in clojure though, since it doesn't have the same goals as parralelising c++ code. For Roc, I think Task builtins would be better for concurrency (ofc the platform would need to declare wheather it supports parralelism or not)
Luke Boswell said:
Are there any blockers for making Task a builtin? Does it just need implementation?
no blockers! No one is working on it yet though
yeah regarding parallelization, I think it's really important to be able to have confidence that your code is running in parallel vs not
one of the reasons records were chosen explicitly for that syntax is because it's really obvious syntactically that when you're building a record, the fields can't depend on each other (because the record hasn't been built yet)
and for them to run in parallel, they can't depend on each other
Haskell's applicative do
also does it automatically based on compiler analysis, but I prefer it to be explicit in syntax
I thought there was a blocker related to effects and dependences. Though maybe that is only an issue once we have the effect interpreter version of task?
yeah basically task as builtin (on its own) just requires:
Task
module to builtinsEffect
in hosted
modules we generate Task
Result
)making Task.map2
able to parallelize has other blockers, but at a minimum it requires task being a builtin, which doesn't have blockers
separately, once we have module params we can (and should) replace hosted
modules with declarations in the platform
module, but that's blocked on task being a builtin
that change might be the dependencies thing you're thinking of; we can't make that change without module params or else the ergonomics would be terrible :sweat_smile:
Instead of generating
Effect
inhosted
modules we generateTask
Ah yeah, that would definitely fix any Effect
and Task
related dependency issues.
Can't have a dependency on Effect
if Effect
doesn't exist
Last updated: Jul 05 2025 at 12:14 UTC