A while ago I wrote a post about what appeared to be the endemic pusillanimity of the Haskell community, tried to find more charitable explanations for my observations, and, I think, mostly succeeded. Unfortunately, the example I gave turned out not to be a good one: it turns out that it is possible to have integer-parametrized types in Haskell (though you have to go through Hell and high water to do so). But the other day, I remembered a much better example.
I'll tell you how I encountered this problem, as a way of leading up to it. I was debugging some code that dealt with deeply-nested lists. Reading the output of "show" was getting painful, and I knew that what I really needed was something like Perl's Data::Dumper, which produces output like the following:
I couldn't find anything like this in the libraries or online, so I had to write my own. Shouldn't be too hard, I thought. OK, so I wanted some polymorphic function to do the dumping, which meant (deep breath) that I was going to have to use typeclasses. So, I defined a typeclass PPrint of pretty-printable data structures, with a single method pprint. Obviously, this function is going to handle lists and maps via recursion, and writing the code for lists was pretty straightforward. But what about the base case? Well, I wanted pprint to default to "show" when I hadn't defined anything else for it to do. I'd also like this falling-back to happen automatically, so my user (ie, me) wouldn't have to write a boilerplate PPrint instance for every single datatype involved in any structure they wanted to pretty-print (which would involve editing code and recompiling, which would break the flow of debugging). Easy enough:
totherme to work out what was going on. Here's the relevant section from YAHT:
For goodness' sake.
I'll tell you how I encountered this problem, as a way of leading up to it. I was debugging some code that dealt with deeply-nested lists. Reading the output of "show" was getting painful, and I knew that what I really needed was something like Perl's Data::Dumper, which produces output like the following:
perl -MData::Dumper -e 'print Dumper { fred => [1,2,[3,4]], barney => [[1,2,[3,4]],[1, { wilma => 3, betty => 4 }]] }' $VAR1 = { 'barney' => [ [ 1, 2, [ 3, 4 ] ], [ 1, { 'betty' => 4, 'wilma' => 3 } ] ], 'fred' => [ 1, 2, [ 3, 4 ] ] };[There's also a CPAN module called Data::Dump::Streamer, which can handle data structures containing closures, printing out the code in the closure (as of v1.11, including bound variables). This makes writing and debugging higher-order functions vastly easier.]
I couldn't find anything like this in the libraries or online, so I had to write my own. Shouldn't be too hard, I thought. OK, so I wanted some polymorphic function to do the dumping, which meant (deep breath) that I was going to have to use typeclasses. So, I defined a typeclass PPrint of pretty-printable data structures, with a single method pprint. Obviously, this function is going to handle lists and maps via recursion, and writing the code for lists was pretty straightforward. But what about the base case? Well, I wanted pprint to default to "show" when I hadn't defined anything else for it to do. I'd also like this falling-back to happen automatically, so my user (ie, me) wouldn't have to write a boilerplate PPrint instance for every single datatype involved in any structure they wanted to pretty-print (which would involve editing code and recompiling, which would break the flow of debugging). Easy enough:
instance (Show a) => PPrint a where pprint = showExcept:
Illegal instance declaration for `PPrint a' (The instance type must be of form (T a b c) where T is not a synonym, and a,b,c are distinct type variables) In the instance declaration for `PPrint a'Well, that's not the least comprehensible error message that GHC's given me by some distance, but it still took a few emails to
![[livejournal.com profile]](https://www.dreamwidth.org/img/external/lj-userinfo.gif)
The “Eq a =>” part of the definition is called the “context.” We should note that here are some restrictions on what can appear in the context and what can appear in the declaration. For instance, we’re not allowed to have instance declarations that don’t contain type constructors on the right hand side. To see why, consider the following declarations:Because clearly there's no known algorithm for detecting a loop in a graph.class MyEq a where myeq :: a -> a -> Bool instance Eq a => MyEq a where myeq = (==)As it stands, there doesn’t seem to be anything wrong with this definition. However, if elsewhere in a program we had the definition:instance MyEq a => Eq a where (==) = myeqIn this case, if we’re trying to establish if some type is an instance of Eq, we could reduce it to trying to find out if that type is an instance of MyEq, which we could in turn reduce to trying to find out if that type is an instance of Eq, and so on. The compiler protects itself against this by refusing the first instance declaration.
For goodness' sake.
Tags: