What techniques are available for making curried versions of overloaded methods in C# libraries?

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?

I managed to take one of your attempts and get it working but it’s not pretty:

let clamp (lower : 'a) (upper : 'a) (value : 'a) =
    match box lower, box upper, box value with
    | (:? int as l), (:? int as u), (:? int as v) ->
        System.Math.Clamp(v, l, u) |> box :?> 'a
    | (:? float as l), (:? float as u), (:? float as v) ->
        System.Math.Clamp(v, l, u) |> box :?> 'a
    | _ ->
        failwith "clamp for this type not implemented"

1 |> clamp 5 10  // 5
6 |> clamp 5 10  // 6
11.0 |> clamp 5.0 10.0  // 10.0

11 |> clamp 5.0 10.0 // compile error because types don't match ✅

In this particular case, I would avoid using this approach. There’s a whole load of boxing and type checking and it seems to be multiple orders of magnitude slower than System.Math.Clamp or the simplest F# equivalent (but only when using the inline keyword):

let inline clamp lower upper value = value |> max lower |> min upper

Generally speaking, I would still avoid this approach as it’s quite verbose and error-prone. There isn’t an easy way to convert overloaded methods into curried functions. The two systems aren’t compatible with each other.

I thought of another way to do this using a methods that take one argument and return a curried function:

[<AutoOpen>]
type Math =
    static member inline clamp (lower:int) =
        fun upper value -> System.Math.Clamp(value, lower, upper)

    static member inline clamp (lower:float) =
        fun upper value -> System.Math.Clamp(value, lower, upper)

This ends up simulating a normal curried function in its actual usage but the type signature looks odd and is not helpful in describing parameter names: static member Math.clamp: lower: int -> (int -> int -> int)

This also performs well, but note that the overall type has to be determined by the lower bound because it’s the first argument.

Also note that the [<AutoOpen>] allows you to use this as a plain function: clamp 1 2 3, instead of Math.clamp 1 2 3

1 Like

Thanks for these answers; it seems like I might want to be doing something that’s not really supposed to be done.

I think I’ll take each case as it comes and see what’s best for each.

Just out of curiosity, why do you suggest using the inline clamp function:

let inline clamp lower upper value = value |> max lower |> min upper

…rather than the non-inline clamp function?:

let clamp lower upper value = value |> max lower |> min upper

I’ve been using the non-inline version in various places and it seems to work just fine.

Is there something that I’m missing?

Only because the inline one seems to be about 300 times faster. The difference may not be noticeable at all in your use case.

Ah, okay, thanks.
I’m not really looking at performance issues at the moment but that’s a very good point.

Note: I’ve since changed the function to:

let clamp lower upper value = 
    let lower, upper = if lower < upper then lower, upper else upper, lower 
    value |> min upper |> max lower

…so that it still works even when I get the lower and upper limits round the wrong way, otherwise property-based testing brings the problem up.