在Haskell中实现ad-hoc多态性的最佳方法?
我有一个多态函数,如:在Haskell中实现ad-hoc多态性的最佳方法?,haskell,polymorphism,abstract-data-type,Haskell,Polymorphism,Abstract Data Type,我有一个多态函数,如: convert :: (Show a) => a -> String convert = " [label=" ++ (show a) ++ "]" 但有时我想给它传递一个Data.Map并进行一些更奇特的键值转换。我知道我不能在这里进行模式匹配,因为Data.Map是一种抽象数据类型(根据),但我在这方面使用卫士是不成功的,我不确定ViewPatterns在这里是否有帮助(为了便于移植,我宁愿避免使用它们) 这更是我想要的: import qualifie
convert :: (Show a) => a -> String
convert = " [label=" ++ (show a) ++ "]"
但有时我想给它传递一个Data.Map并进行一些更奇特的键值转换。我知道我不能在这里进行模式匹配,因为Data.Map是一种抽象数据类型(根据),但我在这方面使用卫士是不成功的,我不确定ViewPatterns在这里是否有帮助(为了便于移植,我宁愿避免使用它们)
这更是我想要的:
import qualified Data.Map as M
convert :: (Show a) => a -> String
convert a
| M.size \=0 = processMap2FancyKVString a -- Heres a Data.Map
| otherwise = " [label=" ++ (show a) ++ "]" -- Probably a string
但这不起作用,因为M.size只能接受Data.Map
具体来说,我试图修改,以便在GraphViz输出中处理边的着色和其他属性
更新
我希望我能接受TomMD、Antal S-Z和luqui对这个问题的所有三个答案,因为他们都理解我真正的问题。我想说:
- Antal S-Z提供了适用于FGL的最“优雅”的解决方案,但也需要进行最多的重写和重新思考才能在个人问题中实施
- TomMD给出了一个很好的答案,在适用性和正确性方面介于Antal S-Z和luqui之间。这也是直接的,我非常感激,我为什么选择他的答案
- luqui给出了最好的“让它快速工作”答案,我可能会在实践中使用(因为我是一名研究生,这只是一些测试一些想法的一次性代码)。我之所以不接受,是因为TomMD的回答可能会在更一般的情况下更好地帮助其他人
尽管如此,它们都是很好的答案,上面的分类是一个粗略的简化。我还更新了问题标题,以更好地表达我的问题(再次感谢大家拓宽了我的视野!您刚才解释的是,您需要一个根据输入类型表现不同的函数。虽然您可以使用
数据
包装器,从而始终关闭该函数:
data Convertable k a = ConvMap (Map k a) | ConvOther a
convert (ConvMap m) = ...
convert (ConvOther o) = ...
更好的方法是使用类型类,从而使convert
函数保持打开和可扩展,同时防止用户输入非感官组合(例如:convather M.empty
)
通过这种方式,您可以为每个不同的数据类型使用您想要的实例,并且每次需要新的专门化时,您都可以通过添加另一个实例可转换的新数据类型来扩展转换的定义,其中…
有些人可能会对newtype
包装器皱眉,并建议使用如下示例:
instance Convertable a where
convert ...
但是,这将需要非常不鼓励的重叠和不可判定的实例扩展,以给程序员带来很少的便利。您的问题实际上与该问题中的问题不同。在您链接的问题中,Derek Thurn有一个函数,他知道该函数采用了设置了,但无法进行模式匹配。在您的情况下,您'正在编写一个函数,该函数将采用任何具有Show
实例的a
;您无法在运行时判断所查看的类型,只能依赖于任何Show
可使用类型的函数。如果您想让一个函数对不同的数据类型执行不同的操作,这称为即席多态性,并且在Haskell中受类型类的支持,如Show
(这与参数多态性相反,参数多态性是指当您编写一个像head(x:)=x
这样的函数时,该函数的类型为head::[a]->a
;而无约束的通用a
则是该函数的参数化原因。)因此,要想做您想做的事情,您必须创建自己的类型类,并在需要时实例化它。然而,这比通常情况要复杂一些,因为您希望将中的所有内容隐式地显示为新类型类的一部分。这需要一些潜在危险且可能不必要的强大GHC扩展。相反,为什么不简化一些事情呢?您可能可以找出实际需要以这种方式打印的类型子集。一旦这样做,您就可以按如下方式编写代码:
{-# LANGUAGE TypeSynonymInstances #-}
module GraphvizTypeclass where
import qualified Data.Map as M
import Data.Map (Map)
import Data.List (intercalate) -- For output formatting
surround :: String -> String -> String -> String
surround before after = (before ++) . (++ after)
squareBrackets :: String -> String
squareBrackets = surround "[" "]"
quoted :: String -> String
quoted = let replace '"' = "\\\""
replace c = [c]
in surround "\"" "\"" . concatMap replace
class GraphvizLabel a where
toGVItem :: a -> String
toGVLabel :: a -> String
toGVLabel = squareBrackets . ("label=" ++) . toGVItem
-- We only need to print Strings, Ints, Chars, and Maps.
instance GraphvizLabel String where
toGVItem = quoted
instance GraphvizLabel Int where
toGVItem = quoted . show
instance GraphvizLabel Char where
toGVItem = toGVItem . (: []) -- Custom behavior: no single quotes.
instance (GraphvizLabel k, GraphvizLabel v) => GraphvizLabel (Map k v) where
toGVItem = let kvfn k v = ((toGVItem k ++ "=" ++ toGVItem v) :)
in intercalate "," . M.foldWithKey kvfn []
toGVLabel = squareBrackets . toGVItem
在这个设置中,我们可以输出到Graphviz的所有内容都是GraphvizLabel
的一个实例;toGVItem
函数引用了一些内容,toGVLabel
将整个内容放在方括号中以供立即使用。(我可能弄错了您想要的一些格式,但这部分只是一个示例。)然后声明什么是GraphvizLabel
的实例,以及如何将其转换为项。TypeSynonymInstances
标志只允许我们编写实例GraphvizLabel字符串,而不是实例GraphvizLabel[Char]
;它是无害的
现在,如果你真的需要一个Show
实例也成为GraphvizLabel
的一个实例,有一种方法。如果你真的不需要这个,那么就不要使用这个代码!如果你真的需要这样做,你必须带上这个可怕的名字不可判定的实例
和重叠实例
la语言扩展(以及名称不太吓人的FlexibleInstances
)。原因是您必须断言Show
能够显示的所有内容都是GraphvizLabel
——但这对编译器来说很难分辨。例如,如果您使用此代码并编写toglabel[1,2,3]
在GHCi提示下,您将得到一个错误,因为1
具有类型Num a=>a
,并且Char
可能是Num
的一个实例!您必须显式指定toGVLabel([1,2,3]:[Int])
让它发挥作用。同样,这可能是不必要的繁重机器,无法解决您的问题。相反,如果您可以限制您认为将转换为标签的内容(很可能),您可以指定这些内容!但是如果您真的想显示abil
{-# LANGUAGE TypeSynonymInstances #-}
module GraphvizTypeclass where
import qualified Data.Map as M
import Data.Map (Map)
import Data.List (intercalate) -- For output formatting
surround :: String -> String -> String -> String
surround before after = (before ++) . (++ after)
squareBrackets :: String -> String
squareBrackets = surround "[" "]"
quoted :: String -> String
quoted = let replace '"' = "\\\""
replace c = [c]
in surround "\"" "\"" . concatMap replace
class GraphvizLabel a where
toGVItem :: a -> String
toGVLabel :: a -> String
toGVLabel = squareBrackets . ("label=" ++) . toGVItem
-- We only need to print Strings, Ints, Chars, and Maps.
instance GraphvizLabel String where
toGVItem = quoted
instance GraphvizLabel Int where
toGVItem = quoted . show
instance GraphvizLabel Char where
toGVItem = toGVItem . (: []) -- Custom behavior: no single quotes.
instance (GraphvizLabel k, GraphvizLabel v) => GraphvizLabel (Map k v) where
toGVItem = let kvfn k v = ((toGVItem k ++ "=" ++ toGVItem v) :)
in intercalate "," . M.foldWithKey kvfn []
toGVLabel = squareBrackets . toGVItem
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances
, UndecidableInstances, OverlappingInstances #-}
-- Leave the module declaration, imports, formatting code, and class declaration
-- the same.
instance GraphvizLabel String where
toGVItem = quoted
instance Show a => GraphvizLabel a where
toGVItem = quoted . show
instance (GraphvizLabel k, GraphvizLabel v) => GraphvizLabel (Map k v) where
toGVItem = let kvfn k v = ((toGVItem k ++ "=" ++ toGVItem v) :)
in intercalate "," . M.foldWithKey kvfn []
toGVLabel = squareBrackets . toGVItem
graphvizWithLabeler :: (a -> String) -> ... -> String
graphvizWithLabeler labeler ... =
...
where sa = labeler a
graphviz = graphvizWithLabeler sl
data NodeType
= MapNode (Map.Map Foo Bar)
| IntNode Int