Exhaustive pattern matching on subsets of a DU

Hi,

I am just at the beginning of my F# journey so please bear with me in the below.

I was wondering whether it is possible and meaningful to make exhaustive pattern matching on subsets of a DU.
An example:

image

I want to put this into subsets with a multi case total active pattern. E.g.:

image

Say I want to know how I should open a fruit that needs to be cracked, e.g. with a hammer or a nut cracker. How can I make an exhaustive pattern check for all fruits that should be cracked, so that adding another crackable fruit I get a compile error until I state whether a hammer, a nut cracker or a third tool should be used? On the other hand I don’t want to have to do anything if I add a fruit that should not be cracked.
Something along the lines of this but without the Na’s and the _ and without having to worry about Peel and Wash fruits:

image

One possibility is to create multiple DUs but I want to be able to create more partitions/views on the fruit DU. E.g.:

image

Is something like this achievable or even advisable in F#?

I hope the above makes my intention clear. If not, please let me know and I will try to elaborate.

Brian

Welcome!
Yeah that’s a great question! Unfortunately while what you’re trying to do is possible, it’s not terribly ergonomic. The issue is to have the exhaustive checking, you need to introduce a new type for crackable fruits. (You can think of a type as a set of the possible values that a value can take, so you’re effectively wanting to build a new type that restricts that set to just the crackable ones). You could do this for example:

type Crackable = Coconut | Hazelnut
type Peelable = Banana | Grape | Orange
type Washable = Apple | Strawberry
type Fruit = Coconut | Hazelnut | Banana | Grape | Orange | Apple | Strawberry

let (|Peel|Crack|Wash|) = function
  | Banana -> Peel Peelable.Banana
  | Grape -> Peel Peelable.Grape
  | Orange -> Peel Peelable.Orange
  | Coconut -> Crack Crackable.Coconut
  | Hazelnut -> Crack Crackable.Hazelnut
  | Apple -> Wash Washable.Apple
  | Strawberry -> Wash Washable.Strawberry

let (|Hammer|Nutcracker|) fruit = 
  match fruit with
  | Crackable.Coconut -> Hammer
  | Crackable.Hazelnut -> Nutcracker

// don't put this in the same namespace as the function above, because the names would conflict
let (|Hammer|Nutcracker|Washrag|Knife|NoTool|) = function
  | Peel Peelable.Banana -> NoTool
  | Peel _ -> Knife
  | Crack Crackable.Coconut -> Hammer
  | Crack Crackable.Hazelnut -> Nutcracker
  | Wash _ -> Washrag

So this:

  • Makes new types for each sub-grouping. I chose to use the same case names, and define them above Fruit so that (for example) if you say Apple you get a Fruit, but if you say Washable.Apple you get a Washable. You could also just use different case names.
  • Makes the Crack|Wash|Peel pattern matcher actually return a value with each case (respectively, a Crackable, Washable, or Peelable). That way if you match on Crack, you get access to the Crackable that you could pass to any function that only accepts Crackable fruits.

Does that get at what you’re trying to do?

Hi ntwilson,

Thanks for taking the time to answer my question.
Much appreciated.

Yes, indeed it seems to do what I want.
Also good to know that I need to introduce a new type to do exhaustive checking. Wasn’t sure of that though I thought it might be so.