ConstraintKinds在一个超级简单的例子中解释

时间:2015-07-09 12:13:26

标签: haskell ghc type-kinds constraint-kinds

什么是Constraint kind

为什么有人会使用它(在实践中)?

有什么好处?

您能举出一个简单的代码示例来说明前两个问题的答案吗?

为什么在this代码中使用它?

2 个答案:

答案 0 :(得分:25)

好吧,我会提到它允许你做的两件实事:

  1. 按类型约束参数化类型
  2. 编写允许其实例指定所需约束的类型类。
  3. 也许最好用一个例子来说明这一点。经典的Haskell瑕疵之一是你不能为类型参数强加类约束的类型创建Functor实例;例如,Set库中的containers类,其元素需要Ord约束。原因是在“vanilla”Haskell中,你必须对类本身有约束:

    class OrdFunctor f where
        fmap :: Ord b => (a -> b) -> f a -> f b
    

    ...但是此类仅适用于需要特定Ord约束的类型。不是一般的解决方案!

    那么如果我们可以采用该类定义并抽象出Ord约束,允许单个实例说明它们需要什么约束呢?好吧,ConstraintKindsTypeFamilies允许:

    {-# LANGUAGE ConstraintKinds, TypeFamilies, FlexibleInstances #-}
    
    import Prelude hiding (Functor(..))
    import GHC.Exts (Constraint)
    import Data.Set (Set)
    import qualified Data.Set as Set
    
    -- | A 'Functor' over types that satisfy some constraint.
    class Functor f where
       -- | The constraint on the allowed element types.  Each
       -- instance gets to choose for itself what this is.
       type Allowed f :: * -> Constraint
    
       fmap :: Allowed f b => (a -> b) -> f a -> f b
    
    instance Functor Set where
        -- | 'Set' gets to pick 'Ord' as the constraint.
        type Allowed Set = Ord
        fmap = Set.map
    
    instance Functor [] where
        -- | And `[]` can pick a different constraint than `Set` does.
        type Allowed [] = NoConstraint
        fmap = map
    
    -- | A dummy class that means "no constraint."
    class NoConstraint a where
    
    -- | All types are trivially instances of 'NoConstraint'.
    instance NoConstraint a where
    

    (请注意,这不是将Functor实例设为Set的唯一障碍;请参阅this discussion。此外,credit to this answer for the NoConstraint trick。)

    但是,这种解决方案尚未被普遍采用,因为ConstraintKinds或多或少是一个新功能。

    ConstraintKinds的另一个用途是通过类约束或类来参数化类型。我将重现this Haskell "Shape Example" code that I wrote

    {-# LANGUAGE GADTs, ConstraintKinds, KindSignatures, DeriveDataTypeable #-}
    {-# LANGUAGE TypeOperators, ScopedTypeVariables, FlexibleInstances #-}
    
    module Shape where
    
    import Control.Applicative ((<$>), (<|>))
    import Data.Maybe (mapMaybe)
    import Data.Typeable
    import GHC.Exts (Constraint)
    
    -- | Generic, reflective, heterogeneous container for instances
    -- of a type class.
    data Object (constraint :: * -> Constraint) where
        Obj :: (Typeable a, constraint a) => a -> Object constraint
               deriving Typeable
    
    -- | Downcast an 'Object' to any type that satisfies the relevant
    -- constraints.
    downcast :: forall a constraint. (Typeable a, constraint a) =>
                Object constraint -> Maybe a
    downcast (Obj (value :: b)) = 
      case eqT :: Maybe (a :~: b) of
        Just Refl -> Just value
        Nothing -> Nothing
    

    此处Object类型的参数是类型类(种类* -> Constraint),因此您可以使用Object Shape类型,其中Shape是一个类:

    class Shape shape where
      getArea :: shape -> Double
    
    -- Note how the 'Object' type is parametrized by 'Shape', a class 
    -- constraint.  That's the sort of thing ConstraintKinds enables.
    instance Shape (Object Shape) where
        getArea (Obj o) = getArea o
    

    Object类型的作用是两个功能的组合:

    1. 存在类型(由GADTs启用),它允许我们将异构类型的值存储在同一Object类型中。
    2. ConstraintKinds,它允许我们将Object类型的用户指定他们想要的约束作为参数,而不是将Object硬编码到某些特定的类约束。 Object类型。
    3. 现在,我们不仅可以创建Shape个实例的异构列表:

      data Circle = Circle { radius :: Double }
                  deriving Typeable
      
      instance Shape Circle where
        getArea (Circle radius) = pi * radius^2
      
      
      data Rectangle = Rectangle { height :: Double, width :: Double }
                     deriving Typeable
      
      instance Shape Rectangle where
        getArea (Rectangle height width) = height * width
      
      exampleData :: [Object Shape]
      exampleData = [Obj (Circle 1.5), Obj (Rectangle 2 3)]
      

      ...但是由于Typeable中的Object约束,我们可以向下转换:如果我们正确猜出Object中包含的类型,我们可以恢复原始类型:

      -- | For each 'Shape' in the list, try to cast it to a Circle.  If we
      -- succeed, then pass the result to a monomorphic function that 
      -- demands a 'Circle'.  Evaluates to:
      --
      -- >>> example
      -- ["A Circle of radius 1.5","A Shape with area 6.0"]
      example :: [String]
      example = mapMaybe step exampleData
        where step shape = describeCircle <$> (downcast shape)
                       <|> Just (describeShape shape)
      
      describeCircle :: Circle -> String
      describeCircle (Circle radius) = "A Circle of radius " ++ show radius
      
      describeShape :: Shape a => a -> String
      describeShape shape = "A Shape with area " ++ show (getArea shape)
      

答案 1 :(得分:12)

ConstraintKind扩展程序允许使用Constraint类型。上下文中出现的每个表达式(通常是::=>之间的内容)都有Constraint种。例如,在ghci中:

Prelude> :kind Num
Num :: * -> Constraint

通常,无法手动使用此类,但ConstraintKinds扩展名允许。例如,现在可以写:

Prelude> :set -XConstraintKinds
Prelude> type HasRequiredProperties a = (Num a, Read a, Show a, Monoid a)
Prelude> :kind HasRequiredProperties
HasRequiredProperties :: * -> Constraint

既然你有一些类型(种类*)并且提供Constraint,你可以编写这样的代码。

Prelude> :{
Prelude| let myAwesomeFunction :: HasRequiredProperties a => a -> IO ()
Prelude|     myAwesomeFunction x = undefined
Prelude| :}

您链接到的图书馆可能使用MonadWidget作为Constraint种类型的同义词,但您必须仔细查看以确定。