Stream: ideas

Topic: Merge builtin functions via tags?


view this post on Zulip jan kili (Oct 09 2022 at 07:51):

In the conversation on List.range, we decided to merge multiple range-related proposals List.rangeInclusive, List.rangeExclusive, List.rangeBy into one function with a rich input that captures all of that nuance. What if we explored merging other builtin functions in a similar way?

For example, we currently have

List.sortAsc : List (Num a) -> List (Num a)
List.sortDesc : List (Num a) -> List (Num a)

but why not

List.sort : List (Num a), [Asc, Desc] -> List (Num a)

view this post on Zulip jan kili (Oct 09 2022 at 07:55):

From

Str.replaceEach : Str, Str, Str -> Result Str [NotFound]*
Str.replaceFirst : Str, Str, Str -> Result Str [NotFound]*
Str.replaceLast : Str, Str, Str -> Result Str [NotFound]*

to

Str.replace : Str, [Each, First, Last], Str, Str -> Result Str [NotFound]*

view this post on Zulip jan kili (Oct 09 2022 at 08:07):

I see a lot of sets of related builtins like Hash.addBytes/addI8/addU16/add* or Num.toU8/toU8Checked/to* that seem like strong contenders for this, but their return types are inherently different (either containing a concrete numeric type or sometimes wrapping the return type in a Result for fallibility). Therefore, I think the criteria here should be "builtin functions with related functionality, similar names, and identical return types should be merged by adding a tag union input parameter".

view this post on Zulip jan kili (Oct 09 2022 at 08:08):

Thoughts? Does this look very elegant to anyone else?

view this post on Zulip jan kili (Oct 09 2022 at 08:14):

From

[1, 5, 2, 4, 3] |> List.sortAsc
"1 5 2 4 3" |> Str.replaceEach " " ", "

to

[1, 5, 2, 4, 3] |> List.sort Asc
"1 5 2 4 3" |> Str.replace Each " " ", "

view this post on Zulip jan kili (Oct 09 2022 at 08:15):

(in these examples, the only syntactical cost to app developers is the addition of a single space character, but I'm optimistic about the conceptual benefits of self-documenting alternatives and the flexibility of toggling behavior with a tag variable instead of switching function calls)

view this post on Zulip Anton (Oct 09 2022 at 11:03):

It does noticeably complicate type signatures...
Lots of new roc programmers will not be familiar with anything like our tags. I think this will add a noticeable difficulty to getting started with roc.

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:18):

yeah my default preference is to have more, simpler functions

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:18):

I think List.range is unusual in that it is notoriously easy to accidentally misuse

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:19):

so I think in its case, there's a benefit to having one function with arguments that make the subtly different alternatives clear

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:19):

which is not a problem most functions have

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:22):

incidentally this preference is part of why Roc doesn't have default arguments

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:23):

languages with default arguments typically would let you do either Str.join strings or Str.join strings ", "

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:24):

with the second argument being optional and defaulting to either "" or " "

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:24):

but I'd rather have Str.join and Str.joinWith that each do one thing

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:25):

instead of having the more complicated function being the only thing you can reach for, with the simpler Str.join : List Str -> Str being unavailable

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:26):

I like being able to reach for something with the simplest type that will get the job done for me, and only looking further for something more complicated when the simplest thing won't meet my needs (which it often will)

view this post on Zulip Richard Feldman (Oct 09 2022 at 11:31):

it also facilitates a nicer documentation reading experience: I can learn about the simplest operation by reading docs that are scoped narrowly too, and then discover the more complicated ones by links like "To insert a string in between each of the joined strings, use Str.joinWith"

view this post on Zulip Brendan Hansknecht (Oct 09 2022 at 14:49):

I think it is also very important to note that tags can have a costs. For example, if List.range doesn't get inlined, you will be spending extra time twiddling with memory on every function call to create the tags. Then you will be spending extra time in the function matching on the tag and pulling the data back out. As such, i would be careful about doing this conversion to any complex functions. Otherwise we would be accidentally slowing down every function we do this too.

view this post on Zulip Richard Feldman (Oct 09 2022 at 15:23):

to be fair, I think it's safe to assume it'll get inlined

view this post on Zulip Richard Feldman (Oct 09 2022 at 15:24):

(if it doesn't automatically, we can make sure it does because it's a builtin)

view this post on Zulip Brendan Hansknecht (Oct 09 2022 at 16:53):

For List.range sure, it is small. For other builtins that are larger, it may not be reasonable to inline them.


Last updated: Jun 16 2026 at 16:19 UTC