在 Haskell 中,我想做类似以下的事情
data Fruits = Apple | Orange | ...
data Meat = Chicken | Beef | ...
type Eats = Fruits | Meat
Run Code Online (Sandbox Code Playgroud)
我希望构造类型Eats,使其成为两种类型的联合。重点是我想在不添加另一层构造函数的情况下执行此操作。这在 Haskell 中可能吗?
data Fruit = Apple | Orange | ...\ndata Meat = Chicken | Beef | ...\nRun 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 }。
因此,每当您使用 时data Food = Fruit | Meat | Vegetable | \xe2\x80\xa6,您都可以使用与此类型匹配的等效函数。也就是说,从此转换:
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\nRun 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\nRun Code Online (Sandbox Code Playgroud)\n要构造这种类型,您只需创建一个Food调用适当情况的which:
{-# 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\nRun 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 是食物的属性。这可以通过定义函数动态完成:
\nimport 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 ]\nRun 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\nRun Code Online (Sandbox Code Playgroud)\n这可以为您提供更多的类型安全性和模式匹配的便利性,但它也要求您小心类别的设计:您将把 放在哪里Tomato?