在Haskell中实现ad-hoc多态的最佳方式是什么? [英] Best way to implement ad-hoc polymorphism in Haskell?
问题描述
我有一个多态函数,如:
convert ::(Show a)=> a - > String
convert =[label =++(show a)++]
<但有时候我想通过它传递一个Data.Map并做一些更加奇妙的键值转换。我知道我不能在这里模式匹配,因为Data.Map是一个抽象数据类型(根据这个类似的SO问题),但我一直没有成功使用警卫来达到这个目的,而且我不确定ViewPatterns是否会在这里帮助(而且为了便携性避免使用它们)。
这是我想要的:
导入限定数据。 Map as M
convert ::(Show a)=> a - >字符串
转换
| M.size \ = 0 = processMap2FancyKVString a - Heres a Data.Map
|否则=[label =++(show a)++] - 可能是一个字符串
<但是这不起作用,因为M.size除了Data.Map之外不能取任何其他东西。
具体来说,我试图修改 sl功能图库中的效用函数,以便处理GraphViz输出中边缘的着色和其他属性。
更新
我希望我可以接受TomMD,Antal深圳和卢奎这个问题,因为他们都明白我真的在问什么。我会说:
- 安塔尔SZ为FGL提供了最优雅的解决方案,但也需要最多的重写和重新思考执行个人问题。 TomMD给出了一个很好的答案,它介于Antal S-Z和luqui之间的适用性和正确性之间。这也是直接的,并且我非常欣赏,为什么我选择了他的答案。
- luqui给出了最好的让它快速工作的答案,我可能会在实践中使用它(因为我是一名研究生,而这只是一些一次性代码来测试一些想法)。我不接受的原因是因为TomMD的回答可能会更好地帮助其他人在更一般的情况下。
有了这些说法,他们都是非常好的答案,上面的分类是一个粗略的简化。我还更新了问题标题以更好地代表我的问题(谢谢再次感谢您扩大我的视野!)
刚刚解释过,你需要一个基于输入类型的行为不同的函数,而你可以使用 data
wrapper,从而关闭所有时间的函数: data可转换ka = ConvMap(Map ka)| ConvO
convert(ConvMap m)= ...
convert(ConvOther o)= ...
更好的方法是使用类型类, convert
函数打开并可扩展,同时防止用户输入非感性组合(例如: ConvOther M.empty
)。
class(显示a)=>可转换a其中
转换:: a - >字符串
实例可转换(M.Map ka)其中
转换m = processMap2FancyKVString m
newtype ConvWrapper a = CW
实例Convertable(ConvWrapper a)其中
convert(CW a)=[label =++(show a)++]
通过这种方式,您可以为每种不同的数据类型使用您想要使用的实例,并且每次需要新的特化时,您都可以扩展<$ c通过添加另一个实例Convertable NewDataType其中...
。
转换 >有些人可能会对 newtype
包装器皱眉,并建议一个实例:
instance Convertable a where
convert ...
但是这需要强烈的不鼓励重叠和不可确定的实例扩展,为程序员提供了便利。
I have a polymorphic function like:
convert :: (Show a) => a -> String
convert = " [label=" ++ (show a) ++ "]"
But sometimes I want to pass it a Data.Map and do some more fancy key value conversion. I know I can't pattern match here because Data.Map is an abstract data type (according to this similar SO question), but I have been unsuccessful using guards to this end, and I'm not sure if ViewPatterns would help here (and would rather avoid them for portability).
This is more what I want:
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
But this doesn't work because M.size can't take anything other than a Data.Map.
Specifically, I am trying to modify the sl utility function in the Functional Graph Library in order to handle coloring and other attributes of edges in GraphViz output.
Update
I wish I could accept all three answers by TomMD, Antal S-Z, and luqui to this question as they all understood what I really was asking. I would say:
- Antal S-Z gave the most 'elegant' solution as applied to the FGL but would also require the most rewriting and rethinking to implement in personal problem.
- TomMD gave a great answer that lies somewhere between Antal S-Z's and luqui's in terms of applicability vs. correctness. It also is direct and to the point which I appreciate greatly and why I chose his answer.
- luqui gave the best 'get it working quickly' answer which I will probably be using in practice (as I'm a grad student, and this is just some throwaway code to test some ideas). The reason I didn't accept was because TomMD's answer will probably help other people in more general situations better.
With that said, they are all excellent answers and the above classification is a gross simplification. I've also updated the question title to better represent my question (Thanks Thanks again for broadening my horizons everyone!
What you just explained is you want a function that behaves differently based on the type of the input. While you could use a data
wrapper, thus closing the function for all time:
data Convertable k a = ConvMap (Map k a) | ConvOther a
convert (ConvMap m) = ...
convert (ConvOther o) = ...
A better way is to use type classes, thus leaving the convert
function open and extensible while preventing users from inputting non-sensical combinations (ex: ConvOther M.empty
).
class (Show a) => Convertable a where
convert :: a -> String
instance Convertable (M.Map k a) where
convert m = processMap2FancyKVString m
newtype ConvWrapper a = CW a
instance Convertable (ConvWrapper a) where
convert (CW a) = " [label=" ++ (show a) ++ "]"
In this manner you can have the instances you want used for each different data type and every time a new specialization is needed you can extend the definition of convert
simply by adding another instance Convertable NewDataType where ...
.
Some people might frown at the newtype
wrapper and suggest an instance like:
instance Convertable a where
convert ...
But this will require the strongly discouraged overlapping and undecidable instances extensions for very little programmer convenience.
这篇关于在Haskell中实现ad-hoc多态的最佳方式是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!