有没有办法结合类型约束?

时间:2013-08-22 00:25:06

标签: haskell type-constraints

在Haskell中,有没有办法将几个类型的约束组合在一起,这样如果满足其中任何一个,就会满足联合?

例如,假设我有一个由DataKind参数化的GADT,我希望一些构造函数只返回给定类型的某些构造函数的值,伪Haskell将是:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
module Temp where

data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black

data Fruit (c :: Color) where
  Banana :: (c ~ Green | c ~ Yellow | c ~ Black)  => Fruit c
  Apple  :: (c ~ Red | c ~ Green )                => Fruit c
  Grape  :: (c ~ Red | c ~ Green | c ~ White)     => Fruit c
  Orange :: (c ~ Tawny )                          => Fruit c

我可以尝试使用类型类来实现OR:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
module Temp where

data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black

data Fruit (c :: Color) where
  Banana :: BananaColor c => Fruit c
  Apple  :: AppleColor c  => Fruit c
  Grape  :: GrapeColor c  => Fruit c
  Orange :: OrangeColor c => Fruit c

class BananaColor (c :: Color)
instance BananaColor Green
instance BananaColor Yellow
instance BananaColor Black

class AppleColor (c :: Color)
instance AppleColor Red
instance AppleColor Green

class GrapeColor (c :: Color)
instance GrapeColor Red
instance GrapeColor Green
instance GrapeColor White

class OrangeColor (c :: Color)
instance OrangeColor Tawny

但这不仅是冗长的,而且与我原先的联盟关闭时的意图略有不同,但是类型类都是开放的。没有什么可以阻止某人定义

instance OrangeColor Blue

因为它是开放的,除非被告知,否则编译器无法推断[Apple, Grape, Banana]必须是[Fruit Green]类型。

2 个答案:

答案 0 :(得分:4)

遗憾的是,我无法想出一种字面意思或Constraint的方法,但如果我们只是在一起,就像在你的例子中一样,我们可以为你的类型方法增添趣味并使类型系列和提升的布尔值关闭。这只适用于GHC 7.6及以上版本;最后,我提到了它如何在GHC 7.8中更好,以及如何将它向后移植到GHC 7.4。

这个想法是这样的:就像我们可以声明一个值级函数isBananaColor :: Color -> Bool一样,我们也可以声明一个类型级函数IsBananaColor :: Color -> Bool

type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green  = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black  = True
type instance IsBananaColor White  = False
type instance IsBananaColor Red    = False
type instance IsBananaColor Blue   = False
type instance IsBananaColor Tawny  = False
type instance IsBananaColor Purple = False

如果我们愿意,我们甚至可以添加

type BananaColor c = IsBananaColor c ~ True

然后我们对每种水果颜色重复此操作,并在第二个示例中定义Fruit

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}

data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black

data Fruit (c :: Color) where
  Banana :: BananaColor c => Fruit c
  Apple  :: AppleColor  c => Fruit c
  Grape  :: GrapeColor  c => Fruit c
  Orange :: OrangeColor c => Fruit c

type family   IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green  = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black  = True
type instance IsBananaColor White  = False
type instance IsBananaColor Red    = False
type instance IsBananaColor Blue   = False
type instance IsBananaColor Tawny  = False
type instance IsBananaColor Purple = False
type BananaColor c = IsBananaColor c ~ True

type family   IsAppleColor (c :: Color) :: Bool
type instance IsAppleColor Red    = True
type instance IsAppleColor Green  = True
type instance IsAppleColor White  = False
type instance IsAppleColor Blue   = False
type instance IsAppleColor Yellow = False
type instance IsAppleColor Tawny  = False
type instance IsAppleColor Purple = False
type instance IsAppleColor Black  = False
type AppleColor c = IsAppleColor c ~ True

type family   IsGrapeColor (c :: Color) :: Bool
type instance IsGrapeColor Red    = True
type instance IsGrapeColor Green  = True
type instance IsGrapeColor White  = True
type instance IsGrapeColor Blue   = False
type instance IsGrapeColor Yellow = False
type instance IsGrapeColor Tawny  = False
type instance IsGrapeColor Purple = False
type instance IsGrapeColor Black  = False
type GrapeColor c = IsGrapeColor c ~ True

-- For consistency
type family   IsOrangeColor (c :: Color) :: Bool
type instance IsOrangeColor Tawny  = True
type instance IsOrangeColor White  = False
type instance IsOrangeColor Red    = False
type instance IsOrangeColor Blue   = False
type instance IsOrangeColor Yellow = False
type instance IsOrangeColor Green  = False
type instance IsOrangeColor Purple = False
type instance IsOrangeColor Black  = False
type OrangeColor c = IsOrangeColor c ~ True

(如果您愿意,可以删除-XConstraintKindstype XYZColor c = IsXYZColor c ~ True类型,只需将Fruit的构造函数定义为XYZ :: IsXYZColor c ~ True => Fruit c。)

现在,这对你有什么影响,有什么不给你买的?从好的方面来说,你可以根据需要定义你的类型,这绝对是一个胜利;自Color关闭以来,没有人可以添加更多类型系列实例并将其打破。

然而,还有缺点。您无法自动告诉您[Apple, Grape, Banana]类型为Fruit Green的推断;更糟糕的是,[Apple, Grape, Banana]具有完全有效的类型(AppleColor c, GrapeColor c, BananaColor c) => [Fruit c]。是的,没有办法将其单一化,但GHC无法解决这个问题。说实话,我无法想象任何解决方案会给你这些属性,尽管我总是准备好惊讶。此解决方案的另一个明显问题是如何 long - 您需要为每个IsXYZColor类型系列定义所有八种颜色案例! (每种类型的全新系列使用也很烦人,但这种形式的解决方案是不可避免的。)


我上面提到GHC 7.8会让这更好;它可以通过避免为每个IsXYZColor类列出每个案例的需要来做到这一点。怎么样?好吧,Richard Eisenberg 闭合重叠有序类型族引入到GHC HEAD中,它将在7.8中提供。关于这个主题有a paper in sumbission to POPL 2014(和extended version),理查德也写了an introductory blog post(似乎有过时的语法)。

这个想法是允许类型族实例被声明为普通函数:方程式必须全部在一个地方声明(删除开放世界假设)并按顺序尝试,这允许重叠。像

这样的东西
type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green  = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black  = True
type instance IsBananaColor c      = False

是不明确的,因为IsBananaColor Green匹配第一个和最后一个方程;但在一个普通的功能,它工作正常。所以新的语法是:

type family IsBananaColor (c :: Color) :: Bool where
  IsBananaColor Green  = True
  IsBananaColor Yellow = True
  IsBananaColor Black  = True
  IsBananaColor c      = False

type family ... where { ... }块以想要定义它的方式定义类型族;如上所述,它表示该类型系列是闭合的,有序的和重叠的。因此,代码将变成类似于GHC 7.8中的以下内容(未经测试,因为我没有在我的机器上安装它):

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}

data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black

data Fruit (c :: Color) where
  Banana :: IsBananaColor c ~ True => Fruit c
  Apple  :: IsAppleColor  c ~ True => Fruit c
  Grape  :: IsGrapeColor  c ~ True => Fruit c
  Orange :: IsOrangeColor c ~ True => Fruit c

type family IsBananaColor (c :: Color) :: Bool where
  IsBananaColor Green  = True
  IsBananaColor Yellow = True
  IsBananaColor Black  = True
  IsBananaColor c      = False

type family IsAppleColor (c :: Color) :: Bool where
   IsAppleColor Red   = True
   IsAppleColor Green = True
   IsAppleColor c     = False

type IsGrapeColor (c :: Color) :: Bool where
  IsGrapeColor Red   = True
  IsGrapeColor Green = True
  IsGrapeColor White = True
  IsGrapeColor c     = False

type family IsOrangeColor (c :: Color) :: Bool where
  IsOrangeColor Tawny = True
  IsOrangeColor c     = False
好吧,我们可以在没有从无聊中入睡的情况下阅读这篇文章!实际上,您会注意到我为此代码切换到显式IsXYZColor c ~ True版本;我这样做是因为因为这些更短的定义,额外的四种类型同义词的样板变得更加明显和令人烦恼!


然而,让我们走向相反的方向并使这个代码变得更加丑陋。为什么?好吧,GHC 7.4(唉,我的机器上仍有的东西)不支持非*结果类型的类型系列。我们可以做些什么呢?我们可以使用类型类和函数依赖来伪造它。我们的想法是,我们有一个类型类IsBananaColor :: Color -> Bool,而不是IsBananaColor :: Color -> Bool -> Constraint,我们添加了从颜色到​​布尔值的函数依赖。那么IsBananaColor c b是可以满足的,当且仅当更好的版本中IsBananaColor c ~ b;因为Color已经关闭,我们有一个功能依赖,它仍然给我们相同的属性,它只是更丑陋(虽然大多数在概念上是如此)。不用多说,完整的代码:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleContexts #-}

data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black

data Fruit (c :: Color) where
  Banana :: BananaColor c => Fruit c
  Apple  :: AppleColor  c => Fruit c
  Grape  :: GrapeColor  c => Fruit c
  Orange :: OrangeColor c => Fruit c

class    IsBananaColor (c :: Color) (b :: Bool) | c -> b
instance IsBananaColor Green  True
instance IsBananaColor Yellow True
instance IsBananaColor Black  True
instance IsBananaColor White  False
instance IsBananaColor Red    False
instance IsBananaColor Blue   False
instance IsBananaColor Tawny  False
instance IsBananaColor Purple False
type BananaColor c = IsBananaColor c True

class    IsAppleColor (c :: Color) (b :: Bool) | c -> b
instance IsAppleColor Red    True
instance IsAppleColor Green  True
instance IsAppleColor White  False
instance IsAppleColor Blue   False
instance IsAppleColor Yellow False
instance IsAppleColor Tawny  False
instance IsAppleColor Purple False
instance IsAppleColor Black  False
type AppleColor c = IsAppleColor c True

class    IsGrapeColor (c :: Color) (b :: Bool) | c -> b
instance IsGrapeColor Red    True
instance IsGrapeColor Green  True
instance IsGrapeColor White  True
instance IsGrapeColor Blue   False
instance IsGrapeColor Yellow False
instance IsGrapeColor Tawny  False
instance IsGrapeColor Purple False
instance IsGrapeColor Black  False
type GrapeColor c = IsGrapeColor c True

class    IsOrangeColor (c :: Color) (b :: Bool) | c -> b
instance IsOrangeColor Tawny  True
instance IsOrangeColor White  False
instance IsOrangeColor Red    False
instance IsOrangeColor Blue   False
instance IsOrangeColor Yellow False
instance IsOrangeColor Green  False
instance IsOrangeColor Purple False
instance IsOrangeColor Black  False
type OrangeColor c = IsOrangeColor c True

答案 1 :(得分:0)

以下是我对该问题进行编码的尝试。主要思想是将水果表示为类型类,将各种类型的水果表示为实现此类型类的类型

data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black

class Fruit a where
  getColor :: a -> Color

data Banana where
  GreenBanana :: Banana
  YellowBanana :: Banana
  BlackBanana :: Banana

instance Fruit Banana where
  getColor GreenBanana = Green
  getColor YellowBanana = Yellow
  getColor BlackBanana = Black

data Apple where
  GreenApple :: Apple
  RedApple :: Apple

instance Fruit Apple where
  getColor GreenApple = Green
  getColor RedApple = Red

你的问题最后一行表明你想要[Fruit Green]类型的东西,这显然意味着Fruit Green应该是一种类型,其中上面的代码中的绿色是一个值构造函数。我们必须将Green作为一种类型,如下所示:

data Red = Red
data Green = Green
data Black = Black

data Fruit c where
  GreenBanana :: Fruit Green
  BlackBanana :: Fruit Black
  RedApple :: Fruit Red
  GreenApple :: Fruit Green


greenFruits :: [Fruit Green]
greenFruits = [GreenBanana, GreenApple]