6
votes

I am trying to use a specific member of a discriminated union as a parameter type. For example:

type SomeUnion =
   | A of int * int
   | B of string

type SomeType(A(i, j)) =
    member this.I = i
    member this.J = j

let a = A(10, 20)
let instance = SomeType(a)

but this is illegal syntax, and complains with "Unexpected symbol '(' in type definition" for SomeType's param list. This is valid syntax:

let doSomethingWithA (A(i, j)) = i + j

but the type signature is SomeUnion -> int rather than A -> int, and it complains about incomplete pattern match (understandable given the signature).

So is this possible? I believe in F# union members are compiled to CLR classes, so seems theoretically possible, but is it practically (i.e. without using something like reflection)? Otherwise I suppose you have to do the manual OOP way which is more verbose, and can't guarantee complete matches.

3
You don't want discriminated unions :) Either find something else, or rethink.Ramon Snir
@RamonSnir well, most of the time I do want a discriminated union because I have a sealed set of choices that I want to be able to match over frequently. However occasionally I want to guarantee specifically one alternative and use its params. I'd prefer not to throw away the benefits of discriminated unions for these infrequent cases.fhusb
That's the case. You can't guarantee it since there is neither compiler nor runtime check that you receive a certain DU case as an argument.bytebuster

3 Answers

7
votes

I agree that it is surprising that you cannot pattern match the constructor argument. It does work with normal members.

Maybe you can do an explicit match in the constructor, to get a runtime error if the value is wrong:

type SomeType(a) =
    let i, j = match a with | A(k, l) -> k, l
    member this.I = i
    member this.J = j

But otherwise, one must understand that A is not a type. So it is not surprising that the type of doSomethingWithA is not as you expected. And you would have to live with the incomplete pattern match warnings.

6
votes

As wmeyer points out, the "A" case of the DU is not a type at all, but is just a component of the union type.

If you really want to reuse one of the union cases on its own, I can't see any alternative to making it an explicit standalone type.

Option one is to just use a type alias:

type TypeA = int * int

type SomeUnion =
    | A of TypeA 
    | B of string

type SomeType(a:TypeA) =
    let (i,j) = a
    member this.I = i
    member this.J = j

let a = (10, 20)
let instance = SomeType(a)

I have added the TypeA annotation in the constructor for clarity.

The second option is to wrap it in a single case DU.

type TypeA = TA of int * int

type SomeUnion =
    | A of TypeA 
    | B of string

type SomeType(a:TypeA) =
    let (TA(i,j)) = a  
    member this.I = i
    member this.J = j

let a = TA (10, 20)
let instance = SomeType(a)

(Note that the match let (TA(i,j)) = a has to have extra parens so that it is not confused with a function.)

5
votes

My F# is a little rusty, but maybe you could do something like this (tested with f# 2 on mono)?

type SomeUnion =
   | A of int * int
   | B of string

type SomeType(i:int, j:int) =
    member this.I = i
    member this.J = j

let createSomeType(SomeUnion.A(i,j)) = new SomeType(i,j)
let a = A(10, 20)
let instance = createSomeType(a)