简体   繁体   中英

Type definitions and type inference in F#

I have two example types, defined in this order:

type Quote = {QuoteNum: decimal option; ShipToID: decimal option}
type Sales = {SalesNum: decimal option; ShipToID: decimal option}

and I'm trying to write a function that can accept a both types:

let fx (y: Map<decimal option, _ list>) = 
    y
    |> Map.map (fun key list -> list
                                   |> List.map (fun x -> x.ShipToID))

When I try to pass a Map<decimal option, Quote list> to the function, I get an error:

Type mismatch. Expecting a
Map<decimal option,Sales list>    
but given a
    Map<decimal option,Quote list>

I would have thought that I'd be able to pass a map of both types to the function, but the compiler seems to be inferring that only a Map<decimal option, Sales list> is acceptable to the function. I suspect that the compiler "sees" the Sales type most recently and assumes that that's what the function needs. I thought I had made the function relatively generic by including the _ in the type annotation.

How can I make the function accept both types? Can I do this without redefining the types themselves?

Your suspicions are correct. F# needs to infer a single concrete type there, so will use the most recent matching type it finds.

You can make it work by defining an interface with ShipToID field and have both types implement it (not sure if that counts as redefining the types for you), or by providing a getter function for extracting the field value (which would let you keep the function generic):

let fx (shipToIdGetter: 'a -> decimal option) (y: Map<decimal option, 'a list>) = 
    y
    |> Map.map (fun key list -> 
                   list
                   |> List.map (fun x -> shipToIdGetter x))

Your code would work as written if F# supported row polymorphism (which happens to be the case for OCaml).

While I believe that scrwtp's answer is the way to go, you also have the choice to use inline and member constraints (or static duck typing...if thats a valid term). For the sake of completeness:

type Quote = {QuoteNum: decimal option; ShipToID: decimal option}
type Sales = {SalesNum: decimal option; ShipToID: decimal option}

let inline fx (y: Map<decimal option, 'a list>) = 
    let inline getId x = ( ^a : (member ShipToID : decimal option) x)

    y |> Map.map (fun key list -> list |> List.map getId)


let q : Quote = { QuoteNum = None; ShipToID = None}
let s : Sales = { SalesNum = None; ShipToID = None}

fx <| Map.ofList [ None, [q]]
fx <| Map.ofList [ None, [s]]

The getId function uses the rather obscure syntax to call the ShipToID member based on ^a 's expected structure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM