Alex Gordon
Alex Gordon

Reputation: 60691

how to create a type based on another type

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

Answers (4)

Guran
Guran

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

s952163
s952163

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

tranquillity
tranquillity

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

BitTickler
BitTickler

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

Related Questions