In our Rust glue we have a bunch of places where we do something like this:
|> \b -> List.walkWithIndex tags b generateEnumTags
if List.walk
(and the WithIndex
variants etc) took the state as the first argument, instead of the list as the first argument, then this could have just been:
|> List.walkWithIndex tags generateEnumTags
it seems like this comes up pretty often, and in retrospect state, ... -> state
fits the general pattern of pipeline-friendliness
(of having the first argument be the same type as the return value)
does anyone have thoughts one way or the other?
Usually my states for walking/folding
are in some zero/mempty state and as I walk trough the list I build out state. Hence moslty I construct that initial state by hand and pass in the list. I haven't reused the state after the fold, just extract the final result out of it.
hm, but in those cases do you build up the list using pipelines? :thinking:
(or in other cases)
because if not, then argument order doesn't matter either way in those situations :big_smile:
Seems nice. I also like that it reads better. state |> List.walk list func
reads like “walk the list”. With the current version it reads like “walk the state”
On the other hand I often construct the list to walk via some series of pipelineing. Then I would need to define an intermediate variable for list. Though that might be fine in practice? Might even be good practice to separate the construction of the list and the consumption of the list, explicitly
do you have some examples of that?
constructing the list in a pipeline and then walking it at the end, I mean
does anyone have thoughts one way or the other?
I do think it would surprise the user, the collection is used as the first argument everywhere except here (if it was changed).
Sure, for example parseDraw on line 68 in this AoC solution: https://github.com/Subtlesplendor/roc-aoc-2023/blob/main/day2.roc
Not saying this is necessarily good style though
I always end up using those functions with an empty state. So I end up wanting the list first.
Cause it is build up the list with some transforms and then pipe it into walk
That said, I think most of the code I have written in roc is not really representative of more complex use cases.
I do think it would surprise the user, the collection is used as the first argument everywhere except here (if it was changed).
I also really agree with this one.
I think glue is about the worst case for these functions cause it is 100% about building one gigantic global buffer
I think most code does more transforms and isn't just a single state accumulator that interacts with many various lists it must walk
I would say that for now, it may just be best to define a helper function in glue that handles the arg reordering and enables pipelining.
interesting, now I notice this one function could take the place of all those |> List.walk
uses in RustGlue.roc
:
Str.concatAll : Str, List Str -> Str
because in every case they're walking over a list and then concatenating all the strings onto the end of the string they're building up
That would make way more temporary allocations?
Cause you would do:
buf
|> Str.concatAll (List.map tags generateEnumTags)
while changing generateEnumTags
to return a string instead of appending onto a string?
for sure, but we already do a ton of that in that script :big_smile:
such as every time we append an interpolation
perf seems to be fine in that use case, and if we needed to optimize it we could always do it by hand
For sure. Just clarifying the difference. The code could be greatly simplified if allocations where ignored and we just always did a bottom up building where there was essentially no buffer except at the top level. All other levels would just be a string interpolation. (I think a decent bit of this already happened. My original code was very allocation adverse, but I think the current code is not so much)
yeah glue should end up running with bump allocation for everything, so the cost should end up being a bunch of memcpys and that's about it
Memcpys and higher memory usage, but it should never use enough memory for that to matter.
I almost always start walk with an empty state and often pipe the list in. I agree that having the state first would be surprising to the user and it seems very inconsistent with the rest of the builtins
Richard Feldman said:
hm, but in those cases do you build up the list using pipelines? :thinking:
Usually lists have to come from somewhere, and that is more often than not some sort of a call to outside world. a.k.a. await
In theory if you are doing two consecutive operations on the list, in order to need list pipelined, you should be able to merge it into one.
I do very often pipeline a resulting state into some sort of finalize
function, which extracts relevant data from the state.
Last updated: Jul 06 2025 at 12:14 UTC