Reputation: 60691
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?
Upvotes: 1
Views: 106
Reputation: 424
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))
Upvotes: 1
Reputation: 6324
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.
Upvotes: 2
Reputation: 1685
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
Upvotes: 3
Reputation: 11875
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.
Upvotes: 2