1
$\begingroup$

Not sure if this question fits here, but I'm looking to implement (or try to) Hakell's type classes into my own language, however I don't understand how resolution works on recursive types like the one below;

data Bush a = Leaf a | Nest (Bush (a, a))

I then implemented a show method on it in a single definition like this:

instance (Show a) => Show (Bush a) where
 show (Leaf x) = show x
 show (Nest x) = show x

It works fine, but I can't understand how the show method would be passed at runtime.
Consider we have a (Bush Int); it might be a leaf (in that case it'd call show on Int) or it could be a nest (Bush (Int, Int)), then it'd require a different show, which could then require yet another one, and so on. Is the resolution done at runtime, or how is that possible?

Don't know if it helps, but I did find a raw definiton given that a is a semigroup (hence a combine argument). I find it weird how the first approach, using instances, works without it.

showx :: Bush a -> ((a, a) -> a) -> a
showx (Leaf x) _ = x
showx (Nest bx) combine = combine (showx bx (\(pair1, pair2) -> (combine pair1, combine pair2)))
asked Sep 15, 2024 at 22:44
$\endgroup$

1 Answer 1

2
$\begingroup$

The instance essentially works the same way as your last example. Implicit is the fact that there is an:

instance (Show a, Show b) => Show (a, b) where
 ...

This specializes to Show a => Show (a, a).

In the case:

 show (Nest x) = show x

We need Show (Branch (a, a)), which requires Show (a, a), which using the pair instance reduces to Show a.

Perhaps the other aspect to note is that the Show (Branch a) instance is—like showx—polymorphically recursive. So when you recursively refer to show, it might be using the Show (Branch a) instance at a different a.

As for how this works, it's true that there is no fixed, finite amount of 'dictionaries' that covers all the Show constraints necessary, so they must be built at runtime (assuming that's how you're implementing type classes). However, those dictionaries are all generated by a finite amount of instance schemas, namely:

Show a => Show (a , a)
Show a => Show (Branch a)

Each of these is defined uniformly for all a, and we're just progressively using different instantiations of them that delegate to dictionaries we've previously built. So we can statically resolve the instance schemas required to build the arbitrary dictionaries at runtime, and generate code for that. This is the same as how your combine for arbitrarily nested tuples is built by repeatedly applying a fixed way of building it out of the smaller combine.

answered Sep 16, 2024 at 1:39
$\endgroup$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.