简体   繁体   中英

match by value in a discriminated union, in F#

with this union:

type T =
    | A
    | B
    | C

and a T list

I would like to implement something like this pseudo code:

let countOfType (t: Type) (l: T list) =
    l
    |> List.filter (fun x -> x.GetType() = t)
    |> List.length

when I would pass if I want to count the 'A', 'B', etc.. but A.GetType() and B.GetType() return the T type, so this doesn't work.

Is there a way where I could check the type by passing it as a parameter?

The practical case here is that I have a Map that gets updated every few seconds and its values are part of the same DU. I need to be able to see how many of each type, without having to update the code (like a match block) each time an entry gets added.


Addendum: I simplified the original question too much and realized it after seeing Fyodor's answer.

So I would like to add the additional part: how could this also be done for cases like these:

type T =
    | A of int
    | B of string
    | C of SomeOtherType

For such enum type T as you specified, you can just use regular comparison:

let countOfType t (l: T list) =
    l
    |> List.filter (fun x -> x = t)
    |> List.length

Usage:

> countOfType A [A; A; B; C; A]
3

> countOfType B [A; A; B; C; A]
1

Try List.choose: ('a -> 'b option) -> 'a list -> 'b list , it filters list based on 'a -> 'b option selector. If selectors evaluates to Some , then value will be included, if selector evaluates to None , then value will be skipped. If you worry about allocations caused by instantiation of Some , then you'll have to implement version that will use ValueOption

    let onlyA lis =
        lis |> List.choose (function
                            | (A _) as a -> Some a
                            | _ -> None)

    let onlyB lis =
        lis |> List.choose (function
                            | (B _) as b -> Some b
                            | _ -> None)
    let lis = [
        A 1
        A 22
        A 333
        B ""
        B "123"
    ]

    lis |> onlyA |> List.length |> printfn "%d"

You can pattern match, and throw away the data, to create a function for the filter.

type T =
| A of int
| B of string
| C of float

[A 3;A 1;B "foo";B "bar";C 3.1; C 4.6]
|> List.filter (fun x -> 
    match x with
    | A _ -> true
    | B _ -> false
    | C _ -> false
)
|> List.length

But in general i would asume, that you create a predicate function in your modul.

let isA x =
    match x with
    | A _ -> true
    | _   -> false

if you have those functions you can just write

[A 3;A 1;B "foo";B "bar";C 3.1; C 4.6]
|> List.filter isA
|> List.length

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM