ronilk
ronilk

Reputation: 372

F# member constraints on return type

Suppose I have following types:

type AddressLow = {
    FlatNo: int
    PinCode: string
}

type AddressHigh = {
    FlatNo: int
    AreaName: string
    PinCode: string
}

type PersonDataLow = {            
    id:int
    name:string
    address: AddressLow
}

type PersonDataHigh = { //same label names, different type for address
    id:int
    name:string
    address: AddressHigh 
}

Following two functions are to build the addresses:

let GetAddressLow () =
    {AddressLow.FlatNo = 10; PinCode = "5245"}

let GetAddressHigh () =
    {AddressHigh.FlatNo = 10; AreaName = "Bel Air"; PinCode = "8225"}

Following function is to build the PersonData:

let GetPerson fGetAddress inputId inputName = //return type inferred as PersonDataHigh
    {
        id = inputId
        name = inputName
        address = fGetAddress()
    }

let p1 = GetPerson GetAddressLow 4 "John Smith" //compile error
let p2 = GetPerson GetAddressHigh 6 "Will Smith" //works

For above function, the return type is inferred as PersonDataHigh by F#. Hence, to return different types for PersonData(i.e. PersonDataHigh and PersonDataLow) I have to write two different functions.

Another approach is to use discriminated unions(DU) but that involves number of conversions back and forth between DU type and case-identifiers of the DU type.

Is it possible to use constraints for the return type so as to write the function just once? Say, something like this:

let inline GetPerson (fGetAddress) (inputId) (inputName) 
    : ^T when ^T: (member id: int) and ^T: (member name: string) and (^T: (member address: AddressLow) or ^T: (member address: AddressHigh)) = //compile error
    {
        id = inputId
        name = inputName
        address = fGetAddress()
    }

If not, is using DU the best choice here? I am using F# 3.0.

Thanks.

Upvotes: 2

Views: 146

Answers (1)

TheInnerLight
TheInnerLight

Reputation: 12184

Have you thought about nesting the low or highness within a single Address type?

Since you most of the data is shared between the two types of Address, I don't think making it a discriminated union or two different types is the most sensible option. Instead, just make one of its properties a disciminated union.

The simplest way to do this is make the AreaName an option.

type Address = {
    FlatNo: int
    AreaName : string option
    PinCode: string
}

type PersonData = {            
    id:int
    name:string
    address: Address
}

Then you can go:

let GetAddressLow () =
    {FlatNo = 10; AreaName = None; PinCode = "5245"}

let GetAddressHigh () =
    {FlatNo = 10; AreaName = Some "Bel Air"; PinCode = "8225"}

You then don't need anything fancy to create your GetPerson function.

Upvotes: 6

Related Questions