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

jhe*_*dus 22 haskell ghc type-kinds constraint-kinds

什么是约束类型

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

到底有什么好处呢?

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

例如,为什么在代码中使用它?

Lui*_*las 26

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

  1. 通过类型类约束对类型进行参数化
  2. 编写类型类,允许其实例指定所需的约束.

也许最好用一个例子来说明这一点.经典的Haskell瑕疵之一是你不能Functor为类型参数强加类约束的类型创建一个实例; 例如,库中的Setcontainers,需要Ord对其元素进行约束.原因是在"vanilla"Haskell中,你必须对类本身有约束:

class OrdFunctor f where
    fmap :: Ord b => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

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

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

{-# 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
Run Code Online (Sandbox Code Playgroud)

(请注意,这不是作出的唯一障碍Functor实例Set;看到这个讨论.此外,信贷这个答案的NoConstraint伎俩).

然而,这种解决方案尚未被普遍采用,因为ConstraintKinds它仍然或多或少是一个新功能.


另一个用途ConstraintKinds是通过类约束或类来参数化类型.我将重现我写的这个Haskell"Shape Example"代码:

{-# 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
Run Code Online (Sandbox Code Playgroud)

这里Object类型的参数是一个类型类(kind * -> Constraint),所以你可以有类似Object Shapewhere 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
Run Code Online (Sandbox Code Playgroud)

这种Object类型的功能是两个功能的组合:

  1. 存在类型(在此处启用GADTs),它允许我们在同一Object类型中存储异构类型的值.
  2. ConstraintKinds,它允许我们,而不是硬编码Object到一些特定的类约束集,让Object类型的用户指定他们想要的约束作为该Object类型的参数.

现在,我们不仅可以创建异构的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)]
Run Code Online (Sandbox Code Playgroud)

......但得益于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)
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢Luis的详细解答! (3认同)

小智 13

ConstraintKind扩展允许使用的Constraint种类.在上下文中出现的每个表达式(通常是在::和之间的东西=>)都有种类Constraint.例如,在ghci中:

Prelude> :kind Num
Num :: * -> Constraint
Run Code Online (Sandbox Code Playgroud)

通常,不可能手动使用这种类型,但ConstraintKinds扩展允许它.例如,现在可以写:

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

既然你有一些类型(种类*),并给出一个Constraint,你可以编写这样的代码.

Prelude> :{
Prelude| let myAwesomeFunction :: HasRequiredProperties a => a -> IO ()
Prelude|     myAwesomeFunction x = undefined
Prelude| :}
Run Code Online (Sandbox Code Playgroud)

您链接到的库可能会MonadWidget作为类型的同义词使用Constraint,但您必须仔细查看以确保.