Matthew Crews
Matthew Crews

Reputation: 4295

F# Function with variable types

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

Answers (1)

Fyodor Soikin
Fyodor Soikin

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

Related Questions