问题描述
什么是?
为什么有人会使用它(在实践中)?
这有什么好处?
你能举一个简单的代码示例来说明前两个问题的答案吗?
为什么会这样?例如,在代码中使用?
好吧,我会提及它允许你做的两件实用的事情:
- 通过类型约束参数化类型
- 编写允许其实例指定它们需要的约束的类型类。
也许最好用一个例子来说明这一点。经典的Haskell瑕疵之一是,对于对类型参数施加类约束的类型,您不能创建 Functor 实例;例如, containers 库中的 Set 类,这需要 Ord 限制其元素。原因是在vanillaHaskell中,你必须对类本身有约束:
class OrdFunctor f其中
fmap :: Ord b => (a - > b) - > f a - > fb
...但是这个类只适用于需要 Ord 约束。不是一般的解决方案!
那么,如果我们可以接受这个类定义并抽象出 Ord 约束,那么该怎么办?个人实例来说明他们需要什么约束?那么, ConstraintKinds 加上 TypeFamilies 允许:
<$ p $
$ b $ import $前导符隐藏(Functor(..))
import GHC.Exts(Constraint)
导入Data.Set(Set)
导入限定的Data.Set as Set
- |一个'Functor'满足某些约束的类型。
类Functor f其中
- |允许的元素类型的约束。每个
- 实例都可以自己选择这是什么。
类型允许f :: * - >约束
fmap ::允许f b => (a - > b) - > f a - > f b
实例Functor Set其中
- | 'Set'选择'Ord'作为约束。
类型允许设置= Ord
fmap = Set.map
实例Functor []其中
- | ``]`可以选择与`Set`不同的约束。
类型允许[] = NoConstraint
fmap = map
- |一个虚拟的类,意思是没有约束。
class NoConstraint a其中
- |所有类型都是无约束的平凡实例。
实例NoConstraint a其中
(请注意,这不是制作a的唯一障碍 Functor 实例到 Set ;参见。此外,)。
尽管这种解决方案尚未普遍采用,因为 ConstraintKinds 仍然或多或少是一项新功能。
$ b
ConstraintKinds 的另一个用途是通过类约束或类来参数化类型。我将重现:
{ - #LANGUAGE GADTs,ConstraintKinds,KindSignatures,DeriveDataTypeable# - }
{ - #LANGUAGE TypeOperators,ScopedTypeVariables,FlexibleInstances# - }
模块形状其中
import Control.Applicative((< $>),(< |>))
import Data.Maybe(mapMaybe)
import Data.Typeable
import GHC.Exts(Constraint)
- |实例
的泛型,反射,异构容器 - 类型类。
data Object(constraint :: * - > Constraint)其中
Obj ::(可键入a,约束a)=> a - >对象约束
派生类型
- |将对象向下转换为满足相关
-约束条件的任何类型。
downcast ::一个约束。 (可键入a,约束a)=>
对象约束 - >也许
downcast(Obj(value :: b))=
case eqT :: Maybe(a:〜:b)of
Just Refl - >只要价值
Nothing - > Nothing
这里 Object 类型的参数是一个类型类型(类型 * - >约束),因此您可以使用类型如 Object Shape ,其中<$
class Shape shape其中
getArea: :形状 - > Double
- 请注意'Object'类型是如何通过'Shape'参数化的,类是
- constraint。这就是ConstraintKinds所能做到的。
实例Shape(Object Shape)其中
getArea(Obj o)= getArea o
Object 类型的功能是两个功能的组合:
- 存在类型(在这里由 GADTs 启用),这允许我们在同一个 Object 类型中存储异构类型的值。
- ConstraintKinds ,它允许我们将 Object 硬编码到某些特定的一组类约束,使用 Object 类型的用户将它们想要的约束作为参数指定给 Object 。
现在,我们不仅可以制作 Shape $ c $的异构列表
data Circle = Circle {radius :: Double}
派生类型
实例Shape Circle其中
getArea(Circle radius)= pi * radius ^ 2
data Rectangle = Rectangle le {height :: Double,width :: Double}
派生类型
实例形状矩形其中
getArea(矩形高度宽度)=高度*宽度
exampleData :: [Object Shape]
exampleData = [Obj(Circle 1.5),Obj(Rectangle 2 3)]
...但是感谢 Object 中的 Typeable 约束,我们可以 downcast :如果我们正确地猜出了 Object 中包含的类型,我们可以恢复原始类型:
- |对于列表中的每个形状,尝试将其转换为圆形。如果我们
- 成功,则将结果传递给一个单形函数
- 要求一个圆。评估:
-
- >>>示例
- [半径为1.5的圆,面积为6.0的形状]
示例:: [String]
示例= mapMaybe步骤示例数据
其中, = describeCircle< $> (向下形状)
< |> Just(describeShape shape)
describeCircle :: Circle - > String
describeCircle(Circle radius)=一个半径圆++显示半径
describeShape :: Shape a => a - >字符串
describeShape shape =具有区域的形状++ show(getArea shape)
What is a Constraint kind?
Why would someone use it (in practice)?
What is it good for?
Could you give a simple code example to illustrate the answers to the previous two questions?
Why is it used in this code for example?
Well, I'll mention two practical things it allows you to do:
- Parametrize a type by a type class constraint
- Write type classes that allow their instances to specify constraints that they need.
Maybe it's best to illustrate this with an example. One of the classic Haskell warts is that you cannot make a Functor instance for types that impose a class constraint on their type parameter; for example, the Set class in the containers library, which requires an Ord constraint on its elements. The reason is that in "vanilla" Haskell, you'd have to have the constraint on the class itself:
class OrdFunctor f where fmap :: Ord b => (a -> b) -> f a -> f b
...but then this class only works for types that require specifically an Ord constraint. Not a general solution!
So what if we could take that class definition and abstract away the Ord constraint, allowing individual instances to say what constraint they require? Well, ConstraintKinds plus TypeFamilies allow that:
{-# 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
(Note that this isn't the only obstacle to making a Functor instance to Set; see this discussion. Also, credit to this answer for the NoConstraint trick.)
This sort of solution hasn't been generally adopted just yet, though, because ConstraintKinds are still more or less a new feature.
Another use of ConstraintKinds is to parametrize a type by a class constraint or class. I'll reproduce 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
Here the parameter of the Object type is a type class (kind * -> Constraint), so you can have types like Object Shape where Shape is a class:
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
What the Object type does is a combination of two features:
- An existential type (enabled here by GADTs), which allows us to store values of heterogeneous types inside the same Object type.
- ConstraintKinds, which allows us to, instead of hardcoding Object to some specific set of class constraints, have the users of the Object type specify the constraint they want as a parameter to the Object type.
And now with that we can not only make a heterogeneous list of Shape instances:
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)]
...but thanks to the Typeable constraint in Object we can downcast: if we correctly guess the type contained inside an Object, we can recover that original type:
-- | 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)
这篇关于ConstraintKinds解释了一个非常简单的例子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!