一种在类型类中声明常量值的方法

时间:2012-11-29 15:52:32

标签: haskell typeclass

我想声明一个类型类,其中包含一些使用未实现的常量值(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帮助或类似的东西欺骗吗?

2 个答案:

答案 0 :(得分: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),那么defaultById仍会编译,但我们仍然会收到与您问题中的错误消息类似的错误消息:编译器将不再知道要使用table的哪个定义

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

答案 1 :(得分:5)

问题是table的定义没有提到类的任何类型变量,因此无法找出要使用的table版本。一个(诚然是hackish)解决方案可能是这样的:

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

不是我真的建议这样做。