2012-11-29 12 views
15

Voglio dichiarare una typeclass che ha alcune funzioni implementate che utilizzano un valore costante non implementato (table):Un modo per dichiarare un valore costante in una classe di tipo

class FromRow a => StdQueries a where 
    table :: String 
    byId :: Int -> QueryM (Maybe a) 
    byId = fmap listToMaybe . queryM sql . Only 
    where sql = read $ "SELECT * FROM " ++ table ++ " WHERE id = ?" 

L'idea è semplice: voglio ottenere i byId (e altre funzioni simili) disponibili istanziando questo typeclass specificando solo il table:

instance StdQueries SomeType where 
    table = "the_constant_value_for_this_type" 

Ma il compilatore continua a lamentarsi con il seguente messaggio:

The class method `table' 
mentions none of the type variables of the class StdQueries a 
When checking the class method: table :: String 
In the class declaration for `StdQueries' 

Esistono soluzioni per questo tipo di problema? Può ingannare con l'aiuto newtype o qualcosa del genere?

risposta

17

La cosa più semplice che si può fare è

class FromRow a => StdQueries a where 
    byId :: Int -> QueryM (Maybe a) 

defaultById :: FromRow a => String -> Int -> QueryM (Maybe a) 
defaultById table = fmap listToMaybe . queryM sql . Only 
    where sql = read $ "SELECT * FROM " ++ table ++ " WHERE id = ?" 

instance StdQueries SomeType where 
    byId = defaultById "the_constant_value_for_this_type" 

Questo è semplice, ma se si dispone di più di una funzione che ha bisogno di accedere al valore table, è necessario specificare che il valore più di una volta.

si può evitare questo, e il bisogno di sabauma per undefined e {-# LANGUAGE ScopedTypeVariables #-} come questo:

newtype Table a = Table String 

class FromRow a => StdQueries a where 
    table :: Table a 
    byId :: Int -> QueryM (Maybe a) 
    byId = defaultById table 

defaultById :: StdQueries a => Table a -> Int -> QueryM (Maybe a) 
defaultById (Table table) = fmap listToMaybe . queryM sql . Only 
    where sql = read $ "SELECT * FROM " ++ table ++ " WHERE id = ?" 

instance StdQueries SomeType where 
    table = Table "the_constant_value_for_this_type" 

La magia qui è la firma tipo per defaultById, che costringe byId per fornire il table dalla stessa istanza. Se avessimo fornito defaultById :: (StdQueries a, StdQueries b) => Table a -> Int -> QueryM (Maybe b), allora defaultById sarebbe ancora compilato, ma avremmo comunque ricevuto un messaggio di errore simile a quello della tua domanda: il compilatore non saprebbe più quale definizione di table usare.

Effettuando la struttura Table a a data invece di un wrapper newtype, è possibile estenderlo per specificare molti campi nella costante, se necessario.

5

Il problema è che la definizione di table non menziona nessuna delle variabili di tipo della classe, quindi non ci sarebbe alcun modo di capire quale versione di table usare. Una soluzione (certamente hacker) potrebbe essere qualcosa di simile:

{-# LANGUAGE ScopedTypeVariables #-} 
class FromRow a => StdQueries a where 
    table :: a -> String 
    byId :: Int -> QueryM (Maybe a) 
    byId = fmap listToMaybe . queryM sql . Only 
    where sql = read $ "SELECT * FROM " ++ table (undefined :: a) ++ " WHERE id = ?" 

instance StdQueries SomeType where 
    table = const "the_constant_value_for_this_type" 

cui si potrebbe quindi utilizzare via

table (undefined :: SomeType) == "the_constant_value_for_this_type" 

Non che consiglio vivamente di fare questo.

+4

Non c'è molto sbagliato in questo, molte classi di tipi lo fanno in questo modo. – leftaroundabout

+0

si prega di formattare il codice per renderlo più leggibile; inoltre, funziona davvero? sembra che tu stia rendendo 'table' una funzione adesso, ma non la stai usando in questo modo nella parte' where sql = ... ' Il tipo di tabella – ErikR

+0

è 'a -> String' e non' String', quindi come lo si concatena? – Satvik

Problemi correlati