I want to declare a typeclass that has some implemented functions which utilize an unimplemented constant value (table
):
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 = ?"
The idea is simple: I want to get the byId
(and other similar functions) available by instantiating this typeclass by specifying just the table
:
instance StdQueries SomeType where
table = "the_constant_value_for_this_type"
But the compiler keeps complaining with the following message:
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'
Are there any solutions to that kind of a problem? Can tricking with newtype
help or anything like that?
A class constant is declared inside a class with the const keyword. Class constants are case-sensitive. However, it is recommended to name the constants in all uppercase letters.
Variables can be declared as constants by using the “const” keyword before the datatype of the variable. The constant variables can be initialized once only. The default value of constant variables are zero.
To turn an ordinary variable into a constant, you have to use the keyword "final." As a rule, we write constants in capital letters to differentiate them from ordinary variables. If you try to change the constant in the program, javac (the Java Compiler) sends an error message.
To declare a constant variable in C++, the keyword const is written before the variable's data type. Constant variables can be declared for any data types, such as int , double , char , or string .
The simplest thing you can do is
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"
This is simple, but if you have more than one function that needs access to the table
value, you have to specify that value more than once.
You can avoid that, and sabauma's need for undefined
and {-# LANGUAGE ScopedTypeVariables #-}
like this:
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"
The magic here is the type signature for defaultById
, which forces byId
to provide the table
from the same instance. If we provided defaultById :: (StdQueries a, StdQueries b) => Table a -> Int -> QueryM (Maybe b)
then defaultById
would still compile, but we would still get a similar error message to the one in your question: the compiler would no longer know which definition of table
to use.
By making Table a
a data
structure instead of a newtype
wrapper, you can extend this to specify many fields in the constant, if required.
The issue is that the definition of table
does not mention any of the type variables of the class, so there would not be any way to figure out which version of table
to use. A (admittedly hackish) solution might be something like:
{-# 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"
Which you could then use via
table (undefined :: SomeType) == "the_constant_value_for_this_type"
Not that I would really recommend doing this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With