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

Nik*_*kov 16 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 = ?"
Run Code Online (Sandbox Code Playgroud)

这个想法很简单:我希望byId通过仅指定table:实例化这个类型类来获得(和其他类似的函数).

instance StdQueries SomeType where
  table = "the_constant_value_for_this_type"
Run Code Online (Sandbox Code Playgroud)

但编译器不断抱怨以下消息:

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'
Run Code Online (Sandbox Code Playgroud)

这种问题有什么解决方案吗?可以在newtype帮助或其他类似的情况下欺骗?

dav*_*420 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"
Run Code Online (Sandbox Code Playgroud)

这很简单,但如果您有多个需要访问该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"
Run Code Online (Sandbox Code Playgroud)

这里的魔法是类型签名defaultById,强制byId提供table从同一个实例.如果我们提供defaultById :: (StdQueries a, StdQueries b) => Table a -> Int -> QueryM (Maybe b)然后defaultById仍然会编译,但我们仍然会得到一个类似的错误信息给你的问题:编译器将不再知道使用哪个定义table.

通过使Table a一个data结构,而不是newtype包装,可以延长这在不断指定多个领域,如果需要的话.


sab*_*uma 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"
Run Code Online (Sandbox Code Playgroud)

然后你可以使用它

table (undefined :: SomeType) == "the_constant_value_for_this_type"
Run Code Online (Sandbox Code Playgroud)

不是我真的建议这样做.

  • 这没有太大的错误,很多类型都是这样做的. (4认同)
  • @sabauma:如果你真的想,你可以通过`data Proxy a = Proxy`,它给你`table :: Proxy a - > String`和`table(Proxy :: Proxy SomeType)`.更冗长,但更安全. (2认同)