I wanted to make my own F#-idiomatic mathematical clamp function by leveraging System.Math.Clamp but making it partially-applicable, e.g. let clampToByte value = clamp 0 255 value
I first tried to make something like this:
let clamp lower upper value = System.Math.Clamp(value, lower, upper)
…which doesn’t compile because the System.Math.Clamp method has many overrides.
Then I created two separate functions (I’m only using ints and floats):
let clampInt lower upper (value : int) = System.Math.Clamp(value, lower, upper)
let clampFloat lower upper (value : float) = System.Math.Clamp(value, lower, upper)
…but that didn’t seem to be a satisfying result to me.
I then started to go down the ‘generic path’:
let clamp (lower : _) (upper : _) (value : _) = System.Math.Clamp(value, lower, upper)
let clamp (lower : 'a) (upper : 'a) (value : 'a) = System.Math.Clamp(value, lower, upper)
let clamp<'a> (lower : 'a) (upper : 'a) (value : 'a) = System.Math.Clamp(value, lower, upper)
let inline clamp (lower : ^a) (upper : ^a) (value : ^a) = System.Math.Clamp(value, lower, upper)
let inline clamp (lower : ^a, upper : ^a, value : ^a) = System.Math.Clamp(value, lower, upper)
…but nothing was compiling.
I then did some searching of the web and found various things in Stack Overflow and other places:
…but I don’t understand most of what the people are talking about, or the examples, and started to go down some kind of ‘generic static reference parameter rabbit hole’, ending with rubbish like this:
let clamp<'a> (lower : 'a) (upper : 'a) (value : 'a) =
match box value with
| :? int as i -> System.Math.Clamp(i, lower, upper)
| :? float as f -> System.Math.Clamp(f, lower, upper)
| _ -> failwith "clamp for this type not implemented"
let clamp<'a> (lower : 'a) (upper : 'a) (value : 'a) =
match box value, box lower, box upper with
| :? int as v, :? int as l, :? int as u -> System.Math.Clamp(v, l, u)
| :? float as v, :? float as l, :? float as u -> System.Math.Clamp(v, l, u)
| _, _, _ -> failwith "clamp for this type not implemented"
…and nothing is working and I’m getting even more confused.
I found that I could do this:
type MathOps =
static member clamp (lower : int, upper : int, value : int) = System.Math.Clamp(value, lower, upper)
static member clamp (lower : float, upper : float, value : float) = System.Math.Clamp(value, lower, upper)
…but, since the parameters can’t be curried (as far as I understand it), that’s not much better than using System.Math.Clamp.
I know I can implement my own generic clamp function quite simply with:
let clamp lower upper value = value |> min upper |> max lower
…but that’s because clamping is very easy to do.
What I’m looking for is a very-easy-to-understand explanation of what I need to do if I really need to use some existing overloaded C# methods and create my own partially-applicable F# versions.
Can anyone help?