I am trying to represent standard playing cards in F#. My goal is to implement a clone of Microsoft Solitaire (the one that comes with Windows) , a game in which Cards' Suit, Face, and Color are important. This exercise is mostly intended as a way to learn some F#.
I have considered using discriminated unions:
type Suit =
| Diamonds
| Hearts
| Clubs
| Spades
type Color =
| Red
| Black
type Face =
Two | Three | Four | Five | Six | Seven |
Eight | Nine | Ten | Jack | Queen | King | Ace
with a record type for Card:
type Card = {
suit: Suit;
face: Face;
color: Color;
}
However, a Card's Color can be inferred from its Suit—all Diamonds and Hearts are Red, and all Clubs and Spades are Black. Suit cannot be determined from Color alone. Perhaps something like this is appropriate:
type Suit =
| Diamonds of Color //should always be red
| Hearts of Color //should always be red
| Clubs of Color //should always be black
| Spades of Color //should always be black
type Face =
Two | Three | Four | Five | Six | Seven |
Eight | Nine | Ten | Jack | Queen | King | Ace
type Card = {
suit: Suit;
face: Face;
}
But this doesn't seem right, since this allows incorrect combinations, e.g. Black Hearts and Red Spades.
My questions are:
What is the most idiomatic way to handle Suit and Color, considering that Color is dependent on Suit?
Should the concept of Color even be explicitly represented? One could theoretically just replace all occurrences of Color with pattern matches against Diamonds or Hearts (which are red) and Clubs or Spades (which are black).
Since Color can always be inferred from Suit, there's no reason to model it explicitly; you'll want to make illegal states unrepresentable.
You can still get a nice programming experience out of your model, and have a nice way of modelling colour, using an Active Pattern:
type Suit =
| Diamonds
| Hearts
| Clubs
| Spades
let (|Red|Black|) suit =
match suit with
| Diamonds | Hearts -> Red
| Clubs | Spades -> Black
This would enable you to pattern match on Suit, like this inane example:
let printColor card =
match card.Suit with
| Red -> "Red"
| Black -> "Black"
Usage example from FSI:
> printColor { Suit = Spades; Face = Ace };;
val it : string = "Black"
> printColor { Suit = Diamonds; Face = King };;
val it : string = "Red"
You can add a recording method:
type Card =
{suit: Suit;face: Face}
member this.Color =
match this.suit with
| Diamonds | Hearts -> Red
| Clubs | Spades -> Black
Example:
let v = {suit = Diamonds;face = Two}
printfn "%A" v.Color
let v1 = {suit = Clubs;face = Two}
printfn "%A" v1.Color
Red
Black
Для продолжения нажмите любую клавишу . . .
Related
I've got animals and colors:
type Animal =
| Cat
| Dog
| Giraffe
type Colors =
| Blue
| Purple
I want to combine the types into a third type, constraining the animals and colors that are available:
type AnimalColorsAvailable =
| Cat of Blue
| Dog of Purple
| Giraffe of Blue
Please forgive the beginner's question.
I'm getting this error
(491,14): error FS0039: The type 'Blue' is not defined.
What am I doing wrong? How do we combine the two types to represent some "valid" state of the world?
If you only want certain combinations available then you create a DU of those combinations as you have done with Animal and Colors:
type AnimalColorsAvailable =
| BlueCat
| PurpleDog
| BlueGiraffe
To relate that back to the individual Colors and Animals you use a function:
let animalAndColor a =
match a with
| BlueCat -> Blue, Cat
| PurpleDog -> Purple, Dog
| BlueGiraffe -> Blue, Giraffe
Assuming you want to be able to retrieve a colored animals animal kind and color kind from a given value, you could resort to some bitwise encoding of that combined information. See the code below, if you are not sure to see what I mean with this:
type Color = | Blue = 0 | Purple = 1
type Animal = | Cat = 0 | Dog = 1 | Giraffe = 2
let makeAnimalCode (a : Animal) (c : Color) = (int a) <<< 4 ||| (int c)
type ExistingAnimal =
| BlueCat = makeAnimalCode Cat Blue
| PurpleGiraffe = makeAnimalCode Giraffe Purple
| BlueDog = makeAnimalCode Dog Blue
let animalKind (ea : ExistingAnimal) = enum<Animal> ((int ea) >>> 4)
let animalColor (ea :ExistingAnimal) = enum<Color> ((int ea) &&& 0xf)
The only (and major) downside of the code above, is, that it probably won't work because the function makeAnimalCode would have to run at compile time. But maybe someone else knows a solution for that.
First, the reason you are getting the error is because the actual types are Animal and Colors, so there is indeed no type of Blue (it is rather a constructor). Second, while constrained types (lists or strings of a certain length for example) are an interesting idea, most languages deal with it via doing verification in the constructor (See for example: https://fsharpforfunandprofit.com/posts/designing-with-types-more-semantic-types/ and https://fsharpforfunandprofit.com/posts/discriminated-unions/), Idris though is one langue that has explicit support for this. That seems to be an overkill, so you could create some records or tuples (which is a multiplication of types) for the possible combinations. But you can stick to the original definition of your Discriminated Unions, as it was actually a good start.
In this version there are actually no constraints on the possible combinations, so even if you can't have a red dog, you could create one:
type Animal =
| Cat
| Dog
| Giraffe
type Colors =
| Blue
| Purple
| Red
type AnimalColorsAvailable = Colors * Animal
let redddog = (Red, Dog) // val redddog : Colors * Animal = (Red, Dog)
In this version, you can only create the specified constrained types, and if you wanted a Red Dog, it can only be a tuple, not a ConstrainedColors, so Blue Dog will compile but Red Dog won't
type Dog = Dog
type Cat = Cat
type Blue = Blue
type Purple = Purple
type Red = Red
let reddog = (Red, Dog)
type ConstrainedColors =
| Blue of Dog
| Purple of Dog
let bluedog = Blue Dog // val bluedog : ConstrainedColors = Blue Dog
let reddog = Red Dog // error FS0003: This value is not a function and cannot be applied.
I would do something like this to express what you are after:
type Animal =
| Lion
| Elephant
| Bear
| Unicorn
type Color =
| Yellow
| Grey
| Black
| Brown
| Pink
type ExistingAnimal = private ExistingAnimal of (Animal*Color) option
module ExistingAnimal =
let fromAnimalAndColor = function
| Lion, Yellow -> ExistingAnimal (Some (Lion, Yellow))
| Elephant, Grey -> ExistingAnimal (Some (Elephant, Grey))
| Bear, Brown -> ExistingAnimal (Some (Bear, Brown))
| Bear, Black -> ExistingAnimal (Some (Bear, Black))
| _ -> ExistingAnimal None
sprintf "%A" (ExistingAnimal.fromAnimalAndColor (Elephant, Pink))
I'm trying to compose an F# type that would have the following signature:
type Foo = (Distance * Event * Course)
So that you would create a Foo like this:
let bar = (25, Freestyle, LCM)
Now the second two parts (event and course) are easy–I'm sure that distance is also, I just don't know it yet–I just use a discriminated union.
Let's say that the only valid values for distance are [25;50;100], what is the best way to construct the Distance type?
I assume the goal is to have easy access to a real integer value, but restrict it to only a set number of cases.
#Petr's suggestion would work fine, you would just convert the enum value to int.
Another option is to calculate the value in a method on a DU type:
type Distance =
TwentyFive | Fifty | Hundred
member this.ToInt() =
match this with
| TwentyFive -> 25
| Fifty -> 50
| Hundred -> 100
or if you want stronger syntax support, a single-case active pattern might be nice:
type Event = Freestyle | Backstroke
type Distance = TwentyFive | Fifty | Hundred
let (|IntDistance|) d =
match d with
| TwentyFive -> 25
| Fifty -> 50
| Hundred -> 100
let race = (Fifty, Freestyle)
let (IntDistance(dist), evt) = race
printfn "Race info: %d %A" dist evt
match race with
| IntDistance(dist), Freestyle -> ...
| IntDistance(dist), Backstroke -> ...
You can use .NET enums:
type Distance = TwentyFive=25 | Fifty=50 | Hundred=100
For pattern matching you must use qualified name though: Distance.Fifty
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?
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
Imagine this discriminated union:
type Direction =
| North
| South
| East
| West
Now imagine that I want a type which only accepts tuples of (North, South) or (East, West). Perhaps this will describe train routes which only run North to South, or East to West. (North, East) and (South, West) should be forbidden, perhaps because the trains don't run like that.
This doesn't work:
type TrainLines =
| North, South
| East, West
Even though that doesn't work, perhaps you can see what I'm trying to do.
This works, but doesn't restrict possibilites to only (North, South) and (East, West):
type TrainLines = Direction * Direction
Any guidance would be welcomed.
This isn't exactly what you asked for, but I think it's likely that
type TrainLines =
| NorthSouth
| EastWest
would do you good. If needed you could add e.g.
with member this.Directions =
match this with
| NorthSouth -> [North; South]
| EastWest -> [East; West]
You can't do exactly what you want, because North, South, East and West aren't types of their own. So you can't have something like North * South; and North, South is a value of type Direction * Direction, but not the only one. Just like you can't define the type
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Now imagine that I want a type which
only accepts tuples of (North, South)
or (East, West).
Interesting feature request: sounds like you want "static range constraints", e.g.
//fictional syntax for static range constraints
type TrainLine = (a,b) where (a=North and b=South) or (a=East and b=West)
let northSouth = TrainLine(North,South) // compiles
let northEast = TrainLine(North,East) // does not compile
Such a feature seems plausible in a language with only literals, but where we get into trouble is when we consider values only known at runtime:
let parseDirection userInputString =
match userInputString with
| "North" -> North
| "South" -> South
| "East" -> East
| "West" -> West
| _ -> failwith "invalid direction"
let directionA = parseDirection(System.Console.ReadLine())
let directionB = parseDirection(System.Console.ReadLine())
//compiler can't enforce constraint because direction values unknown until runtime
let trainLine = TrainLine(directionA,directionB)
However, F#'s does have a nice set of features in Active Patterns which can help to convert runtime input into a set of known cases and then proceed with static confidence:
let (|NorthSouth|EastWest|Invalid|) (a,b) =
match a,b with
| North,South -> NorthSouth
| East,West -> EastWest
| _ -> Invalid
let trainLines = [(North,South); (South,North); (East,West); (North,East);(North,North); (South,East)]
let isValidTrainLine trainLine =
match trainLine with
| NorthSouth -> true
| EastWest -> true
| Invalid -> false
let validTrainLines = trainLines |> List.filter isValidTrainLine
//val it : (Direction * Direction) list = [(North, South); (East, West)]
You really want polymorphic variants from OCaml:
[ `North | `South | `East | `West ]
[ `North | `South ] * [ `East | `West ]
but F# is currently incapable of expressing this. I actually find I need this a lot in my work...
You can introduce unnecessary layers of union types:
type ns = North | South
type ew = East | West
type nsew = NorthSouth of ns | EastWest of ew
and then use ns * ew.
Another solution that can sometimes work well is to use an interface to provide a consistency between two separate union types:
type IDir = abstract AsInt : int
type ns =
| North
| South
interface IDir with
method d.AsInt =
match d with North -> 0 | South -> 1
type ew =
| East
| West
interface IDir with
method d.AsInt =
match d with East -> 2 | West -> 3
Sadly, this imposes all of the disadvantages of OOP...