2016-05-16 6 views
1

Ich arbeite an einer Webapp mit Happstack und ich schreibe Code, um meine Typen in MongoDB zu speichern. Ich hatte das Gefühl, meinen Code etwas zu verkürzen, indem ich den Code in eine Typklasse einfügte, so dass ich denselben Code zum Lesen und Schreiben in die Datenbank für verschiedene Typen verwenden konnte. Etwas wie folgt aus:Wert in typeclass ohne Typ variable constraint

class DatabaseType a where 
    toDoc   :: a -> Document 
    fromDoc   :: Document -> a 
    saveCollection :: Text 
    getFromDatabase :: (MonadIO m) => Pipe -> Text -> Value -> m a 
    getFromDatabase pipe field value = ... 
    ... 

Nun ist das Problem hier ist die saveCollection, da es eine der Variablen vom Typ GHC nicht verwendet wird es jedoch nicht zulassen, kompilieren es um die Datenbankfunktionen sehr wichtig ist (wie getFromDatabase) damit sie wissen, in welcher Sammlung sie speichern möchten.

Frage ist, wie man einen Wert in einer Typklasse hat, die nicht durch die Typvariablen verbunden ist.

Antwort

5

Sie müssen die Typvariable hinzufügen. Der einfachste Weg ist es, einen Proxy zu verwenden:

saveCollection :: proxy a -> Text 
    -- Note the `proxy` is lower case 

instance DatabaseType MyDB where 
    saveCollection _ = "MyDB" 

Nun, es zu benutzen, Sie wahrscheinlich dies tun würden:

import Data.Proxy 

foo = saveCollection (Proxy :: Proxy MyDB) 

Der Grund für die Kleinschreibung in der Methodendeklaration verwendet, ist Bequemlichkeit: Sie können Verwenden Sie einen beliebigen Wert, dessen Typ die richtige Form hat, anstelle von Proxy MyDB, wenn Sie zufällig einen auf der Call-Site haben.


Es gibt einige Situationen, in denen die Standard-Proxy-Technik zu einem problematischen Verlust der Freigabe führen kann. Dies geschieht, weil die Ergebnisse von Funktionsaufrufen normalerweise nicht protokolliert werden. In diesem Fall können Sie einen getaggten Typ verwenden. Data.Tagged definiert

newtype Tagged s b = Tagged {unTagged :: b} 

Stichwort Arten viel umständlicher sind mit als Proxies zu arbeiten, es sei denn, Sie Teiltyp Signaturen oder explizite Art Anwendung zu verwenden, zwei von GHC der zuletzt hinzugefügten Funktionen. Wenn Sie wollen, aber könnten Sie

saveCollection :: Tagged a Text 

Dann in der Instanz schreiben,

saveCollection = Tagged "Hi there." 

es direkt verwenden wären so etwas wie

unTagged (saveCollection :: Tagged MyDB Text) 

oder mit partieller Art Signaturen erfordern,

unTagged (saveCollection :: Tagged MyDB _) 

oder mit expliziter Typ Anwendung Ich denke so etwas wie

unTagged ([email protected]) 

Dank dieser Ungeschicklichkeit, das tagged Paket bietet Funktionen für die Umwandlung zwischen Proxy-basierten und getaggt Darstellungen.

+1

Dies ist in der Tat die idiomatische Lösung. (+1) Dennoch habe ich die Verwendung von "Proxy A" nie vollständig gekauft, auch wenn dies sehr populär geworden ist. Ich würde erwägen, z.B. '[a]' anstelle von 'Proxy a' zu verschleiern: Wenn der beabsichtigte Wert irrelevant ist, und nur der Typ zählt, würde ich lieber einen expliziten' Proxy a' übergeben. Hoffentlich werden GHC 8 und Explicit Type Args Proxies wegwischen. – chi

+0

@chi, stimme ich normalerweise zu.Es kann jedoch sehr praktisch sein, einen Singleton als Proxy zu verwenden. Wenn ich 'assoziieren :: Natty x -> Natty y -> Proxy z -> (x: + y): + z: ~: x: + (y: + z)' und ich habe Singletons, die drei natürliche Zahlen darstellen, Ich kann sie einfach weitergeben, ohne vorher die dritte in einen Proxy umzuwandeln. – dfeuer