2015-07-09 25 views

Odpowiedz

18

Cóż, ja wspomnieć o dwóch praktycznych rzeczy to pozwala zrobić:

  1. parametrize rodzajem poprzez ograniczenie klasy typu
  2. 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:

  1. Typ egzystencjalny (udostępniony tutaj przez GADTs), który pozwala nam przechowywać wartości heterogenicznych typów wewnątrz tego samego typu Object.
  2. 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) 
+3

Wielkie dzięki za szczegółową odpowiedź Luis! – jhegedus

9

Rozszerzenie ConstraintKind umożliwia korzystanie z rodzaju Constraint. Każde wyrażenie pojawiające się w kontekście (zazwyczaj rzeczy między :: i =>), ma rodzaj Constraint. Na przykład, w ghci:

Prelude> :kind Num 
Num :: * -> Constraint 

Generalnie nie jest możliwe, aby ręcznie korzystać z tego rodzaju, ale rozszerzenie ConstraintKinds na to pozwala. Na przykład, można teraz napisać:

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

Teraz, że masz coś, że trwa typ (rodzaj *) i daje Constraint, można napisać kod jak poniżej.

Prelude> :{ 
Prelude| let myAwesomeFunction :: HasRequiredProperties a => a -> IO() 
Prelude|  myAwesomeFunction x = undefined 
Prelude| :} 

Jest możliwe, że biblioteka jesteś związany z zastosowaniami MonadWidget jako synonim typu z Constraint rodzaju, ale trzeba będzie przyjrzeć się bliżej, aby się upewnić.