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
datawrapper, thus closing the function for all time:A better way is to use type classes, thus leaving the
convertfunction open and extensible while preventing users from inputting non-sensical combinations (ex:ConvOther M.empty).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
convertsimply by adding anotherinstance Convertable NewDataType where ....Some people might frown at the
newtypewrapper and suggest an instance like:But this will require the strongly discouraged overlapping and undecidable instances extensions for very little programmer convenience.