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
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe