Is is possible to pattern match on the underlying shape of a discriminated union?
Asked Answered
D

1

2

Does F# support pattern matching of a discriminated union member instance by criteria other than the Identifier pattern?

For example, imagine that I want to match on the underlying shape of the data and I want to consider anything with an int * int shape, regardless of how the DU classifies the value. Is

Here's how I'd do it now:

type ExampleDU = 
  | BinaryCase1 of x:int * y:int
  | BinaryCase2 of x:int * y:int
  | UnaryCase1  of x:int

let underlyingValue = (1,2)
let asCase1 = BinaryCase1 underlyingValue
let asCase2 = BinaryCase2 underlyingValue

let shapeName = 
  match asCase1 with
  | BinaryCase1 (x,y) | BinaryCase2 (x,y) -> "pair" // is this possible without explicitly writing the name for each part?
  | _ -> "atom"

I'd like something closer to the following:

let shapeName = 
  match asCase1 with
  | (x,y) -> "pair" 
  | _ -> "atom"

Is there some similarly expressive syntax that is currently supported in F# or am I stuck with explicitly specifying all cases?

Note: I know that I could figure out how to find the information that I want with reflection, but I'm not interested in such a solution.

Daredeviltry answered 18/12, 2015 at 9:50 Comment(10)
You can use an active pattern for this - but it can't be geralised easilyPederast
@JohnPalmer Do you have any reference on using Active Patterns with Discriminated Unions? I could probably figure out how to do what I want from an example usageDaredeviltry
Why do you want to do this? The fact that the shapes of two cases are similar ought to be entirely incidental. The entire point of a DU is to... well... discriminate between mutually exclusive cases. By definition, two different cases have nothing in common, even though sometimes it may look like they share the same shape.Waters
@MarkSeemann The members of a DU are mutually exclusive. This implies that no objects exist that are instances of more than one member of the DU. However, "two different cases have nothing in common" is too strong. You seem to be asserting that the definition of a disjoint union requires that instances of distinct members must not share common attributes. I'm pretty sure that would be wrong though. It actually seems likely that instances of distinct members of a DU would share attributes, for the same reason that they are declared in the same scope.Daredeviltry
@MarkSeemann My purpose in identifying a common shape among distinct DU members is to deal with the shared attributes of their instances economically.Daredeviltry
@Daredeviltry Fair enough; perhaps I got a little carried away there :) Obviously, the cases do have something in common; otherwise one wouldn't put them in the same type.Waters
@MarkSeemann I thought of a simpler way to explain it. Consider: type Expr = | T | F | NOT of Expr | AND of Expr * Expr | OR of Expr * Expr. It manifests in 3 shapes from 5 symbols (2 constants, 1 function, 2 binary operations). Any evaluation procedure would use a distinct pattern for interpreting each node, based on if it were: a constant; a function; or, a binary operation. Depending on what you're doing with the expression you may be interested in whether an expression is AND/OR or you might only care that it's binary. In the 2nd case, matching on shape could easily save 100s of LOCDaredeviltry
All that said, however, it still sounds like something you ought to be able to design your way out of. As @JohnPalmer says, you can't generalise the solution he suggests. This is probably not a failing in the language, but might be an indication that there's an opportunity for a cleaner design. Could you, for example, define those common elements as a record type, and then make the mutually exclusive cases an element of that record type? (That already sounds like a monadic type in the making.)Waters
(Your post crossed mine.) Valid example. AFAIK, the answer from @JohnPalmer is the best option, and I don't think you can generalise it.Waters
@MarkSeemann I've been doing something similar with the catamorphism pattern. In terms of whether or not it's "cleaner" - it depends. From a pure data perspective it can be. The downsides are that: the boilerplate is more complicated/more overhead; and you lose expressiveness at the semantic level (a good example of this phenomenon). In the particular case I'm working on the complexity would hurt more than it would help. Good suggestion though - P.S. I learned a ton from your DI bookDaredeviltry
P
4

Here is the active pattern answer

let (|Pair|_|) input = 
    match input with
    |BinaryCase1(a,b)
    |BinaryCase2(a,b) -> Some(a,b)
    | _ -> None

and the usage

match asCase1 with |Pair(a,b) -> printfn "matched" | _ -> ();;
Pederast answered 18/12, 2015 at 10:38 Comment(2)
I see - but it looks like this approach would still require code that enumerates each case and assigns the shape (a Active Pattern per Discriminated Union). Or, were you suggesting that this could be generalized to something more reusable across distinct DU types?Daredeviltry
This can't be generalized at all.Pederast

© 2022 - 2024 — McMap. All rights reserved.