JoyfulPanda
JoyfulPanda

Reputation: 1037

F# Extract value within Discriminated Union without matching

I have the following Discriminated Union (DU) declaration:

type Book =
    | Dictionary of string[]
    | Novel of int[]
    | Comics of bool[]

An example:

let x = Dictionary [|"a"; "b"|]

How can I extract the length of the array inside without doing pattern matching and without caring about the data type of the array (in this case: string, int, bool). Note: I have no control over the DU declaration; as a result, I can't write new member method within Book, like getArrayLength()

Of course, we can do it in some way as followed:

match x with
| Dictionary (x: _[]) -> x |> Array.length
| Novel (x: _[]) -> x |> Array.length
| Comics (x: _[]) -> x |> Array.length

But typing x |> Array.length a lot is incovenient. This is a simple example, but we can think of a general problem:

type Animal =
   | Dog of DogClass
   | Cat of CatClass
   | Cow of CowClass
   ...

... and DogClass, CatClass, etc. may share something. We want to get that shared thing. E.g. those classes inherit from AnimalClass, within which there is countLegs() method. Suppsed there are many animals, pattern matching for all of them while the code block after -> is almost the same. I love the principle DRY (Don't Repeat Yourself).

Is there any convenient way to tackle such problem?

==

EDITED 21.10.2019

I was also looking for some syntax like:

let numEles =
   match x with
   | _ (arr: _[]) -> x |> Array.Length
   | _ -> failwith "No identifiers with fields as Array."

let numLegs =
   match anAnimall with
   | _ (animal: ?> Animal) -> animal.countLegs()
   | _ -> failwith "Can't count legs because of not being an animal."

I think this still follows the spirit of matching, but seem like this approach is not supported.

Upvotes: 3

Views: 1563

Answers (1)

Phillip Carter
Phillip Carter

Reputation: 5005

Realistically, there's no getting around pattern matching here. DUs were, in a way, built for it. Since you don't control the type, you can always add a type extension:

type Book with
    member this.Length =
        match this with
        | Dictionary d -> d.Length
        | Novel n -> n.Length
        | Comics c -> c.Length

let x = Dictionary [|"a"; "b"|]
printfn "%d" x.Length // Prints 2

Though it's also equally valid to define a Book module with a length function on it if you prefer that:

module Book =
    let length b =
        match b with
        | Dictionary d -> d.Length
        | Novel n -> n.Length
        | Comics c -> c.Length

let x = Dictionary [|"a"; "b"|]
printfn "%d" (x |> Book.length) // prints 2

But you'll need to write a pattern match expression on the Book type at least once. The fact that every case is made up of data that all has the same property doesn't really help the fact that you need to still identify every case individually.

Upvotes: 4

Related Questions