Reputation: 4295
I have a question on making a function generic so that I can reuse the behavior. I have a situation where I want to perform the same set of actions whether the data is a float
or a string
. I have included a bit of the data model so you can see what I am doing. Near the bottom I have two different functions in the DataSeries
module, simpleHigh
and preferredHigh
. The simpleHigh
function does exactly what I want. You can see that in both cases, float
or string
, I am using the same set of functions.
Ideally I could do what I attempt to do in the preferredHigh
function. I define a single function and then use it for both conditions. The compiler complains though saying there is a type mismatch. I tried making the inner high
function take a type argument:
let high<'T> v = (Seq.sortBy (fun x -> x.Value) >> Seq.last >> (fun x -> x.Value))
But because it is contained inside another function definition the compiler says, "Explicit type parameters may only be used on module or member bindings".
Am I fighting a loosing battle here? Should I just settle with the simple version? My instinct tells me I am missing simple subtle and simple here. I would rather reuse the behavior if possible instead of writing the exact some thing twice. Should I take a different approach? All feedback welcome!
open System
type Observation<'T> = {
ObsDateTime : DateTimeOffset
Value : 'T
}
type Result =
| Float of float
| String of string
type DataSeries =
| Float of Observation<float> seq
| String of Observation<string> seq
module DataSeries =
let summarise
(f: float Observation seq -> float)
(s: string Observation seq -> string)
(ds: DataSeries) =
match ds with
| DataSeries.Float v -> f v |> Result.Float
| DataSeries.String v -> s v |> Result.String
// What works
let simpleHigh ds =
summarise
(Seq.sortBy (fun x -> x.Value) >> Seq.last >> (fun x -> x.Value))
(Seq.sortBy (fun x -> x.Value) >> Seq.last >> (fun x -> x.Value))
ds
// What I would rather write
let preferredHigh ds =
let high v = (Seq.sortBy (fun x -> x.Value) >> Seq.last >> (fun x -> x.Value))
summarise
high
high
ds
Upvotes: 3
Views: 145
Reputation: 80734
The way you have preferredHigh
written right now, it doesn't compile for an entirely different reason: high
is a function of two arguments (one explicitly defined v
and another coming from the function composition), but you're passing it to summarise
, which expects a function of one argument.
So, the obvious way would be to remove the extra argument v
:
let high = (Seq.sortBy (fun x -> x.Value) >> Seq.last >> (fun x -> x.Value))
However, this wouldn't compile for the reason that you have already noted: the compiler determines the type of high
based on the first usage as float Observation seq -> float
, and then refuses to accept it in the second usage, because it doesn't match string Observation seq -> string
.
This (as you probably already know) is due to the fact that in F# values cannot be generic, unless you write them as such explicitly. Aka "value restriction".
However, functions can be generic. Even nested functions can: even though you can't write the generic parameters explicitly, the compiler would still infer a generic type for a nested function where appropriate.
From this, the solution is obvious: make high
a function, not a value. Give it an argument, and don't forget to apply the composed function to that argument, so that the resulting function doesn't end up with two arguments (aka "eta-abstraction"):
let high x = (Seq.sortBy (fun x -> x.Value) >> Seq.last >> (fun x -> x.Value)) x
Upvotes: 3