Cóż, ja wspomnieć o dwóch praktycznych rzeczy to pozwala zrobić:
- parametrize rodzajem poprzez ograniczenie klasy typu
- zajęcia typu Write, które umożliwiają ich wystąpień do określenia ograniczeń, których potrzebują.
Może najlepiej to zilustrować na przykładzie. Jedną z klasycznych brodawek Haskella jest to, że nie można utworzyć instancji Functor
dla typów, które nakładają ograniczenia klasy na ich typ parametrów; na przykład klasa Set
w bibliotece containers
, która wymaga ograniczenia na jego elementach w postaci Ord
. Powodem jest to, że w „waniliowy” Haskell, trzeba mieć ograniczenia w klasie sama:
class OrdFunctor f where
fmap :: Ord b => (a -> b) -> f a -> f b
... ale potem klasa ta działa tylko dla typów, które wymagają specjalnie się Ord
ograniczenie. Nie ogólne rozwiązanie!
Co możemy zrobić, jeśli możemy zdefiniować tę klasę i usunąć z niej ograniczenie Ord
, pozwalając poszczególnym instancjom stwierdzić, jakich ograniczeń potrzebują? Cóż, ConstraintKinds
Plus TypeFamilies
pozwolić:
{-# 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
(Zauważ, że nie jest jedyną przeszkodą na drodze do tworzenia instancji Functor
do Set
patrz this discussion Również credit to this answer for the NoConstraint
trick..)
Tego rodzaju rozwiązanie nie zostało jeszcze na razie przyjęte, ponieważ ConstraintKinds
jest wciąż mniej lub bardziej nową funkcją.
Innym zastosowaniem ConstraintKinds
jest parametrize typ poprzez ograniczenie klasy lub klasy. będę powielać 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
Tutaj parametr typu Object
jest klasa typ (rodzaj * -> Constraint
), dzięki czemu można mieć typy jak Object Shape
gdzie Shape
jest klasa:
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
Co Typ Object
to połączenie dwóch funkcji:
- Typ egzystencjalny (udostępniony tutaj przez
GADTs
), który pozwala nam przechowywać wartości heterogenicznych typów wewnątrz tego samego typu Object
.
ConstraintKinds
, który pozwala nam, zamiast kodowania twardego Object
, na określony zestaw ograniczeń klasy, mieć użytkowników typu Object
określić ograniczenia, które chcą jako parametr do typu Object
.
A teraz z tym możemy nie tylko sprawiają, niejednorodną listę Shape
przypadkach:
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)]
... ale dzięki Typeable
przymusu w Object
możemy przygnębiony: jeśli poprawnie odgadnąć typ zawarty w Object
, możemy odzyskać ten oryginalny typ:
-- | 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)
Wielkie dzięki za szczegółową odpowiedź Luis! – jhegedus