Match on child discriminated union - f#

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.

Related

How to Return Specific Discriminated Union from Function

I have a hierarchy of discriminated unions.
type SpecificNoun =
| Noun
| NounPhrase
| Pronoun
| PosesivePronoun
type SpecificModifier =
| Adverb //slowly, quickly, verb + ly (90% of the time)
| Preposition //off, on, together, behind, before, between, above, with, below
type SpecificVerb =
| ActionVerb
| BeingVerb
| PossesiveVerb
| TransitiveVerb
type PartsOfSpeech =
| Noun of SpecificNoun
| Verb of SpecificVerb
| Adjective
| Punctuation
| Modifier of SpecificModifier
I need to translate a string into one of them, but it has to be a PartOfSpeech so I can use it in my match cases. The below code does not compile.
let StringToPartOfSpeech (part:string) =
match part with
| "Noun" -> SpecificNoun.Noun
| "NounPhrase" -> SpecificNoun.NounPhrase
| "Pronoun" -> SpecificNoun.Pronoun
| "PossessivePronoun" -> SpecificNoun.PosesivePronoun
| "Adverb" -> SpecificModifier.Adverb
This is a related question to this: F# - Can I return a discriminated union from a function however, in my case, everything is just straight discriminated unions
You need to return a consistent type from all branches. In your case the PartsOfSpeech type is ideal.
So that means you need to take a type like SpecificNoun.Noun and wrap it in the appropriate case from PartsOfSpeech.
Also, what if the input string doesn't match any of the cases?
In the code below I decided to return a PartsOfSpeech option, but you could raise an exception,
or return a more detailed Success/Failure type, etc.
let StringToPartOfSpeech (part:string) =
match part with
| "Noun" ->
SpecificNoun.Noun |> PartsOfSpeech.Noun |> Some
| "NounPhrase" ->
SpecificNoun.NounPhrase |> PartsOfSpeech.Noun |> Some
| "Pronoun" ->
SpecificNoun.Pronoun |> PartsOfSpeech.Noun |> Some
| "PossessivePronoun" ->
SpecificNoun.PosesivePronoun |> PartsOfSpeech.Noun |> Some
| "Adverb" ->
SpecificModifier.Adverb |> PartsOfSpeech.Modifier |> Some
| _ -> None
Your code doesn't compile because you are returning two values with different types :
let StringToPartOfSpeech (part:string) =
match part with
| "Noun" -> Noun // type of SpecificNoun
| "NounPhrase" ->NounPhrase // type of SpecificNoun
| "Pronoun" -> Pronoun // type of SpecificNoun
| "PossessivePronoun" ->PosesivePronoun // type of SpecificNoun
| "Adverb" -> Adverb // type of SpecificModifier
Why you didn't use your type PartsOfSpeech ?
Try the following code :
type PartsOfSpeech =
| PNoun of SpecificNoun
| PVerb of SpecificVerb
| PAdjective
| PPunctuation
| PModifier of SpecificModifier
| PUnknown
let StringToPartOfSpeech (part:string) =
match part with
| "Noun" -> PNoun (Noun)
| "Adverb" -> PModifier (Adverb)
| _ -> PUnknown
Plus, to avoid compiler warnings, I add a case for a Unknown String.

New to F# and Don't Know How to Fix FS0025 error

I'm struggling trying to teach myself F# (my very first functional programming language) by building a very important app (or trying to) that will record user input and write to a JSON file (or eventually, a database) the outcomes of requests for help made by poor trafficking survivors to all the anti-trafficking charities/NGO's in the US. I keep getting this error, after trying everything I could think of to make sure that all possible cases for the pattern in question were considered. I don't know how to fix it, and I really can't use any error suppression on this app. Any help at all would be greatly appreciated, as I have no one else to reach out to for any help and support. Here is my code from the TerminalBuilder.fs file:
namespace Nonprofits
module TerminalBuilder =
open System
open System.IO
open Nonprofits.Types
let rec caller():Caller =
printfn "Are you reporting as an advocate on behalf of someone else or for yourself?"
printfn " 1 for Advocate"
printfn " 2 for Self"
let answer = Console.ReadLine()
match answer with
| "1" -> Advocate
| "2" -> ClientOrVictim
| _ -> printfn "Invalid Entry"
caller()
let specialneeds():Set<Disability> =
let rec spnds(s:Set<Disability>): Set<Disability> =
printfn "Do you/the person on whose behalf you're reporting have any disabling conditions?"
printfn " 1 for Learning Disability"
printfn " 2 for Physical Disability"
printfn " 3 for Mental Health Issues"
printfn " 4 for Substance Addiction Issues"
printfn " 5 for Pregnancy-Related Limitations"
printfn " 6 for Chronic Illness"
printfn " 7 for Don't KNow/Undiagnosed"
printfn " Enter 'Exit' for None"
let answer = Console.ReadLine()
match answer.Trim().ToLower() with
| "exit" -> s
| _ ->
let sn =
match answer.Trim().ToLower() with
| "1" -> Some LearningDisabled
| "2" -> Some PhysicallyDisabled
| "3" -> Some MentalIllness
| "4" -> Some SubstanceAddiction
| "5" -> Some Pregnancy
| "6" -> Some ChronicIllness
| "7" -> Some Undiagnosed
| "exit" -> printfn "No disabling conditions"
None
match sn with
| None -> spnds(s)
| Some (x) -> spnds(s.Add(x))
spnds(new Set<Disability> ([]))
And here is my types file, Nonprofits.fs:
namespace Nonprofits
open System.Collections
module Types =
type NgoType =
| HomelessShelter
| DVShelter
| TraffickingVictimSafehouse
| TraffickingSurvivorAftercare // gamut of legal, housing, clothing, food, medical, reintegration, etc.
| FoodPantries
| ClothingAssistance
| FreeMedicalDentalClinic
type Ngo = Ngo of NgoType * string
type Caller =
| ClientOrVictim
| Advocate
and CallerStatus =
| VictimServicesAdvocate of Caller
| DVvictim of Caller
| SexTraffickingSurvivor of Caller
| HomelessVictim of Caller
| NaturalDisasterVictim of Caller
| GeneralPovertyVictim of Caller
and Disability =
| Pregnancy
| PhysicallyDisabled
| LearningDisabled
| MentalIllness
| SubstanceAddiction
| ChronicIllness
| Undiagnosed
and SpecialNeeds = SpecialNeeds of Set<Disability>
type UnmetNeeds =
| TraffickingSafebed
| DVsafebed
| Housing
| Clothing
| Food
| Legal
| Medical
| Dental
| Vision
| DrugRehab
| TraumaCare
| PsychiatricCare
| SkillsTraining
| EducationHelp
| JobPlacement
| EconomicSupport
type CallerRequest =
| TraffickingVictimAftercare of Set<UnmetNeeds>
| PovertyVictimCare of Set<UnmetNeeds>
type Followup =
| SocialWorkerFollowup of Help
| CallerSelfDirected of Help
and Help =
| Helped //fully helped with everything caller needed
| ExhaustedOptions // exhausted resources and still not helped
| WrongHelp //i.e. caller offered smoking cessation counseling when caller needed sex trafficking aftercare
| NotHelped of Followup
| GivenReferral of ReferredToNextNgo
and ReferredToNextNgo = ReferredToNextNgo of Followup * Ngo
type CallOutcome =
| CallerHelped
| CallerNotHelped of Followup
| CallerReferred of ReferredToNextNgo
type Call = Call of Caller * CallerRequest * CallOutcome
And here is the error I'm getting:
C:\Users\3CU1501060\Documents\Projects\DUandTypesPractice\DUandTypesPractice\TerminalBuilder.fs(27,27): Warning FS0025: Incomplete pattern matches on this expression. For example, the value '"a"' may indicate a case not covered by the pattern(s). (FS0025) (DUandTypesPractice)
when you pattern match
let sn =
match answer.Trim().ToLower() with
| "1" -> Some LearningDisabled
| "2" -> Some PhysicallyDisabled
| "3" -> Some MentalIllness
| "4" -> Some SubstanceAddiction
| "5" -> Some Pregnancy
| "6" -> Some ChronicIllness
| "7" -> Some Undiagnosed
| "exit" -> printfn "No disabling conditions"
None
```
you check "1", "2", ... "exit", but there are other values, for example "a", you need to add another case
| answer -> //do something
in your case maybe
| answer -> printfn "not supported answer %A" answer
None // or retry?
the compiler is helping you, telling you "what happen if user choose 'a'"?

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?

How can I make this match expression more concise?

Learning F# by writing blackjack. I have these types:
type Suit =
| Heart = 0
| Spade = 1
| Diamond = 2
| Club = 3
type Card =
| Ace of Suit
| King of Suit
| Queen of Suit
| Jack of Suit
| ValueCard of int * Suit
I have this function (ignoring for now that aces can have 2 different values):
let NumericValue =
function | Ace(Suit.Heart) | Ace(Suit.Spade) | Ace(Suit.Diamond) | Ace(Suit.Club) -> 11
| King(Suit.Heart) | King(Suit.Spade)| King(Suit.Diamond) | King(Suit.Club) | Queen(Suit.Heart) | Queen(Suit.Spade)| Queen(Suit.Diamond) | Queen(Suit.Club) | Jack(Suit.Heart) | Jack(Suit.Spade)| Jack(Suit.Diamond) | Jack(Suit.Club) -> 10
| ValueCard(num, x) -> num
Is there a way I can include a range or something? Like [Ace(Suit.Heart) .. Ace(Suit.Club)]. Or even better Ace(*)
You want a wildcard pattern. The spec (ยง7.4) says:
The pattern _ is a wildcard pattern and matches any input.
let numericValue = function
| Ace _-> 11
| King _
| Queen _
| Jack _ -> 10
| ValueCard(num, _) -> num

Resources