2013-06-22 8 views
6

Quando ho un tipo di dati come il seguente in Haskell:vincoli tipi: Passare più vincoli

data A ctx = A (forall a. ctx a => a -> a) 

Poi posso mettere funzioni che lavorano sui valori di tipi di una determinata classe in questo tipo di dati:

> A (+3) :: A Num 
A (+3) :: A Num :: A Num 

Ma è anche possibile inserire funzioni che hanno più vincoli in questo tipo di dati? Ho provato questo:

> :t ((+ 3) . succ) 
((+ 3) . succ) :: (Enum c, Num c) => c -> c 
> :t A ((+ 3) . succ) :: A (Enum,Num) 
    Expecting one more argument to `Enum' 
    In an expression type signature: A (Enum, Num) 
    In the expression: A ((+ 3) . succ) :: A (Enum, Num) 

Quindi questo non funziona. Com'è possibile fare ciò che voglio, o è impossibile? So che una soluzione sarebbe utilizzare un tipo di dati "fittizio" e una famiglia di tipi, ma non voglio farlo se c'è un altro modo a causa della sua complessità. Inoltre, io questo esempio avrei potuto usare (+1) invece di succ, ma ci sono casi più complessi in cui una tale trasformazione in una sola classe non è possibile.

+0

Che dire di un altro typeclass come 'classe (Num una, Enum a) => Foo a'? – thoferon

+0

Sì, ovviamente funzionerebbe anche, ma non mi piace molto fare una classe di tipo solo per combinare due contesti – bennofs

risposta

1

Ho trovato un modo per generalizzare l'idea di unire più classi in una sola. Anche se è piuttosto difficile da usare, funziona.

{-# LANGUAGE ConstraintKinds  #-} 
{-# LANGUAGE EmptyDataDecls  #-} 
{-# LANGUAGE FlexibleContexts  #-} 
{-# LANGUAGE FlexibleInstances  #-} 
{-# LANGUAGE GADTs     #-} 
{-# LANGUAGE KindSignatures  #-} 
{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE OverlappingInstances #-} 
{-# LANGUAGE RankNTypes   #-} 
{-# LANGUAGE ScopedTypeVariables #-} 
{-# LANGUAGE TypeOperators   #-} 
{-# LANGUAGE UndecidableInstances #-} 
module Test where 

import GHC.Exts 

data (:>+<:) ctx1 ctx2 a where 
    C'C :: (ctx1 a, ctx2 a) => (ctx1 :>+<: ctx2) a 

data (:++:) (ctx1 :: * -> Constraint) (ctx2 :: * -> Constraint) = C 

class Ctx2 c1 c2 a where 
    useCtx :: c1 :++: c2 -> a -> ((c1 :>+<: c2) a -> b) -> b 

instance (c1 a, c2 a) => Ctx2 c1 c2 a where 
    useCtx _ a f = f C'C 

fC :: (Ctx2 Num Enum a) => a -> a 
fC a = useCtx (C :: Num :++: Enum) a $ \C'C -> 3 + succ a 

data A ctx = A 
    { call :: forall a. ctx a => a -> a 
    } 

main = print $ call (A fC :: A (Ctx2 Num Enum)) 3 

Dovrebbe essere possibile utilizzare gli alias di vincolo per definire Ctx3, Ctx4, ...