2012-11-29 9 views
15

Chcę zadeklarować typeclass że ma kilka funkcji zaimplementowanych które wykorzystują ZAIMPLEMENTOWANE stałą wartość (table):Sposób zadeklarować stałą wartość w klasie typu

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 = ?" 

Pomysł jest prosty: chcę uzyskać byId (i inne podobne funkcje) dostępnego przez instancji tej typeclass określając tylko table:

instance StdQueries SomeType where 
    table = "the_constant_value_for_this_type" 

Ale kompilator skarży utrzymuje z następującym komunikatem:

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' 

Czy są jakieś rozwiązania tego rodzaju problemu? Czy można oszukać przy pomocy newtype pomocy lub czegoś w tym stylu?

Odpowiedz

17

Najprostszą rzeczą, jaką możesz zrobić, to

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" 

Jest to prosty, ale jeśli masz więcej niż jedną funkcję, która potrzebuje dostępu do wartości table, trzeba określić, że wartość więcej niż jeden raz.

Można tego uniknąć, i potrzeba sabauma za undefined i {-# LANGUAGE ScopedTypeVariables #-} tak:

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" 

Magia tutaj jest podpis typu dla defaultById, który zmusza byId zapewnić table z tej samej instancji. Gdybyśmy podali defaultById :: (StdQueries a, StdQueries b) => Table a -> Int -> QueryM (Maybe b), to nadal kompilowałby się defaultById, ale nadal otrzymywalibyśmy komunikat podobny do tego w pytaniu: kompilator nie będzie już wiedział, której definicji użyć.

Tworzenie struktury Table a zamiast data zamiast na opakowaniu newtype, można rozszerzyć, aby określić wiele pól w stałej, w razie potrzeby.

5

Kwestia polega na tym, że definicja table nie wspomina o żadnej ze zmiennych typu klasy, więc nie byłoby sposobu, aby dowiedzieć się, której wersji table użyć. Rozwiązanie (wprawdzie hackish) może być coś takiego:

{-# 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" 

które można następnie użyć poprzez

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

Nie że ja naprawdę polecam to zrobić.

+4

Nie ma w tym nic złego, wiele klas typu robi to w ten sposób. – leftaroundabout

+0

proszę sformatować kod, aby był bardziej czytelny; także, czy to naprawdę działa? wygląda na to, że teraz tworzysz 'table', ale nie używasz go w ten sposób w części' where sql = ... '. – ErikR

+0

typ tabeli to 'a -> String', a nie' String', więc jak się je łączy? – Satvik

Powiązane problemy