January 2018

S M T W T F S
  123456
78910111213
14151617181920
21222324252627
28293031   

Style Credit

Expand Cut Tags

No cut tags
Saturday, March 10th, 2007 12:57 pm
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:
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 = show
Except:
  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] totherme to work out what was going on. Here's the relevant section from YAHT:
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:
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
         (==) = myeq
In 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.
Because clearly there's no known algorithm for detecting a loop in a graph.

For goodness' sake.

Reply

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting