1
votes

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.

1
In F# you have to care about the type of the parameters of a DU case. In your 'general problem' where all DU parameters share a base class, you can factor out the pattern matching into its own function, and provide another function that will perform the work using the base class members. - dumetrulo
There's a change request for F# that, if accepted and implemented, allows direct access to the data in a DU if all cases share the same type. However, that wouldn't help you here, as the cases have all different array types. - Abel

1 Answers

2
votes

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.