I’m not ashamed to say that I’ve always felt the same 
“minimum of x and maximum value” and “maximum of x and minimum value” is very chiastic
How does this thread’s proposal address one-sided clamping though? Do we always want a clamp function that is two-sided, that is with both a minimum and maximum at the same time?
> developing a video game
> drinking a potion boosts health capacity to 150% for the rest of a level
> finish level with health at 120%
> starting the next level should reset us to health capacity of 100%
> we should clip off the top of the health bar value to leave us with health at most 100%
> okay do we want the maximum(?) of health and 100%
> err no that would leave us at 120%
> so we want the maximum(?) of health and 0%
> nope, that still leaves us with 120%
> ah we want the minimum of health and 100% right? *pauses to think*
> yeah that does it
> also if we finished the level at less than 100% health, say 80%, then min(health, 100%) leaves us with 80%, that’s fine
Simple one sided:
import functools
at_most_100_percent = functools.partial(min, 1.0)
print(at_most_100_percent(-0.5)) # -0.5
print(at_most_100_percent( 0.8)) # 0.8
print(at_most_100_percent( 0.9)) # 0.9
print(at_most_100_percent( 1.0)) # 1.0
print(at_most_100_percent( 1.1)) # 1.0
print(at_most_100_percent( 1.2)) # 1.0
becomes
import functools
def clamp(x, minimum=None, maximum=None):
if minimum is not None and maximum is not None and minimum > maximum:
raise ValueError
ret = x
if minimum is not None:
ret = max(ret, minimum)
if maximum is not None:
ret = min(ret, maximum)
return ret
at_most_100_percent = functools.partial(clamp, maximum=1.0)
print(at_most_100_percent(-0.5)) # -0.5
print(at_most_100_percent( 0.8)) # 0.8
print(at_most_100_percent( 0.9)) # 0.9
print(at_most_100_percent( 1.0)) # 1.0
print(at_most_100_percent( 1.1)) # 1.0
print(at_most_100_percent( 1.2)) # 1.0
when using a two-sided clamp function. I agree that functools.partial(clamp, maximum=1.0) is clearer than functools.partial(min, 1.0), especially when one is tired, but the implementation is checking the if minimum is not None etc conditions every single call of at_most_100_percent.
Maybe a factory approach is slightly more optimized but it looks a bit icky:
from typing import Callable
def clamp_factory(minimum=None, maximum=None) -> Callable:
if minimum is not None and maximum is not None and minimum > maximum:
raise ValueError
def clamp(x):
if minimum is not None:
x = max(minimum, x)
if maximum is not None:
x = min(maximum, x)
return x
return clamp
at_most_100_percent = clamp_factory(maximum=1.0)
print(at_most_100_percent(-0.5)) # -0.5
print(at_most_100_percent( 0.8)) # 0.8
print(at_most_100_percent( 0.9)) # 0.9
print(at_most_100_percent( 1.0)) # 1.0
print(at_most_100_percent( 1.1)) # 1.0
print(at_most_100_percent( 1.2)) # 1.0