16

我想声明一个类型类,它具有一些实现的函数,这些函数利用未实现的常量值(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 = ?"

这个想法很简单:我想byId通过仅指定以下来实例化这个类型类来获得(和其他类似的功能)table

instance StdQueries SomeType where
  table = "the_constant_value_for_this_type"

但是编译器一直抱怨以下消息:

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'

有没有解决这类问题的方法?newtype可以借助帮助或类似的东西来欺骗吗?

4

2 回答 2

17

你能做的最简单的事情是

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"

这很简单,但如果您有多个函数需要访问该table值,则必须多次指定该值。

您可以避免这种情况,并且 sabauma 需要undefined这样{-# LANGUAGE ScopedTypeVariables #-}

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"

这里的神奇之处在于 的类型签名defaultById,它强制byId提供table来自同一实例的 。如果我们提供defaultById :: (StdQueries a, StdQueries b) => Table a -> Int -> QueryM (Maybe b)thendefaultById仍然会编译,但我们仍然会收到与您问题中的错误消息类似的错误消息:编译器将不再知道table要使用哪个定义。

如果需要,您可以通过创建Table a结构data而不是newtype包装器来扩展它以指定常量中的许多字段。

于 2012-11-29T16:35:28.933 回答
5

问题是 的定义table没有提到类的任何类型变量,所以没有办法确定table使用哪个版本。一个(诚然骇人听闻的)解决方案可能类似于:

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

然后您可以通过

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

并不是说我真的建议这样做。

于 2012-11-29T16:03:39.493 回答