Haskell 类型同义词联合?

ape*_*pen 5 haskell

在 Haskell 中,我想做类似以下的事情

data Fruits = Apple | Orange | ...
data Meat = Chicken | Beef | ...

type Eats = Fruits | Meat
Run Code Online (Sandbox Code Playgroud)

我希望构造类型Eats,使其成为两种类型的联合。重点是我想在不添加另一层构造函数的情况下执行此操作。这在 Haskell 中可能吗?

Jon*_*rdy 3

data Fruit = Apple | Orange | ...\ndata Meat = Chicken | Beef | ...\n
Run Code Online (Sandbox Code Playgroud)\n

您可以直接构造\xe2\x80\x99t 未标记的联合。但是,您可以构造与标记联合(求和类型)等效的东西,但不需要标记,而是使用函数。

\n

像这样的和类型Either a b可以用它的模式匹配函数来表示,比如either :: (a -> c) -> (b -> c) -> Either a b -> c; 该表达式either f g e相当于case表达式case e of { Left x -> f x; Right y -> g y }

\n

因此,每当您使用 时data Food = Fruit | Meat | Vegetable | \xe2\x80\xa6,您都可以使用与此类型匹配的等效函数。也就是说,从此转换:

\n
data Food\n  = Fruit Fruit\n  | Meat Meat\n  | Vegetable Vegetable\n  -- \xe2\x80\xa6\n\n-- Energy density in kilojoules per gram\nnewtype EnergyDensity = ED Word\n\nfoodED :: Food -> EnergyDensity\nfoodED (Fruit f)     = fruitED f\nfoodED (Meat m)      = meatED m\nfoodED (Vegetable v) = vegetableED v\n-- \xe2\x80\xa6\n
Run Code Online (Sandbox Code Playgroud)\n

对此:

\n
{-# Language RankNTypes #-}\n\n-- A food-matching function.\ntype FoodF r\n  = (Fruit -> r)\n  -> (Meat -> r)\n  -> (Vegetable -> r)\n  -- \xe2\x80\xa6\n  -> r\n\n-- A polymorphic matching function.\nnewtype Food = Food { matchFood :: forall r. FoodF r }\n\nfoodED :: Food -> EnergyDensity\nfoodED food = matchFood food\n  (\\ f -> fruitED f)\n  (\\ m -> meatED m)\n  (\\ v -> vegetableED v)\n  -- \xe2\x80\xa6\n\n-- Or, simplified:\n--\n-- f food = matchFood food fruitED meatED vegetableED\n
Run Code Online (Sandbox Code Playgroud)\n

要构造这种类型,您只需创建一个Food调用适当情况的which:

\n
{-# Language BlockArguments #-}\n\nfruit :: Fruit -> Food\nfruit x = Food \\ f _m _v -> f x\n\nmeat :: Meat -> Food\nmeat x = Food \\ _f m _v -> m x\n\nvegetable :: Vegetable -> Food\nvegetable x = Food \\ _f _m v -> v x\n
Run Code Online (Sandbox Code Playgroud)\n

由于像这样的 \xe2\x80\x9cpositional\xe2\x80\x9d 匹配可能很笨拙,您还可以引入记录类型进行匹配。然而,这可能不是 \xe2\x80\x99t 作为 \xe2\x80\x9cclean\xe2\x80\x9d 或像你想象的那样简单。

\n

基本问题是我们\xe2\x80\x99正在创建类型层次结构,然后尝试删除生成的嵌套标签。通常最好通过制作单一类型的 \xe2\x80\x99s\xe2\x80\x99s 来避免嵌套,例如 \xe2\x80\x9cfoods\xe2\x80\x9d,其中像 \xe2\x80\x9cis Fruit\xe2\x80\ x9d 是食物的属性。这可以通过定义函数动态完成:

\n
import Data.Set (Set)\nimport qualified Data.Set as Set\n\ndata Food\n  = Apple\n  | Orange\n  -- \xe2\x80\xa6\n  | Chicken\n  | Beef\n  -- \xe2\x80\xa6\n\nisFruit :: Food -> Bool\nisFruit Apple   = True\nisFruit Orange  = True\n-- \xe2\x80\xa6\nisFruit Chicken = False\nisFruit Beef    = False\n-- \xe2\x80\xa6\n\ndata Animal = Fowl | Beast | Fish\n\nfoodAnimal :: Food -> Maybe Animal\nfoodAnimal Chicken = Just Fowl\nfoodAnimal Beef    = Just Beast\nfoodAnimal _       = Nothing\n\nanimalProducts :: [Food] -> Set Animal\nanimalProducts foods\n  = Set.fromList\n    [ animal\n    | food <- foods\n    , Just animal <- foodAnimal food\n    ]\n
Run Code Online (Sandbox Code Playgroud)\n

或者静态地,如果所有类别都是不相交的,则在数据类型上使用类型标记:

\n
{-# Language\n    DataKinds,\n    GADTs,\n    KindSignatures #-}\n\ndata Food (t :: FoodGroup) where\n  Apple   :: Food 'FruitGroup\n  Orange  :: Food 'FruitGroup\n  -- \xe2\x80\xa6\n  Chicken :: Food 'MeatGroup\n  Beef    :: Food 'MeatGroup\n  -- \xe2\x80\xa6\n\ndata FoodGroup\n  = FruitGroup\n  | MeatGroup\n  | VegetableGroup\n  -- \xe2\x80\xa6\n\n-- Aliases for subsets of \xe2\x80\x98Food\xe2\x80\x99.\ntype Fruit     = Food 'FruitGroup\ntype Meat      = Food 'MeatGroup\ntype Vegetable = Food 'VegetableGroup\n\n-- Non-meat cases are impossible because t=MeatGroup, so\n-- we don\xe2\x80\x99t need to match them, and don\xe2\x80\x99t need to return\n-- a \xe2\x80\x98Maybe\xe2\x80\x99-wrapped result.\nmeatAnimal :: Food 'MeatGroup -> Animal\nmeatAnimal Chicken = Fowl\nmeatAnimal Beef    = Beast\n
Run Code Online (Sandbox Code Playgroud)\n

这可以为您提供更多的类型安全性和模式匹配的便利性,但它也要求您小心类别的设计:您将把 放在哪里Tomato

\n