Haskell 没有类型变量约束的typeclass中的值
我正在使用Happstack开发一个Web应用程序,我正在编写一些代码来将我的类型存储在MongoDB中。我想通过将代码放入一个typeclass来缩短代码,这样我就可以使用相同的代码来读写不同类型的数据库。大概是这样的:Haskell 没有类型变量约束的typeclass中的值,haskell,types,typeclass,Haskell,Types,Typeclass,我正在使用Happstack开发一个Web应用程序,我正在编写一些代码来将我的类型存储在MongoDB中。我想通过将代码放入一个typeclass来缩短代码,这样我就可以使用相同的代码来读写不同类型的数据库。大概是这样的: class DatabaseType a where toDoc :: a -> Document fromDoc :: Document -> a saveCollection :: Text
class DatabaseType a where
toDoc :: a -> Document
fromDoc :: Document -> a
saveCollection :: Text
getFromDatabase :: (MonadIO m) => Pipe -> Text -> Value -> m a
getFromDatabase pipe field value = ...
...
现在这里的问题是saveCollection
,因为它不使用任何类型变量,GHC不会让它编译,但是它对数据库函数(如getFromDatabase
)非常重要,以便它们知道保存到哪个集合
问题是,如何在类型类中有一个不被类型变量绑定的值。您必须添加类型变量。最简单的方法是使用代理:
saveCollection :: proxy a -> Text
-- Note the `proxy` is lower case
instance DatabaseType MyDB where
saveCollection _ = "MyDB"
现在要使用它,您可能需要执行以下操作:
import Data.Proxy
foo = saveCollection (Proxy :: Proxy MyDB)
在方法声明中使用小写的原因是方便:如果调用站点上碰巧有一个值,则可以使用类型正确的任何值,而不是Proxy MyDB
在某些情况下,标准代理技术可能导致有问题的共享丢失。这是因为函数调用的结果通常不会被记忆。在这种情况下,可以使用标记类型<代码>数据。标记的定义
newtype Tagged s b = Tagged {unTagged :: b}
标记类型比代理更难使用,除非您使用部分类型签名或显式类型应用程序,这是GHC最近添加的两个特性。如果你愿意,你可以写
saveCollection :: Tagged a Text
那么在这个例子中,
saveCollection = Tagged "Hi there."
直接使用它将需要以下内容
unTagged (saveCollection :: Tagged MyDB Text)
unTagged (saveCollection@MyDB)
或者,对于部分类型签名
unTagged (saveCollection :: Tagged MyDB _)
或者使用显式类型应用程序
unTagged (saveCollection :: Tagged MyDB Text)
unTagged (saveCollection@MyDB)
由于这种尴尬,标记的
包提供了在基于代理的表示和标记的表示之间转换的功能。这确实是惯用的解决方案。(+1)尽管如此,我从未完全相信代理a的使用,即使这已经变得非常流行。我会考虑通过例如<代码> [a]代码>而不是<代码>代理A//>来混淆:如果预期值是无关的,并且只有类型是重要的,我宁愿通过一个明确的代码>代理A/<代码>。希望GHC 8和显式类型args能够清除代理。@chi,我通常同意。但是,可以非常方便地使用单例作为代理。如果我有assoc::Natty x->Natty y->proxy z->(x:+y):+z:~:x:+(y:+z)
并且我有表示三个自然数的单例,我可以将它们传入,而不必首先将第三个转换为代理。