2016-03-22 11 views
2

ich eine Funktion in Template Haskell haben, die die Typinformationen extrahiert für Summe von Rekord Konstrukteuren wie folgt:Performing Art Gleichheit in Vorlage Haskell

listFields :: Name -> Q ([[(String,Name,Type)]]) 
listFields name = do 
    TyConI (DataD _ _ _ cons _) <- reify name 
    let showClause (RecC conName fields) = (map (\(x,_,t) -> (nameBase $ x,x,t)) fields) 
    return $ map showClause cons 

die Art dort für ein Feld gegeben, wie Sie im Vergleich Gleichheit dieser Typ mit einem bestimmten Typ wie GHC.Base.String oder Data.Text.Internal.Text? Ich sehe TypeQ in TH Dokumentation. Es baut Typausdruck auf. Ich kann jedoch keine Dokumentation darüber finden, wie ein bestimmter Typ wie String oder Text oder Int erstellt wird, damit ich ihn für den Vergleich der Gleichheit verwenden kann? Ich schätze Hinweise dazu, wie man das macht, insbesondere wie man den AST für einen bestimmten Typ bekommt.

Der Grund für diese Frage ist, dass gegebenen Datensatzkonstruktor wir jedes Feld in konvertieren möchten. show und pack sollten jedoch für die Typen String und Text unterschiedlich benannt werden. So, müssen Sie verschiedene Spleiße generieren, wenn der Typ (keine Konvertierung) oder String (nur Anruf pack, nicht show anrufen) oder etwas anderes (rufen Sie pack . show unter der Annahme Show Instanz existiert).

+0

Da Sie Template Haskell zu lernen scheinen, bin ich eher neugierig, welche Ressourcen Sie verwenden. Ich habe noch keine guten finden können, um es selbst zu lernen. – dfeuer

+0

Sie können also eine Gleichheitsüberprüfung mit '==' durchführen, aber das ist mit ziemlicher Sicherheit falsch, weil es nur eine rohe syntaktische Äquivalenz ist und Typ Synonyme nicht normalisiert, ganz zu schweigen von Umleitungen durch Module oder was auch immer. Du solltest besser eine überladene Typklasse schreiben, die sich nur bei 'String' oder etwas anders verhält und nur in deinem Codegen-Modul benutzt. Typgleichheit ist wirklich schwer. – jozefg

+1

@dfeuer, zwei Hauptressourcen: [Beispiel für eine Funktion zur Ableitung] (https://ocharles.org.uk/blog/guest-posts/2014-12-22-template-haskell.html) und [eine ausführliche Übersicht über TH ] (https://artyom.me/lens-over-tea-6#template-haskell). Ständiges Experimentieren in 'ghci' mit der Hackageseite für [' TH'] (https://hackage.haskell.org/package/template-haskell-2.10.0.0/docs/Language-Haskell-TH.html) in einem anderen Browser hilft viel. Die AST-Generierung in 'ghci' mit einfachem Code und das Verständnis der Bedeutung von' Name' in 'TH' halfen mir dabei, schnell zu beschleunigen. – Sal

Antwort

3

Als eine Folge zu der anderen Antwort, hier ist etwas, mit dem Sie ToText ohne überlappende Instanzen schreiben können. Es nutzt meinen neuen Lieblingstrick - geschlossene Familien über datakinds als „Wahl“ Mechanismus mit typischen Typklassen Mischen (Anmerkung: nicht einmal funktionale Abhängigkeiten verwenden, viel weniger überlappende Instanzen) den eigentlichen Code zu synthetisieren:

{-# LANGUAGE TypeFamilies, DataKinds, MultiParamTypeClasses, FlexibleInstances, ScopedTypeVariables, FlexibleContexts #-} 

import Data.List 
import Data.Text (unpack, pack, Text) 
import Data.Proxy 

data ToTextMethod = TTMChar | TTMString | TTMText | TTMShow 

type family ToTextHow a where 
    ToTextHow Char = TTMChar 
    ToTextHow String = TTMString 
    ToTextHow Text = TTMText 
    ToTextHow a = TTMShow 

class ToTextC a b where 
     toTextC :: a -> b -> Text 

instance Show a => ToTextC a (Proxy TTMShow) where 
     toTextC a _ = pack (show a) 

instance ToTextC Char (Proxy TTMChar) where 
     toTextC c _ = pack [c] 

instance ToTextC String (Proxy TTMString) where 
     toTextC s _ = pack s 

instance ToTextC Text (Proxy TTMText) where 
     toTextC t _ = t 

toText :: forall a. (Show a, ToTextC a (Proxy (ToTextHow a))) => a -> Text 
toText x = toTextC x (Proxy :: Proxy (ToTextHow a)) 

Die Namen könnten wahrscheinlich etwas Arbeit gebrauchen, und es könnte nett sein, die Argumente auf toTextC zu spiegeln, aber das funktioniert sogar in Ghc 7.8.3.

+0

Gibt es einen Grund, warum Sie 'toTextC :: a -> proxy b -> Text' nicht verwendet haben? Das ist der übliche Ansatz und weniger ausführlich. Außerdem denke ich, dass Sie diese Argumente tatsächlich umdrehen sollten. Der Nachteil davon ist natürlich, dass es ziemlich geschlossen ist. Das ist auch ein Vorteil. – dfeuer

+0

Es ist auch besser für das interessantere Argument, dass ein MPTC als letzter kommt (wie mich jemand in letzter Zeit daran erinnert hat, ist das generalisierte Ableiten von neuen Typen besser so). Daher sollte der Instanzauswahltyp wahrscheinlich das erste Klassenargument sein. – dfeuer

+0

Wie ich am Ende bemerkte, "könnte es schön sein, die Argumente umzudrehen" - ich habe es nicht gemacht, weil ich in eine Richtung gestartet bin und nicht das Gefühl hatte, es zu ändern, und die Antwort steht immer noch. Auch 'toTextC' wird nur intern verwendet, es spielt also keine Rolle. Die einzige exportierte "Schnittstelle" ist 'toText' selbst. – sclv

1

Folgende Empfehlungen von jozefg in den Kommentaren, löste ich dieses Problem mit einer überladenen Funktion mit Typ Unterschrift a -> Text. Halten Sie dies für ein paar Tage offen, um zu sehen, ob jemand einen besseren Vorschlag hat.

Dies ist mein ursprünglicher TH splice (ghci Ausgang):

> runQ [| pack . show $ 1 ::Int|] 
SigE (InfixE (Just (InfixE (Just (VarE Data.Text.pack)) (VarE GHC.Base..) 
(Just (VarE GHC.Show.show)))) (VarE GHC.Base.$) (Just (LitE (IntegerL 1)))) 
(ConT GHC.Types.Int) 

Int zu Text umgewandelt wird. Die Ausführung von pack . show auf String oder Text wird jedoch problematisch sein, da es eine weitere Schicht von doppelten Anführungszeichen hinzufügen wird (und macht sowieso keinen Sinn). So benötigen wir eine spezielle Handhabung für für Text, String und Char Typen. So Lösung ist eine Funktion toText :: a -> Text und verwenden Sie es in der codegen zu schreiben, wie unten:

> runQ [| toText $ 1 ::Int|] 
SigE (InfixE (Just (VarE ToText.toText)) (VarE GHC.Base.$) (Just (LitE (IntegerL 1)))) (ConT GHC.Types.Int) 

Nun wird die Code-Generierung von toText behandelt sich je nach Typ. Dies ist, wie ich es in ghc 7.10.3 schrieb - es dauert den Standardcode (von dem ersten Spleiß, wie oben dargestellt), und Überlastungen es für einige Arten - jetzt haben wir den richtigen Code in TH codegen Lage bei der Kompilierung:

{-# LANGUAGE FlexibleInstances #-} 
module ToText 
where 

import Data.List 
import Data.Text (unpack, pack, Text) 

class ToText a where 
    toText :: (Show a) => a -> Text 

instance {-# OVERLAPPING #-} ToText a where 
    toText = pack . show 

instance {-# OVERLAPPING #-} ToText Char where 
    toText c = pack [c] 

instance {-# OVERLAPPING #-} ToText String where 
    toText = pack 

instance {-# OVERLAPPING #-} ToText Text where 
    toText = id 
+0

Ew .... Überlappende Instanzen sind böse. Gibt es eine Möglichkeit, mit der Sie einfach einen Standard für die 'toText' Methode verwenden können? – dfeuer

+0

@dfeuer, yep, ich stimme zu ... bis jetzt, kenne ich keine Möglichkeit, überlappende Instanzen zu vermeiden. Deshalb ist es offen in der Hoffnung, dass es eine einfachere Lösung gibt. – Sal

Verwandte Themen