Stream: ideas

Topic: List.repeat paramters


view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 02:44):

Simple proposal, change List.repeat from a, Nat -> List a to { elem: a, len: Nat } -> List a.

Of course, the exact record field names can change. The main reason I want this change is because I regularly mix up the parameter order when dealing with integer constants. For example, List.repeat 0 1000. You can't tell if that is a 0 filled list of length 1000 or a 1000 filled list with 0 elements. I think this is a case where being more explicit will help avoid bugs/annoying debugging.

view this post on Zulip Joshua Warner (Mar 06 2023 at 02:53):

Slight tangent - but Rust Analyzer adds helpful parameter name annotations to call sites (at least in VSCode). If the Roc editor did the same, does that solve this (without adding extra typing)?

view this post on Zulip Luke Boswell (Mar 06 2023 at 02:54):

I like the idea. I guess you lose the ability to use easily in a pipeline though. "-" |> List.repeat 10 etc

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 04:02):

I feel like List.repeat is pretty rare in pipelines except at the beginning, but I could definitely be wrong for other people's use cases

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 04:03):

Also, annotations would theoretically help, but I prefer to have it directly in the text file because who knows what editor i am using, if I have the lsp, and how it is configured. every parameter name at a roc call sight might look really odd and hard to read given the syntax.

view this post on Zulip Nick Hallstrom (Mar 06 2023 at 04:43):

Personally I’ve ran into this exact same confusion with parameter order for List.repeat or List.fill or whatever the language I’m using happens to call it multiple times so I’d love this change. Definitely outweighs being able to pipe IMO

view this post on Zulip David Mell (Mar 06 2023 at 04:43):

An approach that could continue to support pipelines would be to keep 2 separate arguments, but require that they be wrapped in tags. E.g. [Elem a], [Len Nat] -> List a

Admittedly, it feels a bit strange to use tags in a context where there is only one allowed value.

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 05:41):

Interesting then in a pipeline, you would do: "-" |> Elem |> List.repeat (Len 10)

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 05:41):

Of course with "-" coming from some previous pipeline stage instead of by constant

view this post on Zulip Luke Boswell (Mar 06 2023 at 06:16):

Is there a name for this kind of pattern? Where you pass in a configuration record instead of invidiual parameters to a fn?

Maybe there is a natural rule for when this is a good idea?

If we look at some other examples; would these work too?

Actually.. This gave me an idea..

What about a, { len: Nat } -> List a?

You can then do List.repeat 0 {len: 1000} and "-" |> List.repeat {len: 100} etc. Resolves your original concern, and is a pattern that can be easily repeated.

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:19):

I don't think this is an issue for most of those string function, at least not as bad.

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:20):

With roc's focus on pipelining, it is expected that the first argument of a function will be what is expected to be pipelined. The core data being operated on.

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:20):

As such, Str.split first arg will be the data that is being split.

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:20):

etc

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:21):

With List.repeat on the other hand, it doesn't really have core data that I would expect to be pipelined in

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:21):

I could see someone picking either order for the arguments

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:22):

calculateLen ... |> List.repeat "x" or calculateValue ... |> List.repeat 100

view this post on Zulip Luke Boswell (Mar 06 2023 at 06:23):

What do you think of a, { len: Nat } -> List a though?

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:24):

With just one field in the record, it feels weird that it isn't a tag. I would probably write a, Len Nat -> List a, but otherwise, I definitely like the idea. That said, if a lot of functions change to an api like this, I think the record syntax may make more sense due to cases with multiple named args.

view this post on Zulip Luke Boswell (Mar 06 2023 at 06:25):

Yeah another one for example; List.intersperse : List elem, {sep : elem} -> List elem

view this post on Zulip Luke Boswell (Mar 06 2023 at 06:26):

Not sure why that one is elem instead of a in the docs... we seem to be switching randomly for some reason

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:27):

Probably a different person wrote the function signature.

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:27):

Why would you do it to List.intersperse? types are clear and there is nothing that could get mixed up?

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 06:28):

haha, though the example in the doc is backwards. Though it may have been written before we had pipelining and updated all of the function arg orders.

view this post on Zulip David Mell (Mar 06 2023 at 06:39):

I know this is silly, but I made a version with tags that supports either order:

repeat : [Elem a [Len Nat], Len Nat [Elem a]] -> List a
repeat = \param ->
  when param is
    Elem elem (Len len) | Len len (Elem elem) -> List.repeat elem len

"-" |> Elem (Len 10) |> repeat

view this post on Zulip Luke Boswell (Mar 06 2023 at 06:40):

Tags are awesome. :grinning:

view this post on Zulip dank (Mar 06 2023 at 10:16):

are there any overheads caused by passing records instead of plain values?

view this post on Zulip Ayaz Hafiz (Mar 06 2023 at 13:53):

nope

view this post on Zulip Richard Feldman (Mar 06 2023 at 13:55):

David Mell said:

An approach that could continue to support pipelines would be to keep 2 separate arguments, but require that they be wrapped in tags. E.g. [Elem a], [Len Nat] -> List a

I think we should try this!

view this post on Zulip Richard Feldman (Mar 06 2023 at 13:55):

I don't know of any precedent for any other language trying it, so the only way we'll find out if it's nice or not is if we try it :big_smile:

view this post on Zulip Richard Feldman (Mar 06 2023 at 13:56):

and on paper it seems to have the properties we're looking for:

:check: clearly disambiguates which argument is which
:check: works in pipelines

view this post on Zulip Richard Feldman (Mar 06 2023 at 13:56):

we also have a bit of precedent for using tags to label arguments with List.range

view this post on Zulip Richard Feldman (Mar 06 2023 at 13:56):

(of course there it's multiple tags, so a more common use of tags, but it's something where you see tags being used at the call site)

view this post on Zulip Brendan Hansknecht (Mar 06 2023 at 15:38):

dank said:

are there any overheads caused by passing records instead of plain values?

Technically, yes. In these cases there shouldn't be, but this isn't universally true. It is a complex picture that is hardware dependent and depends heavily on the exact types. For most small records it should be the same cost. For medium records, it depends, but i would lightly expect raw arguments to do better. For large records i would expect the record to could be faster than the raw args.

Note: in most cases i would not expect a roc user to ever think about this cost. The only time I would expect it to make a real difference is in passing around a large config to a ton of functions or recursively. That config could be put in a record to pass it around as one value instead of many. That could alleviate register pressure and make the program run faster.


Last updated: Jun 16 2026 at 16:19 UTC