Here’s a neat little trick I came up with (not sure if anyone else has done this yet.)
Let’s say you want to buy a pet, and you currently can’t decide.
import PetShop.Cat exposing (Cat)
import PetShop.Dog exposing (Dog)
import PetShop.Fish exposing (Fish)
You know that no matter which animal you choose, you will need to feed it. Also, cats and dogs may go outside - a fish should probably stay it its tank.
feed : Animal a -> Animal a
goOutside : DogOrCat a -> DogOrCat a
buyCat : Cat -> MyCat
buyDog : Dog -> MyDog
buyFish : Fish -> MyFish
We now want to define Animal
and DogOrCat
such that the following code-snippets compile:
PetShop.cat
|> buyCat
|> feed
|> goOutside
PetShop.dog
|> buyDog
|> feed
|> goOutside
but the same will not work for a fish
PetShop.fish
|> buyFish
|> feed
|> goOutside -->CompilerError
Solution
type Animal animal
= Animal
{ cat : Cat
, dog : Dog
, fish : Fish
}
type alias IsCat
= { isCat : ()
, isDogOrCat : ()
}
type alias IsDog
= { isDog : ()
, isDogOrCat : ()
}
type alias IsFish
= { isFish : () }
type alias MyCat =
Animal IsCat
type alias MyDog =
Animal IsDog
type alias MyFish =
Animal IsFish
type alias DogOrCat a =
Animal { a | isDogOrCat : () }
Further Ideas
If you are willing to own a Schrödingers Maybe Cat, you could implement Animal
like this instead:
type Animal animal
= Animal
{ cat : Maybe Cat
, dog : Maybe Dog
, fish : Maybe Fish
}
buyCat : Cat -> MyCat
buyCat cat =
Animal
{ cat = cat
, dog = Nothing
, fish = Nothing
}
With this implementation you can now group all your pets in a single list
toAnimal : Animal a -> Animal ()
toAnimal (Animal animal) = Animal animal
toMyCat : Animal a -> Maybe MyCat
toMyCat (Animal animal) =
if animal.cat /= Nothing then
Just (Animal animal)
else
Nothing
myAnimals : List (Animal ())
myAnimals =
[ PetStore.cat |> buyCat |> toAnimal
, PetStore.dog |> buyDog |> toAnimal
, PetStore.fish |> buyFish |> toAnimal
]
myCats : List MyCat
myCats =
myAnimals
|> List.filterMap toMyCat
So that’s it. I Hope you liked this little post