Best way to implement ad-hoc polymorphism in Haskell?

后端 未结 3 1757
悲&欢浪女
悲&欢浪女 2020-12-28 09:32

I have a polymorphic function like:

convert :: (Show a) => a -> String
convert = \" [label=\" ++ (show a) ++ \"]\"

But sometimes I w

3条回答
  •  执笔经年
    2020-12-28 09:52

    You may not be asking the right thing. I'm going to assume that you either have a graph whose nodes are all Maps or you have a graph whose nodes are all something else. If you need a graph where Maps and non-maps coexist, then there is more to your problem (but this solution will still help). See the end of my answer in that case.

    The cleanest answer here is simply to use different convert functions for different types, and have any type that depends on convert take it as an argument (a higher order function).

    So in GraphViz (avoiding redesigning this crappy code) I would modify the graphviz function to look like:

    graphvizWithLabeler :: (a -> String) -> ... -> String
    graphvizWithLabeler labeler ... =
       ...
       where sa = labeler a
    

    And then have graphviz trivially delegate to it:

    graphviz = graphvizWithLabeler sl
    

    Then graphviz continues to work as before, and you have graphvizWithLabeler when you need the more powerful version.

    So for graphs whose nodes are Maps, use graphvizWithLabeler processMap2FancyKVString, otherwise use graphviz. This decision can be postponed as long as possible by taking relevant things as higher order functions or typeclass methods.

    If you need to have Maps and other things coexisting in the same graph, then you need to find a single type inhabited by everything a node could be. This is similar to TomMD's suggestion. For example:

    data NodeType
        = MapNode (Map.Map Foo Bar)
        | IntNode Int
    

    Parameterized to the level of genericity you need, of course. Then your labeler function should decide what to do in each of those cases.

    A key point to remember is that Haskell has no downcasting. A function of type foo :: a -> a has no way of knowing anything about what was passed to it (within reason, cool your jets pedants). So the function you were trying to write is impossible to express in Haskell. But as you can see, there are other ways to get the job done, and they turn out to be more modular.

    Did that tell you what you needed to know to accomplish what you wanted?

提交回复
热议问题