2017-12-02 3 views
0

ich habe:fromConstrB oder etwas andere nützliche

class C a where 
    anyC :: a 
data D = D String|AnyD deriving (Eq, Show, Data) 
instance C D where 
    anyC = AnyD 

und ich möchte Funktion schreiben :: C a => a die D someString schaffen. Ich rufe anyC an und bekomme a das ist AnyD. Als nächstes erstellen Sie D someString von AnyD. In den meisten modernen Sprachen ist es einfach. Für Haskell habe ich this tutorial gefunden und siehe dort fromConstrB. Also, Ziel scheint machbar, aber .. Ich traf das Problem, dass Artikel falsch aussieht: Ich kann nichts in der Art von Chris konstruieren, weil Konstruktorargument algebraischen Typ sein muss und Code wie fromConstr (toConstr (1 :: Int)) Fehler Exception: Data.Data.constrIndex is not supported for Prelude.Int, as it is not an algebraic data type. zurückgibt. Und das gleiche gilt für String, also kann ich meine someString nicht an meinen Konstruktor übergeben! Wie umgehen Sie es? mit

Antwort

6

A Definition Typ

foo :: C a => a 

verspricht einen Wert von jede Typ a, so lange zu liefern, wie es in Klasse ist C. Der Benutzer von foo erhält a, foo selbst wählen kann nicht einen bestimmten Typ auswählen.

Mit Ihrer Einstellung, die nur möglich ist (nicht-Fehler) Definition ist

foo = anyC 

Wenn Sie D "hello" zurückkehren wollen, dann ist dies vom Typ D. Sie können diese Art verwenden, wenn Sie wollen:

bar :: D 
bar = D "hello" 

dass Beachten Sie auch, wenn Sie möchten, können Sie Ihre Instanz ändern auch:

instance C D where 
    anyC = D "hello" 

In Bezug auf

In Modernste Sprachen ist es einfach.

Dies ist eigentlich nicht der Fall. In Java könnte zum Beispiel ein vages Äquivalent sein:

Java wird das auch nicht akzeptieren. Andere Probleme beiseite, foo() ist vielversprechend, alle vom Anrufer A zurückgegeben, kann nicht wählen, um eine D zurückgeben.

Haskell-Typ-Variablen entsprechen sehr grob den generischen Java-Typparametern. Sie könnten stattdessen über das Subtyping nachdenken, das in Java und anderen OOP-Sprachen vorhanden ist, aber nicht von Haskell verwendet wird.


Wenn Sie ein fromConstrB Beispiel möchten, können Sie dieses versuchen:

{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables, TypeOperators, GADTs #-} 

module FromConstr where 

import Data.Data 

data D = D String | AnyD deriving (Show, Data) 

foo :: D 
foo = fromConstrB field ctor 
    where 
    ctor :: Constr 
    ctor = toConstr (D "aaa") 
    field :: forall a. Data a => a 
    field = case eqT :: Maybe (a :~: String) of 
     Just Refl -> "new string" 
     Nothing -> error "trying to fill a non-string field" 

foo ausgewertet D "new string".Dies kann erweitert werden, so dass field mehr Typen umfasst, wenn der Konstruktor mehr benötigt, indem Sie case eqT s verschachteln, so dass wir nach allen benötigten Typen suchen.

Beachten Sie auch, dass fromConstrB ist begrenzt, da wenn unser Konstruktor zwei Felder mit dem gleichen Typ hat, können wir die Felder nicht mit unterschiedlichen Werten füllen. Dafür müssten wir auf die komplexeren fromConstrM zurückgreifen, denke ich.


Hier ist ein wenig Komfort Hilfsfunktion. Es dauert eine Constr, und eine Liste von "untypisierten" Argumente (Dynamic macht alle Typprüfung zur Laufzeit) und versucht, einen Wert zu erstellen, der den Konstruktor auf die gegebenen Argumente anwendet.

applyConstr :: Data a => Constr -> [Dynamic] -> Maybe a 
applyConstr ctor args = let 
    nextField :: forall d. Data d => StateT [Dynamic] Maybe d 
    nextField = do 
     as <- get 
     case as of 
     [] -> lift Nothing -- too few arguments 
     (a:rest) -> do 
      put rest 
      case fromDynamic a of 
       Nothing -> lift Nothing -- runtime type mismatch 
       Just x -> return x 
    in case runStateT (fromConstrM nextField ctor) args of 
     Just (x, []) -> Just x 
     _   -> Nothing -- runtime type error or too few/too many arguments 

Zum Beispiel können Sie es wie folgt:

bar :: D 
bar = case applyConstr (toConstr (D "aaa")) [toDyn "hello"] of 
    Just x -> x 
    Nothing -> error "runtime type mismatch" 

Wenn der Konstruktor mehr Argumente hat, können Sie nur die Liste länger machen müssen, wie in [toDyn "string", toDyn (42::Int), toDyn True]. Die Funktion toDyn konvertiert typisierte Werte in "nicht typisierte", so dass sie zusammen in derselben Liste gespeichert und an applyConstr übergeben werden können. Später wird applyConstr (zur Laufzeit) testen, ob diese Liste die genaue Länge hat und Werte der richtigen Typen hat.

+0

Meine Frage ist, wie man 'fromConstrB' mit einfachen (eingebauten) Typen, nicht über Typklassen und Instanzen. So kann ich in den meisten modernen Sprachen: a) alle Konstruktoren bekommen b) Konstruktorargumente erhalten c) typen/Standardwerte für sie erhalten d) sogar Aufrufkonstruktor. Ich sehe ein Beispiel, wie man es in diesen Tutorials macht, aber anscheinend liegt das Tutorial darin. Also, Frage ist: Wie konstruiere ich den Wert von Data-like type, wenn ich den benötigten Konstruktor schon gefunden habe? Beispiel zeigt nur das Ziel. Sicher, es hat ein Problem: Ich muss überprüfen, dass der gefundene Konstruktor nur 1 arg hat und es ist String –

+0

@ Paul-AG Siehe meine Bearbeitung, wie man 'fromConstrB' und einige verwandte Funktionen verwendet. – chi

+1

'nextField = StateT uncons >> = heben. fromDynamic' – Gurkenglas