Giving names to function arguments is a way of documenting our code; if there was no such need then we could give them names such as “arg1” and “arg2” and be no worse for it – but in reality this would of course make our code much harder to understand. In F# we often pass around functions as arguments to other functions, in which case their arguments are no longer named:
let foo<'T> (bar : 'T -> 'T -> float -> 'T) =
// do something
This function foo takes a “bar” as an argument, which itself takes two 'T and a float and returns a 'T, but it is hard to tell what it is supposed to do. It would become much clearer if we could name the arguments of “bar”:
let foo<'T> (bar : (startVal : 'T) -> (endVal : 'T) -> (interpolationAmt : float) -> 'T) =
// do something
Now it is obvious what “bar” is used for: It interpolates between two values of type 'T. Furthermore it also becomes clear that the order of the 'T arguments matter and in which order to apply them. But unfortunately this code doesn’t compile.
So my question is if there is a way to do this that I’ve missed, and if not how do people usually deal with this problem?
Unfortunately, it's not possible to add parameter names to the function signature. The best you can do to make the function signature easy to understand is custom types. But I suggest creating custom type only for real domain types which have some meaning on their own. Usually, such types have some restrictions - non-empty list, string with max length 42, negative integer etc. So for your function, I would create a type for interpolation amount:
type InterpolationAmount = private InterpolationAmount of float
module InterpolationAmount =
let create (amount: float) : InterpolationAmount =
if amount < 0. then failwith "blah-blah"
else InterpolationAmount amount
At least it's now clear what float
argument is and you can control the interpolation amount value.
Now about interpolation start and end. To ensure that the user will pass correctly arguments to this function you can make it a function with a single argument. You will of course lose the partial application option, but all arguments will have names and it's hard to mess up here:
type InterpolationArgs<'T> = {
startValue: 'T
endValue: 'T
amount: InterpolationAmount
}
Of course, using a name other than bar
will also give a hint to the user
let foo<'T> (interpolate : InterpolationArgs<'T> -> 'T) =
interpolate {
startValue = a
endValue = b
amount = InterpolationAmount.create 1.2
}