Hello everyone :wave:.
As per this discussion we noticed that roc's standard library (builtins) doesn't have a clamp equivalent. Documentation for clamp in Elm here.
What does clamp do? Clamp allows you to "constrain" an arbitrary number between a lower and an upper limit. 10 clamped between 20 and 100 is 20. 50 clamped between 20 and 100 is 50. 130 clamped between 20 and 100 is 100.
Is clamp a clear enough name?
Could we think of alternatives. for instance between , constrain, inRange ?
In terms of API design I can think of at least two approaches:
Approach 1 (equal to Elm)
API signature:
clamp : Num a, Num a, Num a -> Num a
clamp = \value, lower, higher -> ...
Usage: myValue |> clamp 0 100 -or- clamp myValue 0 100
Approach 2 (inspired by roc's List.sublist)
API signature:
clamp: Num a, { min: Num a, max: Num a } -> Num a
clamp = \value, { min, max } -> ...
Usage: myValue |> clamp { min: 0, max: 100 } -or- clamp myValue { min: 0, max: 100 }
For both these proposals I am a bit worried of name clashing with the new Num.min Num.max functions. That is why I would be okay to use argument names such as lower, higher.
Let me know what you think of these proposals and which one would be more "rociomatic" :rock_on:
Thank you!
Is clamp a clear enough name?
clamp may indeed not be familiar to non-native English speakers, some more suggestions limitTo, limitRange, limitToRange.
I prefer appoach 2. Name clashing may indeed be annoying with min and max. I think I prefer low and high as potential alternatives.
:thinking: I wonder if we should worry about non-sensical inputs, such as 15 |> clamp { low: 20, high: 10 } <-- this would .. :thinking: return what :sweat_smile::sweat_smile:
Which makes me think that with approach 1, clamp could be "self-healing"
In the sense that both these would work
15 |> clamp 10 20 == 15
15 |> clamp 20 10 == 15
Hmm, it could be reasonable to return a Result, because in the case that you are executing something like { low: 20, high: 10 } something is likely going wrong in your code and you want to be aware of it
The alternative to not having the Result would be doing what Elm does where, if I read correctly, if you provide such non-sensical input, the output is the same as input, triggering the user to go check what is he doing wrong.
On one hand I love the Result construction, but on the other hand they simply aren't free, both in terms of performance, as well as code flow.
We can see examples of this Result avoidance pattern for instance in List.set where it states: "If the given index is outside the bounds of the list, returns the original list unmodified."
We could have decided to use a Result [OutOfBounds] there, but this behavior is also fine (a user should always unit test). I think we only leverage Result when we can't possibly provide a sensible return value. For "clamp" return the user's original input is sensible enough, I think
Yeah, the performace argument is good. I wonder if we should add clamp and clampChecked. It seems that this function could be commonly used in data analysis, and low cost assistance in ensuring your analysis is correct seems valuable.
Fábio Beirão said:
The alternative to not having the
Resultwould be doing what Elm does where, if I read correctly, if you provide such non-sensical input, the output is the same as input, triggering the user to go check what is he doing wrong.
Elm will not give back the original value. It will first check lower bound, and then upper bound. E.g. clamp 50 { low: 200, high: 100 } will return 200. clamp 250 { low: 200, high 100 } will return 100.
The same happens in rust in release mode. In debug mode there it will panic
In zig it is safety checked undefined behavior (using assert)
Haskell does the same as elm I think
I don't know what would be best, just throwing the info out here
We also have the option to just limit to the range ignoring order. As in clampBetween x (10, 20) == clampBetween x (20,10). Not sure if that is more reasonable though.
I think the underlying issue is that I am thinking of clamp as "restrict this number x between these two other numbers"
But when we invert the arguments to clamp, then the behavior becomes "prevent this number x from ever being between these two numbers"
Therefore maybe what emerges is indeed two functions instead of one
"restrictToInterval" (and here indeed (10, 20) is the same as (20, 10))
"restrictFromInternal" (and once again, (10, 20) versus (20, 10) would have the same outcome/desired behavior)
If we go with the two functions with clear names and expectations, then I would be okay with the simpler Num a, Num a, Num a -> Num a signature for both of them
Aside, we may want follow list.range and make it clear if each side is inclusive or exclusive.
If we use a record, we could make the min and max optional.
For example, if you want to restrict num so that it's above some lowerBound, you could do this:
num |> Num.clamp { min: lowerBound }
I'm not sure if others feel this way, but I like that because using Num.max often feels wrong to me at first:
num |> Num.max lowerBound
# It always twists my brain for a second that a lower bound needs Num.max, not Num.min
The only problem is that it seems like right now, you can't define a function where optional fields refer to other arguments, but I'm not sure whether that was intentional decision:
# doesn't work
clamp = \x, { min ? x, max ? x } ->
# ...
Aside, we may want follow list.range and make it clear if each side is inclusive or exclusive.
I may be missing something but how does a clamp with exclusive boundaries work?
"restrictToInterval" (and here indeed (10, 20) is the same as (20, 10))
"restrictFromInternal" (and once again, (10, 20) versus (20, 10) would have the same outcome/desired behavior)
restrictToInterval seems reasonable. I'm having trouble thinking of a usecase for restrictFromInternalthough.
num |> Num.clamp { min: lowerBound }
I do like the way that looks :)
I don't know why, collision detection/prevention comes to mind :sweat_smile: as in restrict this entity from ever occupying the same coordinates as this other entity, but I might be stretching it a bit
Uhu, right now it does not seem like such a common operation that it justifies being a builtin
I can also imagine a drag-drop scenario where you could use excludeFromInterval to prevent the user from entering a critical region of the UI
The only problem is that it seems like right now, you can't define a function where optional fields refer to other arguments, but I'm not sure whether that was intentional decision:
It's not my area of expertise but supporting that could be difficult.
I haven't tried it, but this might work?
clamp = \x, config ->
{ min ? x, max ? x } = config
# ...
Yeah that works!
nice! Can you open an issue for that? I think it should be possible to make both work
restrictToInterval would come with a performance cost over clamp. The min and max is already set with clamp. With restrictToInterval you need to check which is which.
I wouldn't worry about the perf cost. It should be branchless or can be made branchless. If the values to clamp by are constant, it will inline anyways. Also, someone can write the faster version manually if somehow clamp is the bottleneck of the program.
I may be missing something but how does a clamp with exclusive boundaries work?
What is the value of restrictToInterval 1 {min: 1, max: 10}? Is it 1 or 2? As in, are we containing within or bringing to. Maybe that isn't actually a confusion in practice. Just came to mind due to the API.
Riiight, I think it'll be ok in practice
I agree that the intervals should always be inclusive as in [ 1..10 ]. In other words: the output of restrictToInterval should always be one of the inputs
Sounds good
restrictToInterval is being implemented, currently with the signature Num a, Num a, Num a -> Num a. Calls look like this: restrictToInterval 1 5 10. I think a record similar to List.range would be better here, so you can instantly see what's what in a call. I propose restrictToInterval 1 { startAt: 5, endAt: 10 }.
I don't think we should go exactly like List.range with Before, At, and After here for now.
Anton said:
I don't think we should go exactly like
List.rangewithBefore,At, andAfterhere for now.
I don't think Before and After even make sense. Maybe with integers, but definitely not with floats. I guess you could take the next biggest representable float, but I doubt that is what a user would want.
Anton said:
restrictToIntervalis being implemented, currently with the signatureNum a, Num a, Num a -> Num a. Calls look like this:restrictToInterval 1 5 10. I think a record similar toList.rangewould be better here, so you can instantly see what's what in a call. I proposerestrictToInterval 1 { startAt: 5, endAt: 10 }.
This was in the discussion above already I think. The question then is what happens if startAt is bigger than endAt?
what happens if startAt is bigger than endAt?
I think we should switch them inside the function if that's the case, so:
restrictToInterval 1 { startAt: 5, endAt: 10 } == 5
# and
restrictToInterval 1 { startAt: 10, endAt: 5 } == 5
Alright, the current implementation does this already, just without the explicit record. I'll change it
Should the names be startAt and endAt? I like low and high or min and max better
are we sure restrictToInterval is a better name than clamp?
this isn't a function I personally use often, but I get the impression that the people who do likely expect it to be called clamp :big_smile:
I use it mostly in game dev. I think for people who know it, clamp is indeed easier to find. But for users unfamiliar to it, I don't really know.
However I think the same is true for List.keepIf (instead of filter) for example. I don't have a strong preference, but I think we shouldn't just pick clamp because every other language uses it
I find it more important that it's easier to understand what the function does for readers vs easy to discover for existing clamp users.
Searching clamp in the docs should point you to restrictToInterval
Should the names be startAt and endAt? I like low and high or min and max better
I value consistency with List.range. I feel like we should crash or error if we use the names low/high or min/max and the order is wrong.
Anton said:
Searching
clampin the docs should point you torestrictToInterval
Which file do I have to edit to add it here?
Anton said:
I value consistency with
List.range. I feel like we should crash or error if we use low/high or min/max and the order is wrong.
Is crashing really the best option? I feel like this could be an easy bug. Swapping the arguments also feels weird because they are named now, but I think it is still better than crashing.
Which file do I have to edit [...]
Anton said:
Which file do I have to edit [...]
yeah, but I mean in the repo :big_smile:
Opps, haha, bad paste, one sec
I found this one 'www/public/different-names/index.html', but not sure if it is generated
Yes, that's the one, it is not generated
Anton said:
Searching
clampin the docs should point you torestrictToInterval
I like this in theory, but the problem is that people don't type clamp, they type cl, see that there are 0 results, and stop typing :sweat_smile:
Anton said:
Yes, that's the one, it is not generated
I'll make the changes there than. In general I think there could be more functions in it
we could make some sort of alias system, but I could see it being confusing - like "why am I getting these results?"
If there are 0 results we should definitely feed the results with synonyms
I buy that, but sometimes there might be 1 (unrelated) result, especially if you're only typing 1-2 letters
we could always include the extra results, like below a horizontal line or something
but the design considerations are kinda tricky here to prevent confusion and/or clutter in the common case :sweat_smile:
like below a horizontal line or something
Side by side could work well I think, unconventional but seems right
Like a second column with synonyms
interesting! Is there enough room in the layout for that though? :thinking:
Definitely enough room on desktop, on mobile not though.
I'm worried about flipping the operands if out of order, since that could indicate a logic/arithmetic error. That's very similar to Go's designers considering but rejecting Python-style negative indexing of lists (i.e. x[-1] returning the last elem), because of concerns that it'd mask off-by-one failures and so on (i.e. the programmer intended to reverse iterate using positive indices, but ended up starting at the second-to-last and ending, via -1 wrap around at the last).
In terms of inclusive/exclusive, would this benefit from a tag based design?
restrictToInterval x { min: Include 5, max: Exclude 10, AdjustTo: NearestBound }
Where AdjustTo could be something like Min/Max/NearestBound/WrapAround/Literal x (which need not be in the range, in case a sentinel value is desired to mark out-of-range cases).
I think clampToRange sounds a little more practical to me: "interval" is a bit mathematically formal, and programming languages tend to use range rather than interval for that notion. Clamp also sounds more like an adjustment to me, whereas restrict ambiguously invokes the notions of adjust or validate-without-change, depending on the reader.
I'm worried about flipping the operands if specified, since that could indicate a logic/arithmetic error.
That's been on my mind as well, it's reasonable to return a Result here...
would this benefit from a tag based design?
I'd like to start simple and expand later if there is demand.
due to floats, I really think we shouldn't use tags
Just should be a call to max and a call to min
Maybe also a check if the range is valid or returning a result.
Though honestly, for usability, I think this would be better to do any of the following than return a result:
from unity docs:
Note: if the minimum value is is greater than the maximum value, the method returns the minimum value.
so must apply the max first, then apply the min which will overwrite the max.
godot doesn't document this case, but its function should have the note:
Note: if the minimum value is is greater than the maximum value, the method returns the maximum value.
so reversed from unity
personally, I think crashing is the best answer here. Reversed is clearly an incorrect program state that we would prefer to alert the user of. That said, I don't think anyone will use a function like this if it returns a result. It is like division and addition where it needs to be convenient. Also, a significant portion of the time, it should be used with static parameters where order can't be wrong.
We could make the default function crashable and add a Checked variant that returns Result.
When using autocomplete (or viewing the docs) it's nice to have the Checked variant right under it, that also makes it clear the other one is unchecked.
That seems like a good compromise. I can update the PR tomorrow
I think clampToRange sounds a little more practical to me: "interval" is a bit mathematically formal, and programming languages tend to use range rather than interval for that notion.
I also prefer ToRange over ToInterval.
I also agree with crashing rather than flipping the args or choosing the max or min.
If I call clamp x min max, then afterwards I expect it to be safe to assume that x >= min and that x <= max. Anything but a crash violates one of those guarantees.
Last updated: Jun 16 2026 at 16:19 UTC