Подтвердить что ты не робот

ConstraintKinds объяснил на простом примере

Что такое Тип ограничения?

Зачем кому-то его использовать (на практике)?

Для чего это полезно?

Не могли бы вы привести простой пример кода, чтобы проиллюстрировать ответы на предыдущие два вопроса?

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

4b9b3361

Ответ 1

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

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

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

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

... но тогда этот класс работает только для типов, которым требуется, в частности, ограничение Ord. Не общее решение!

Итак, что, если мы могли бы взять это определение класса и абстрагироваться от ограничения Ord, позволяя отдельным экземплярам сказать, какое ограничение требуется им? Ну, ConstraintKinds plus 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, см. это обсуждение., кредит на этот ответ за трюк NoConstraint.)

Такое решение, как правило, еще не принято, поскольку ConstraintKinds все еще более или менее новая функция.


Другим использованием ConstraintKinds является параметризация типа с помощью ограничения класса или класса. Я воспроизведу этот код Haskell "Пример формы" , который я написал:

{-# 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 the sort of thing ConstraintKinds enables.
instance Shape (Object Shape) where
    getArea (Obj o) = getArea o

Что делает тип Object - это комбинация двух функций:

  • Экзистенциальный тип (включен здесь GADTs), который позволяет хранить значения гетерогенных типов внутри одного и того же типа Object.
  • 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 мы можем downcast: если мы правильно угадали тип, содержащийся внутри 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)

Ответ 2

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