[Haskell-cafe] Type classes vr.s functions

Brian Hurt bhurt at spnz.org
Wed Dec 24 09:48:55 EST 2008



On Tue, 23 Dec 2008, wren ng thornton wrote:

> In particular, imagine that you have two different and valid ways to convert 
> the same type into Foo; which do you choose? With the 
> continuation/combinator/argument approach this is a non-issue since you can 
> just pass in the one you need. With type-classes it's tricky since they're 
> the same type, which leads to hacks with newtype wrappers or phantom types.
>
> If there is guaranteed to be only one valid transformation from any given 
> type into Foo, then type-classes make your intentions clear and they never 
> run into this issue. If more than one valid transformation could exist for 
> some type, then the extra argument is cleaner.

In this case, there is gaurenteed to be only one valid transformation. 
Basically, I have a number of similar data structures (which enforce 
different constraints, which is why they're not all the same data 
structure), and the function in question converts the specific 
(constraint-enforcing) data structures into a general 
(non-constraint-enforcing) data structure on which I can perform generic 
algorithms.

To be more specific, I'm writing a compiler in Haskell (what a shock), and 
the source data structures are the parse trees after various 
transformations- for example, after the lambda lifting phase, the parse 
tree should not have lambda expressions in them at all (they should have 
all been lifted to top-level functions).  So, while the 
before-lambda-lifting data structure has a Lambda constructor, the 
after-lambda-lifting data structure doesn't, thus enforcing the constraint 
that lambda lifting removes all (non-top-level) lambda expressions.  But I 
want to be able to get all free variables of a given expression, both 
before and after lambda lifting, so I just define a function to convert 
both types into a common generic representation that I can write a "get 
free variables" function to work on.

At this point, I realize that I'm being stupid and way over thinking 
things.  Haskell is a *lazy* language- I'm still wrapping my head around 
the implications of that statement.  My original thinking was that the 
conversion function would be a one-level conversion, i.e. the data 
structure would be like:
data Generic a =
 	UnaryOp uop a
 	| BinaryOp a bop a
 	| If a a a
 	...

i.e. I'd only convert the root node, and the child nodes would still be 
the original data structure.  So I'd need to pass around a function of the
form:
 	a -> Generic a
which is my conversion function.  But what I'm doing here is 
hand-reimplementing a lazy conversion of the data structure- which I get 
for free anyways.  So what I should do is define the data structure like:
data Generic =
 	UnaryOp uop Generic
 	| BinaryOp Generic bop Generic
 	| If Generic Generic Generic
 	...

Then all I need to do is to write the pure conversion function a -> 
Generic, and then run the generic algorithm over the data structure.  That 
gives me the exact behavior I'm looking for, without either (explicitly) 
passing a conversion function around or playing with fancy typeclasses.

Pardon me while I go beat my head against the wall.

Brian



More information about the Haskell-Cafe mailing list