2015-07-09 2 views

ответ

18

Ну, я упомяну две практические вещи, это позволяет вам сделать:

  1. параметризовать типа с помощью класса типа ограничения
  2. классы записи типа, которые позволяют их экземпляры, чтобы указать ограничения, которые им необходимы.

Возможно, лучше проиллюстрировать это примером. Один из классических бородавок Haskell состоит в том, что вы не можете создать экземпляр Functor для типов, которые накладывают ограничение класса на их параметр типа; например, класс Set в библиотеке containers, для которого требуется ограничение Ord. Причина заключается в том, что в «ванильный» Haskell, вы должны были бы иметь ограничение на сам класс:

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

... но тогда этот класс работает только для типов, которые требуют конкретно в 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 

(Обратите внимание, что это не единственное препятствие для принятия 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.

А теперь, что мы можем не только сделать гетерогенный список 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) 
+3

Большое спасибо за подробный ответ Луис! – jhegedus

9

Расширение 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 рода, но вам придется поближе, чтобы убедиться.