Enumeration with specific type and property - f#

I want validate if an Animal is a Person and its name is Alex.
Person object:
type Person(name: string) =
member this.Name = name
Enumration:
type Animal =
| Person of Person
| Cat
| Dog
Pattern matching:
let KnowAnimal animal =
match animal with
| Person person && person.Name = "Alex" -> 1
| Cat -> 2
| Dog -> 3
| _ -> 4

You can specify pattern guards with when:
let KnowAnimal animal =
match animal with
| Person person when person.Name = "Alex" -> 1
| Cat -> 2
| Dog -> 3
| _ -> 4

Related

Match on child discriminated union

If I have a discriminated union with multiple values sharing a child (Apple and MoreApples both have type Apple)...
type Apples =
| GrannySmith
| Gala
type Fruit =
| Apple of Apples
| MoreApples of Apples
| Banana
let speakFruit = function
| Apple GrannySmith
| MoreApples GrannySmith -> "granny smith"
| Apple Gala
| MoreApples Gala -> "gala"
| Banana -> "banana"
Is there a way to match on the sub-union to remove duplication? - something like:
let speakFruit2 = function
| _ GrannySmith -> "granny smith"
| _ Gala -> "gala"
| Banana -> "banana"
I don't think there is a nice way to do this with single pattern, but you can define an active pattern that will give you an alternative perspective of the data where the two kinds of apples are merged:
let (|AnyApple|Banana|) = function
| Apple a | MoreApples a -> AnyApple a
| Banana -> Banana
This hides the standard Banana definition - you should probably use another name to avoid confusion, but the rest stays the same. Now you can pattern match using AnyApple:
let speakFruit = function
| AnyApple GrannySmith -> "granny smith"
| AnyApple Gala -> "gala"
| Banana -> "banana"
How about this?
let speakFruit = function
| Apple x | MoreApples x ->
match x with
| GrannySmith -> "Granny Smith"
| Gala -> "gala"
| Banana -> "banana"
Partial Active Pattern could also be a solution
let (|IsKind|_|) kind z =
match z with
| Apple x | MoreApples x -> if (kind = x) then Some true else None
| _ -> None
let speakFruit x =
match x with
| IsKind GrannySmith z -> "Granny Smith"
| IsKind Gala z -> "Gala"
| Banana -> "banana"
| _ -> "something else"
But to be honest - I agree with Fyodor above. You probably should rethink your types.

In F#, How can I attach metadata to discriminated union values?

I want to create something that's kind of like an enum with an F# record type for a value instead of an int. For example, if I've got the union:
type BologneseIngredients = | Spaghetti
| Tomatoes
| MincedBeef
| GrandmasSecretIngredient
I know that spaghetti is always 30cm long and tomatoes are always red. What I could do is have a 'get metadata' function:
let getMetadata = function
| Spaghetti -> { length: 30.0<cm> }
| Tomatoes -> { colour: Color.Red }
| _ -> { }
but I'd really like to keep the definition of the union and the data together. Is there a nice way to do this?
You could add properties to your discriminated union...
type BologneseIngredients =
| Spaghetti
| Tomatoes
| MincedBeef
| GrandmasSecretIngredient
member x.Color =
match x with
| Spaghetti -> Color.AntiqueWhite
| Tomatoes -> Color.Red
| MincedBeef -> Color.Firebrick
| GrandmasSecretIngredient -> Color.Transparent
let foo = Tomatoes
printfn "%A" foo.Color
> Color [Red]
my suggestion:
module Recipes =
type BologneseIngredients = | Spaghetti
| Tomatoes
| MincedBeef
| GrandmasSecretIngredient
let length (ind : BologneseIngredients) : float<cm> option =
match ind with
| Sphaghetti -> Some 30.0<cm>
| _ -> None
// .. or a bit more "metadata"ish
type Metadata =
| Length of float<cm>
| Color of System.Drawing.Color
let metadata =
function
| Sphaghetti -> [ Length 30.0<cm ]
| Tomatoes -> [ Color System.Drawing.Color.Red ]
| ...
let metaLength meta =
meta |> List.tryPick (function | Length l -> Some l | _ -> None)
let getLength = metadata >> metaLength

Wildcard for type when matching discriminated unions

In the following real world example I do a match:
type Style = Nice | Cool | Ugly
type Color = Blue | Yellow | Orange | Grey | Cyan
type ClothingProperties = Style * Color
type Clothes =
| Jeans of ClothingProperties
| Pullover of ClothingProperties
| Shirt of ClothingProperties
type Person =
| Person of string * Clothes
let team = [Person("Jan", Jeans (Cool, Blue)); Person("Pete", Shirt (Nice, Cyan)); Person("Harry", Pullover (Ugly, Grey))]
let matchPerson person=
match person with
| Person(name, Jeans(Ugly,_) ) -> printfn "%s wears ugly stuff." name
| Person(name, Pullover(Ugly,_) ) -> printfn "%s wears ugly stuff." name
| Person(name, Shirt(Ugly,_) ) -> printfn "%s wears ugly stuff." name
| _ -> ()
List.iter(fun x->matchPerson x) team
Is there a way to create a more efficient match, so I don't need to check each clothing case? Something like this:
let matchPerson person=
match person with
| Person(name, _ (Ugly,_) ) -> printfn "%s wears ugly stuff." name
| _ -> ()
Of course, this is not correct syntax. But how can I achieve such an effect?
That's not straightforward, you can use reflection, but the problem is that your discriminated union needs some redesign, because if you know there will always be a ClothingProperties then you can change it to this:
type Style = Nice | Cool | Ugly
type Color = Blue | Yellow | Orange | Grey | Cyan
type ClothingProperties = Style * Color // or just use a tuple
type Clothe =
| Jeans
| Pullover
| Shirt
type Clothes = Clothe *ClothingProperties
type Person =
| Person of string * Clothes
let matchPerson person=
match person with
| Person(name, (_,(Ugly,_)) ) -> printfn "%s wears ugly stuff." name
| _ -> ()
A related issue is described here Is it possible to pass discriminated union tags as arguments?

Is it possible to pass discriminated union tags as arguments?

Is it possible to pass the type of a discriminated union tag to another function so it can use it for pattern matching?
Non working example of what I mean:
type Animal = Pig of string | Cow of string | Fish of string
let animals = [Pig "Mike"; Pig "Sarah"; Fish "Eve"; Cow "Laura"; Pig "John"]
let rec filterAnimals animalType animals =
if animals = [] then
[]
else
let rest = filterAnimals animalType (List.tail animals)
match List.head animals with
|animalType animal -> animal::rest // <- this doesn't work
|_ -> rest
printfn "%A" (filterAnimals Pig animals)
Discriminated unions works best if there is no semantic overlap between the cases.
In your example, each case contains the same component with the same meaning, a string indicating "the name of the animal". But that's a semantic overlap! The discriminating union will then force you to make distinctions you don't want to: You don't want to be forced to discriminate between the "name of a pig" and the "name of a cow"; you just want to think of "the name of an animal".
Let's make a type that fits better:
type Animal = Pig | Cow | Fish
type Pet = Animal * string
let animals = [(Pig, "Mike"); (Fish, "Eve"); (Pig, "Romeo")
With that type, filtering out non-Pigs is a one-liner:
animals |> List.filter (fst >> (=) Pig)
If not every animal has a name, use an option type:
type Pet = Animal * string option
You would use a discriminated union for your animals if you knew that, say, every Pig has a name, but no Fish does: those cases have no overlap.
You can change the filterAnimals function to take a Partial Active Pattern as input:
let rec filterAnimals (|IsAnimalType|_|) animals =
if animals = [] then
[]
else
let rest = filterAnimals (|IsAnimalType|_|) (List.tail animals)
match List.head animals with
| IsAnimalType animal -> animal::rest
| _ -> rest
Then you can define an Active Partial Pattern for pigs:
let (|IsPig|_|) candidate =
match candidate with
| Pig(_) -> Some candidate
| _ -> None
And you can call the function like this (FSI example):
> filterAnimals (|IsPig|_|) animals;;
val it : Animal list = [Pig "Mike"; Pig "Sarah"; Pig "John"]
Actually, you can reduce the Partial Active Pattern like this:
let (|IsPig|_|) = function | Pig(x) -> Some(Pig(x)) | _ -> None
And it turns out that you can even inline them:
> filterAnimals (function | Pig(x) -> Some(Pig(x)) | _ -> None) animals;;
val it : Animal list = [Pig "Mike"; Pig "Sarah"; Pig "John"]
> filterAnimals (function | Fish(x) -> Some(Fish(x)) | _ -> None) animals;;
val it : Animal list = [Fish "Eve"]
> filterAnimals (function | Cow(x) -> Some(Cow(x)) | _ -> None) animals;;
val it : Animal list = [Cow "Laura"]
Just for completeness, I'll list this solution.
If you quoted your input, you'd be able to reason about the names of the tags:
open Microsoft.FSharp.Quotations
type Animal = Pig of string | Cow of string | Fish of string
let isAnimal (animalType : Expr) (animal : Expr) =
match animal with
| NewUnionCase(at, _) ->
match animalType with
| Lambda (_, NewUnionCase (aatt, _)) -> aatt.Name = at.Name
| _ -> false
| _ -> false
let animal = <# Pig "Mike" #>
isAnimal <# Pig #> animal // True
isAnimal <# Cow #> animal // False
This is admittedly quite verbose though, and it would become even more so if you wanted to quote a list instead of a single value.
A slightly different version, where we quote only the animal type, would let you easily filter lists of animals, as you need (at the price of some questionable comparison of strings):
open Microsoft.FSharp.Quotations
type Animal = Pig of string | Cow of string | Fish of string
let isAnimal (animalType : Expr) animal =
match animalType with
| Lambda (_, NewUnionCase (aatt, _)) -> animal.ToString().EndsWith(aatt.Name)
| _ -> false
let animal = Pig "Mike" // No quote now, so we can process Animal lists easily
isAnimal <# Pig #> animal // True
isAnimal <# Cow #> animal // False
let animals = [Pig "Mike"; Pig "Sarah"; Fish "Eve"; Cow "Laura"; Pig "John"]
let pigs = animals |> List.filter (isAnimal <# Pig #>)
The latter version is not really superior to passing the tag name as a string.
No, it's not possible to pass just the tag, in order to be able to treat separately the tag and the string you can define them like this:
type AnimalType = Pig | Cow | Fish
type Animal = Animal of AnimalType * string
let animals = [Animal (Pig, "Mike"); Animal (Pig, "Sarah"); Animal (Fish, "Eve"); Animal (Cow, "Laura"); Animal (Pig, "John")]
let rec filterAnimals animalType animals =
if animals = [] then
[]
else
let rest = filterAnimals animalType (List.tail animals)
match List.head animals with
| Animal (x, animal) when x = animalType -> animal::restwork
|_ -> rest
printfn "%A" (filterAnimals Pig animals)
Alternatively you can use just a tuple of AnimalType * string
UPDATE
Regarding your question in the comments about what happens if the structure is not always the same, there's a trick you can use: you can compare the type of two Discriminated Unions since each tag is compiled to a different sub-class.
type Animal =
| Person of string * string
| Pig of string
| Cow of string
| Fish of string
let animals = [Pig "Mike"; Pig "Sarah"; Fish "Eve"; Cow "Laura"; Pig "John"]
let rec filterAnimals animalType animals =
if animals = [] then
[]
else
let rest = filterAnimals animalType (List.tail animals)
match List.head animals with
| x when animalType.GetType() = x.GetType() -> x::rest
|_ -> rest
printfn "%A" (filterAnimals (Pig "") animals)
But before going this way, you should think if you really need to model your problem like this.
Even if you decide to go with this structure I would rather use the built-in filter function, see the solution proposed by #polkduran.
This does not answer your question directly but suggests an alternative way to achieve what you want.
You could filter your list using the existing high order function List.filter and pattern matching:
let pigs = animals |> List.filter (function |Pig(_)-> true |_->false )
I think this is a more idiomatic approach: you filter your list using a pattern, in this case you filter your animals keeping only those who satisfy the pattern Pig(_).

how to do recursive discriminated unions by value?

In F# is it possible to have a discriminated union based on both a type and a value?
Here's an attempt at expressing this without knowing what the syntax should look like.
type Foo =
| A of int * Foo
| B of string where B.name = "1" * Foo
| C of string where C.name = "2" * Foo
| Empty
I'm not entirely sure what you're trying to achieve. But, if you want to create a type that will have a name property when which is "1" when the type is B and "2" when it is C, then you can add a member:
type Foo =
| A of int * Foo
| B of Foo
| C of Foo
| Empty
member x.Name =
match x with
| B _ -> "1"
| C _ -> "2"
| _ -> failwith "Name is not available!"
If you were hoping to use the numbers in pattern matching, then you can define an active pattern. Say you have a type with just A or B (which has a name):
type Foo =
| A of int
| B of string * Foo
Now you can write an active pattern that lets you distinguish between A, B with name "1" and B with name "2":
let (|A|B1|B2|) x =
match x with
| A n -> A n
| B("1", foo) -> B1 foo
| B("2", foo) -> B1 foo
| _ -> failwith "Invalid B"
If you now have a value foo, you can pattern match against these three cases:
match foo with
| A n -> ...
| B1 subfoo -> ...
| B2 subfoo -> ...

Resources