Re: Add builtin function for min(max())
On the topic of NaNs: I think in the face of ambiguity, we should refuse the temptation to guess and raise ValueError when the values given are not comparable to the point of clearly determining whether `value` is or isn't within `minimum` and `maximum` or which of the two bounds it exceeds. It is the programmer's responsibility to ensure they supply arguments that make sense, and non-numbers should be considered exceptional behaviour: as such, the programmer that anticipates those kinds of values should handle them exceptionally. About the name: clamp seems to be the most favoured name and the discussion convinced me it's the one that should make it to the eventual proposal. About the stdlib/builtin status: `min(max(x, min_), max_)` is a common enough idiom for a simple enough function that I think it is definitely beneficial and in no way dangerous to have, and that any status other than builtin would incur too much overhead for something so small. I risk claiming that most calls to max() and min() occur in modules that do not `import math` (or at the very least, they occur often enough without `import math`), and I believe clamp(), being very similar in scope to those, should benefit from similar status. I don't think clamp is a common enough identifier in non-specific fields to disqualify its implementation as a builtin by itself. By contrast, terms like `min` and `max` are extremely common in pretty much all contexts, and yet we've all survived just fine by typing `min_` and `max_` or even shadowing the names altogether (which I prefer not to do, but is definitely an option). FInally, I'd like to lit another fire: given that `min()` and `max()` accept an arbitrary amount of arguments, and that the signature that seems to be preferred for the hypothetical clamp at the moment sees `value` as the first positional argument, should the function then accept iterables as values for `minimum` and `maximum`? Something along the lines of: clamp(value: Number, minimum: Union[Number, Iterable[Number]], maximum: Union[Number, Iterable[Number]])
On Sun, Jul 5, 2020 at 2:36 PM Federico Salerno <salernof11@gmail.com> wrote:
On the topic of NaNs: I think in the face of ambiguity, we should refuse the temptation to guess and raise ValueError when the values given are not comparable to the point of clearly determining whether `value` is or isn't within `minimum` and `maximum` or which of the two bounds it exceeds. It is the programmer's responsibility to ensure they supply arguments that make sense, and non-numbers should be considered exceptional behaviour: as such, the programmer that anticipates those kinds of values should handle them exceptionally.
+1
About the stdlib/builtin status: `min(max(x, min_), max_)` is a common enough idiom for a simple enough function that I think it is definitely beneficial and in no way dangerous to have, and that any status other than builtin would incur too much overhead for something so small. I risk claiming that most calls to max() and min() occur in modules that do not `import math` (or at the very least, they occur often enough without `import math`), and I believe clamp(), being very similar in scope to those, should benefit from similar status.
I don't think clamp is a common enough identifier in non-specific fields to disqualify its implementation as a builtin by itself. By contrast, terms like `min` and `max` are extremely common in pretty much all contexts, and yet we've all survived just fine by typing `min_` and `max_` or even shadowing the names altogether (which I prefer not to do, but is definitely an option).
Has anyone suggested a .clamp method on the various numerical classes? Since this is a convenience that's easy to do without or implement yourself, I don't think it's important that it's super general and can instantly be used with anything comparable.
On 2020-07-05 10:09, Federico Salerno wrote: [snip]
FInally, I'd like to lit another fire: given that `min()` and `max()` accept an arbitrary amount of arguments, and that the signature that seems to be preferred for the hypothetical clamp at the moment sees `value` as the first positional argument, should the function then accept iterables as values for `minimum` and `maximum`? Something along the lines of:
clamp(value: Number, minimum: Union[Number, Iterable[Number]], maximum: Union[Number, Iterable[Number]])
I think that's overcomplicating it. How often have you actually wanted that feature? If you want bounds that are iterables, just use min/max on them first.
On Sun, Jul 5, 2020 at 6:15 AM MRAB <python@mrabarnett.plus.com> wrote:
clamp(value: Number, minimum: Union[Number, Iterable[Number]], maximum: Union[Number, Iterable[Number]])
Ss ( IsA Zzz What would that return? What if the iterable were two different lengths? If anything, I would accept an iterable for value, and return an iterator. Min() with an iterable is essential a reduce. Clamp() with an iterable would be
I think that's overcomplicating it. How often have you actually wanted that feature?
If you want bounds that are iterables, just use min/max on them first. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/QCP347... Code of Conduct: http://python.org/psf/codeofconduct/
-- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
Sorry, that got sent too soon. On Sun, Jul 5, 2020 at 1:59 PM Christopher Barker <pythonchb@gmail.com> wrote:
On Sun, Jul 5, 2020 at 6:15 AM MRAB <python@mrabarnett.plus.com> wrote:
clamp(value: Number, minimum: Union[Number, Iterable[Number]], maximum: Union[Number, Iterable[Number]])
What would that return? What if the iterables were two different lengths?
If anything, I would accept an iterable for the value, and return an iterator.
min() with an iterable is essentially a reduce. clamp() with an iterable would be a map.
Though then there is the question of what to return for a scalar value: an iterator with one value or a scalar? So maybe these kinds of operations should be left to numpy. -CHB
On 2020-07-05 14:06, MRAB wrote:
On 2020-07-05 10:09, Federico Salerno wrote: [snip]
FInally, I'd like to lit another fire: given that `min()` and `max()` accept an arbitrary amount of arguments, and that the signature that seems to be preferred for the hypothetical clamp at the moment sees `value` as the first positional argument, should the function then accept iterables as values for `minimum` and `maximum`? Something along the lines of:
clamp(value: Number, minimum: Union[Number, Iterable[Number]], maximum: Union[Number, Iterable[Number]])
I think that's overcomplicating it. How often have you actually wanted that feature?
If you want bounds that are iterables, just use min/max on them first.
Another point: min and max accept either a single iterable or multiple values, but not a mixture, so they set a precedent for 'clamp' not accepting a mixture too.
On 05/07/2020 10:09, Federico Salerno wrote:
On the topic of NaNs: I think in the face of ambiguity, we should refuse the temptation to guess and raise ValueError when the values given are not comparable to the point of clearly determining whether `value` is or isn't within `minimum` and `maximum` or which of the two bounds it exceeds. It is the programmer's responsibility to ensure they supply arguments that make sense, and non-numbers should be considered exceptional behaviour: as such, the programmer that anticipates those kinds of values should handle them exceptionally.
About the name: clamp seems to be the most favoured name and the discussion convinced me it's the one that should make it to the eventual proposal.
About the stdlib/builtin status: `min(max(x, min_), max_)` is a common enough idiom for a simple enough function that I think it is definitely beneficial and in no way dangerous to have, and that any status other than builtin would incur too much overhead for something so small. I risk claiming that most calls to max() and min() occur in modules that do not `import math` (or at the very least, they occur often enough without `import math`), and I believe clamp(), being very similar in scope to those, should benefit from similar status. I don't think clamp is a common enough identifier in non-specific fields to disqualify its implementation as a builtin by itself. By contrast, terms like `min` and `max` are extremely common in pretty much all contexts, and yet we've all survived just fine by typing `min_` and `max_` or even shadowing the names altogether (which I prefer not to do, but is definitely an option).
+1 to all of the above (I don't feel strongly about the name). All seems very sensible.
FInally, I'd like to lit another fire: given that `min()` and `max()` accept an arbitrary amount of arguments, and that the signature that seems to be preferred for the hypothetical clamp at the moment sees `value` as the first positional argument, should the function then accept iterables as values for `minimum` and `maximum`? Something along the lines of:
clamp(value: Number, minimum: Union[Number, Iterable[Number]], maximum: Union[Number, Iterable[Number]])
-1. As others have said, this is over-egging it. I think None should be allowed as a missing bound. I don't think the new function should be restricted to numbers. There may be uses for strings, or for user-built classes; why restrict it unnecessarily? If it quacks like supporting __lt__ and __gt__, it's a duck. Rob Cliffe
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/YUOCR3... Code of Conduct: http://python.org/psf/codeofconduct/
On 05/07/2020 23:55, Rob Cliffe wrote:
I think None should be allowed as a missing bound. +1 I don't think the new function should be restricted to numbers. There may be uses for strings, or for user-built classes; why restrict it unnecessarily? If it quacks like supporting __lt__ and __gt__, it's a duck.
I'm not opposed to this, although the semantics of calling it with strings may not be intuitive (does it compare the length? Alphabetic order? What order is respected for CJK or symbols?); on the other hand, we already have such behaviour on min() and max() so adding it to clamp() should reasonably be expected to follow the same logic. On that note: On 06/07/2020 04:21, Henk-Jaap Wagenaar wrote:
I do not agree clamp should be restricted to numeric values. I would expect clamp to be agnostic to the specifics of floats/numbers and like sort expect it to work for any values as long as they compare (using a dunder). I think having something like min=-math.inf is hence right out in my mind. If I got this right, the implementation could be as simple as:
def clamp(value, *, min=None, max=None): if min is not None and value < min: return min if max is not None and max < value: return max return value
+1 I think it's important to have None as sentinel value for "unlimited bound" as it is non-numeric (and non-anything-else) and thus automatically supports any comparable type. Otherwise, only numbers would benefit from the feature and anything else would need some kind of custom instance just so that anything compared True for any < and > operation. Practicality should always win when it comes to tools as general as clamp().
On 06/07/2020 04:39, Chris Angelico wrote:
In terms of reading poorly, this is far worse: clamp(x, 10) Does that ensure that it's no *greater* than 10 or no *less* than 10? Since the args would be min before max, I would expect that this has a lower bound and no upper bound, but there'll be people equally confident that it should behave like range() and have an upper bound with no lower bound (which would probably be more useful in a lot of situations anyway). So I also agree that the bounds should be given explicitly. ChrisA
I agree as well. I know most here (at least out of those who gave an opinion) don't seem to like it but an advantage of the originally proposed signature: clamp(min, val, max) Is that it would automatically force explicit bounds and place min and max in completely unambiguous order respective to each other. I know clamp(val, min, max) is familiar because it reflects the structure of min(max(val, min), max) but the clarity falls apart when this is reduced to one function call, so it may be worth considering alternatives; it has also been argued that consistency in appearance between min(max()) and clamp() should not be expected.
On 06/07/2020 10:44, Federico Salerno wrote:
On 05/07/2020 23:55, Rob Cliffe wrote:
I don't think the new function should be restricted to numbers. There may be uses for strings, or for user-built classes; why restrict it unnecessarily? If it quacks like supporting __lt__ and __gt__, it's a duck.
I'm not opposed to this, although the semantics of calling it with strings may not be intuitive (does it compare the length? Alphabetic order? What order is respected for CJK or symbols?); on the other hand, we already have such behaviour on min() and max() so adding it to clamp() should reasonably be expected to follow the same logic.
The behaviour for strings should be as per the existing string comparisons. Anything else would be surprising. The same goes for objects of any other class that supports the relational operators. Although using it for e.g. sets would be unusual and come labelled with "consenting adults, use at own risk". Rob Cliffe
participants (5)
-
Alex Hall
-
Christopher Barker
-
Federico Salerno
-
MRAB
-
Rob Cliffe