2016-07-22 14 views
1

Wenn ich eine TypeRep für einen Typ oder eine habe, die eine Instanz dieses Typs enthält, gibt es eine Möglichkeit, die entsprechende Instanz einer bekannten Klasse zu finden, damit ich eine Funktion dieser Klasse aufrufen kann, ohne das Ganze zu kennen Frage eingeben? Wenn es ein solches Feature nicht gibt, gibt es einen Grund, warum es unmöglich wäre, oder wurde es einfach nicht implementiert?Haskell Dynamic/TypeRep: Extrahiere Instanz einer Typklasse, ohne den vollständigen Typ zu kennen?

Alternativ gibt es einen Weg (vielleicht mit Vorlage Haskell) durch eine generierte Liste aller Typen mit Instanzen einer Typklasse zu durchlaufen, so dass ich eine dynamische Besetzung für jeden durchführen und die Ergebnisse überprüfen kann?

Was ich versuche ist Eq und eine Version von Show zu tun zu implementieren, die tatsächlich die Daten anzeigt, für Dynamic (wenn möglich) für die Zwecke der einfacheren und allgemein nützliche Unit-Tests zu schreiben; Ich brauche keine hohe Leistung dafür, also wäre die Verwendung von generiertem Code, der alle Möglichkeiten durchläuft, akzeptabel.

+0

Ich bin neugierig auf Ihren Anwendungsfall hier. Warum arbeitest du überhaupt mit "Dynamic"? –

+0

@BenjaminHodgson Ich arbeite an einem Interpreter für eine objektorientierte Sprache und möchte die Einbettung beliebiger Haskell-Werte in einen eigenen Wertetyp erlauben, damit Objektmethoden, die als Haskell-Code implementiert sind, alle benötigten Werte in den Objekten des Zielsprache. – Jules

Antwort

3

Es ist nicht möglich zu prüfen, ob ein Typ zur Laufzeit eine Instanz von etwas ist. Einer der Gründe ist, dass das Importieren eines Moduls neue Instanzen einbringen könnte und das Ergebnis der Funktion ändern würde (dies wäre sehr schlecht für Haskell).

Sie können durch eine Liste bekannter Typen gehen. Die Idee ist, dass Sie den Typ zusammen mit den Instanzen für diesen Typ in einer GADT speichern und darauf abgleichen, um die gewünschten Instanzen zu erhalten. Ich bin mir nicht ganz sicher, was Sie wollen, aber ich denke, es ist so etwas wie das ist:

data EqShow where 
    JustEq :: (Typeable a, Eq a)   => Proxy a -> EqShow 
    EqShow :: (Typeable a, Eq a, Show a) => Proxy a -> EqShow 

Es gibt eine Version für die Typen, die nur Eq und eine haben, dass beide Eq und Show haben. Die Idee ist, dass wir auf diese passen können, wenn die Typen übereinstimmen und die Instanz Eq verwenden, um zu überprüfen, ob sie gleich sind. Wenn eine Show Instanz verfügbar ist, können wir auch die Ergebnisse anzeigen. Um mehrere EqShows zu speichern, habe ich eine Hash-Map verwendet, um die Typen zu suchen. Hier ist der vollständige Code:

{-# LANGUAGE GADTs #-} 
import   Data.Dynamic 
import   Data.Typeable 
import   Data.HashMap.Lazy (HashMap) 
import qualified Data.HashMap.Lazy as HM 

data EqShow where 
    JustEq :: (Typeable a, Eq a)   => Proxy a -> EqShow 
    EqShow :: (Typeable a, Eq a, Show a) => Proxy a -> EqShow 

justEq :: (Typeable a, Eq a) => Proxy a -> (TypeRep, EqShow) 
justEq p = (typeRep p, JustEq p) 

eqShow :: (Typeable a, Eq a, Show a) => Proxy a -> (TypeRep, EqShow) 
eqShow p = (typeRep p, EqShow p) 

-- | Different outcomes of testing equality. 
data Result 
    = DifferentType TypeRep TypeRep 
    | NotEq TypeRep (Maybe (String, String)) 
    | IsEq TypeRep (Maybe String) 
    | UnknownType TypeRep 
    deriving Show 

-- | Check if two Dynamics are equal. Show them if possible 
dynEq :: HashMap TypeRep EqShow -> Dynamic -> Dynamic -> Result 
dynEq hm da db 
    | ta /= tb = DifferentType ta tb 
    | otherwise = 
     case HM.lookup ta hm of 
     Just (EqShow p) -> checkEqShow p (fromDynamic da) (fromDynamic db) 
     Just (JustEq p) -> checkJustEq p (fromDynamic da) (fromDynamic db) 
     Nothing   -> UnknownType ta 
    where 
    ta = dynTypeRep da 
    tb = dynTypeRep db 

    -- Check if two results are equal and display them. 
    checkEqShow :: (Typeable a, Eq a, Show a) => Proxy a -> Maybe a -> Maybe a -> Result 
    checkEqShow _ (Just a) (Just b) 
     | a == b = IsEq ta (Just (show a)) 
     | otherwise = NotEq ta (Just (show a, show b)) 
    checkEqShow _ _ _ = UnknownType ta 

    -- Check if two results are equal without displaying them. 
    checkJustEq :: (Typeable a, Eq a) => Proxy a -> Maybe a -> Maybe a -> Result 
    checkJustEq p (Just a) (Just b) 
     | a == b = IsEq ta Nothing 
     | otherwise = NotEq ta Nothing 
    checkJustEq p _ _ = UnknownType ta 

Sie können sie eine Liste der bekannten Typen machen:

knownEqShows :: HashMap TypeRep EqShow 
knownEqShows = HM.fromList 
    [ eqShow (Proxy :: Proxy Int) 
    , eqShow (Proxy :: Proxy Char) 
    ] 

und überprüfen Sie sie:

> let a = toDyn 'a' 
> let b = toDyn 'b' 
> let c = toDyn (1 :: Int) 
> dynEq knownEqShows a a 
IsEq Char (Just "'a'") 
> dynEq knownEqShows a b 
NotEq Char (Just ("'a'","'b'")) 
> dynEq knownEqShows a c 
DifferentType Char Int 

Gene bekannt EqShow s Template Haskell verwenden wäre schwierig, . Sie könnten vielleicht eine Version für Typen ohne Variablen erstellen (Double, Char usw.). Wenn Sie jedoch eine Variable haben (Maybe a zum Beispiel), könnten Sie dies nicht in EqShow speichern, Sie müssten alle Versionen schreiben davon (Maybe Int, Maybe (Maybe Double) usw.), aber es gibt eine unendliche Anzahl von diesen.


Natürlich wäre es viel einfacher, anstatt Dynamic der Verwendung eines anderen Wrapper zu verwenden (möglich):

data EqShowDynamic where 
    JustEqD :: (Typeable a, Eq a)   => a -> EqShowDynamic 
    EqShowD :: (Typeable a, Eq a, Show a) => a -> EqShowDynamic 

So sind die Eq und Show Instanzen bereits vorhanden sind.

+0

Wenn ich mir die Fälle ansehe, die ich verwenden muss, denke ich, dass der einfachere Ansatz, einen Wrapper zu verwenden, wahrscheinlich der beste Weg ist, aber danke für all die Ideen. :) – Jules

+0

Ich habe mich am Ende tatsächlich entschieden, einen etwas anderen Weg zu gehen. Das Ergebnis ist jetzt [up on hackage] (http://hackage.haskell.org/package/constrained-dynamic). – Jules