A way to declare a constant value in a type class

haskell, typeclass

Solution

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.

Problem

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?

Original source