F# Discriminated Union - "downcasting" to subtype

1.4k views Asked by At

I don't really know what the proper title for this question should be, but:

I have a discriminated union called MyDiscriminatedUnion in F#:

type MyDiscriminatedUnion =
| Foo of Foo
| Bar of Bar

where Foo and Bar are record types:

type Foo = {
   ... }
type Bar = {
   ... }

I created a value of union type Foo:

let foo = Foo {
   ... }

The compiler tells me that foo is of type MyDiscriminatedUnion.

Then I want to pass foo into a function that expects type Foo, not MyDiscriminatedUnion. Therefore the compiler complains. How do I tell the compiler that foo is of type Foo?

I have tried:

let foo:Foo 

when constructing the value of union type.

I have tried to "downcast" foo to Foo by:

foo :?> MyDiscriminatedUnion.Foo

but neither of them work.

Please help.

2

There are 2 answers

2
Tarmil On BEST ANSWER

This is a common mistake when coming from an OO language: there are no subtypes involved in this code. The fact that you named your union cases the same as the type of the field they contain can make this confusing though, so let me give a slightly different example:

type MyDiscriminatedUnion =
  | Its_a_Foo of Foo
  | Its_a_Bar of Bar

Its_a_Foo and Its_a_Bar are not subtypes, they're union cases. A value of type MyDiscriminatedUnion is either an Its_a_Foo, in which case it has a field of type Foo, or an Its_a_Bar, in which case it has a field of type Bar. To know which one it is and get the corresponding field, you need to use pattern matching.

// The function that takes a record of type Foo as argument
let f (x: Foo) = ...

// Our value of type MyDiscriminatedUnion
let myDU = Its_a_Foo { ... }

// Extracting the Foo and passing it to f
match myDU with
| Its_a_Foo foo -> f foo
| Its_a_Bar bar -> // What should we do if it's an Its_a_Bar instead?

// If you're _really_ certain that myDU is Its_a_Foo { ... }, not Its_a_Bar { ... }, you can do this.
// You will get a compile-time warning "FS0025: Incomplete pattern matches on this expression".
// If it's Its_a_Bar, you will get a runtime error.
let (Its_a_Foo foo) = myDU
f foo
3
Marta On

Rubber duck debugging case here...

I needed to write a function:

let MapToSubtype subtype =
   match subtype with
   | Foo foo -> foo

Then apply the function:

let x = MapToSubtype foo

... and worked like a charm.

Edit: Please note that as JustSomeFSharpGuy pointed out the MapToSubtype function doesn't cover all cases so the compiler will give a warning and could give a runtime exception if something else than Foo is passed in.

So the function should really look like this:

let MapToSubtype subtype =
    match subtype with
    | Foo foo -> foo
    | _ -> // deal with exception here