我正在尝试编写一些EDSL来为键分配值。所以我有以下数据类型的值:
data Value = B Bool | I Int
我希望有统一的方法将不同的值转换为Value
类型的对象。所以我创建了以下类型类:
class ToValue a where toValue :: a -> Value
instance ToValue Bool where toValue = B
instance ToValue Int where toValue = I
不幸的是,这段代码无法编译:
foo :: [Value]
foo = [toValue True, toValue 3]
我明白原因。但这让我感到难过。我真的不明白如何解决这个问题...如果我启用-XOverloadedStrings
并且我想将T Text
构造函数添加到我的Value
类型,事情会变得更加困难。
我的最终目标是有能力写下这样的东西:
foo :: [(Text, Value)]
foo = [ "key1" !!! True
, "key2" !!! 42
, "key3" !!! "foo"
, "key4" !!! [5, 7, 10]
]
据我所知,我总是可以手动将每个值包装到相应的构造函数中,但我更愿意避免这种情况(因为在我的实际生活中构造函数长于一个字母,并且代码并没有真正降低构造函数的噪声)。 / p>
我可以做些什么来实现最接近的可能实现?如果可能的话,我想避免Num
的不安全Value
实例。
答案 0 :(得分:2)
使用ExtendedDefaultRules
。 (它在GHCi中默认启用,并且pragma在GHC中启用它。)
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE OverloadedStrings #-}
default (Int, String)
class Value a where
toValue :: a -> String
instance Value Int where
toValue = show
instance Value String where
toValue = id
main = do
print (toValue 3) -- would otherwise be ambiguous
print (toValue "x")
如果我理解正确,这里的目标是保持语法统一,同时适当地专门化文字。一种方法是使用Template Haskell,因此foo
可能看起来像
foo = [$(toValue [|True|]), $(toValue [|3|])]
或
foo = [ [toValue| True |], [toValue| 3 |] ]
后者的美元噪音较少,但实现自定义引用需要表达式解析器而 template-haskell 不提供。