I’ve turned my attention to this once again, and I think I need to illustrate 
the current roadblock I’ve hit to seek some advice.

Currently, there is no way to specify that implementing one generic interface 
automatically provides an implementation of another generic interface. This is 
actually a pretty big flaw. One of the most common uses of Haskell typeclasses 
is providing wrappers, such as Compose, which provide implementations of 
Applicative “for free”.

I think this is best explained via example, so I’ve put together some sample 
code here: https://gist.github.com/lexi-lambda/535b7985d4570d1e0222 
<https://gist.github.com/lexi-lambda/535b7985d4570d1e0222>

That snippet includes two files, a.rkt and b.rkt. The first is how the generic 
system currently operates, while the second example will fail to compile. The 
first example illustrates a problem, and the second example is my current idea 
for a solution.

The basic idea is this: a generic interface, in this case gen:sequence, 
provides some basic operations, in this case cons, first, rest, and empty?. A 
separate generic interface is gen:iterable. Obviously, plenty of things that 
aren’t simple sequences can be iterable—for example, hash tables and sets—so it 
makes sense to have a separate interface.

The problem is that, while gen:iterable can be trivially implemented in terms 
of the methods of gen:sequence, there is no way to specify that all 
implementors of gen:sequence should also have implementations for gen:iterable 
defined in terms of gen:sequence’s methods. The current “solution” to this is 
to add a clause to gen:iterable’s #:defaults clause, as I’ve demonstrated in 
the first example. This does, indeed, make all implementations of gen:sequence 
also iterable. Unfortunately, this solution is inadequate for two reasons:

This creates a circular dependency between gen:iterable and gen:sequence. It 
becomes impossible to put these interfaces in separate modules. This means that 
all “subinterfaces” of gen:iterable must be defined in the same module as the 
iterable interface itself, which is, frankly, unacceptable.

Furthermore, and perhaps even more of a deal-breaker, this completely prevents 
user-defined interfaces from providing “automatic” implementations of 
gen:iterable. All of these must be manually encoded into the #:defaults clause, 
and if a user does not have the ability to modify the interface’s declaration, 
there is no solution.

The most elegant solution would be to add a way to make interfaces “inherit” 
other interfaces. I’ve provided a sample of what that might look like in the 
second example, b.rkt. By pulling the relationship out of gen:iterable’s 
#:defaults clause and moving into gen:sequence’s #:implements clause (analogous 
to #:methods on struct definitions), this alleviates both of the above problems.

I have become increasingly convinced that some kind of solution to these 
problems is fundamentally necessary to having an expressive enough generics 
system to adequately implement things like generic collections elegantly. Even 
if my proposed solution is not the right approach, I think something of the 
sort needs to be implemented before the issue of generic collections can be 
tackled.

This raises the question of how to actually implement such a thing. I’m not 
terribly familiar with how the generics system actually works—I know it’s 
really just implemented in terms of struct properties, but I’m unfamiliar with 
the precise details of that machinery—and though I’ve read through portions of 
racket/private/generic and racket/private/define-struct, no clear and obvious 
path to implementation is apparent to me. Perhaps someone with better 
experience in that area would be able to point me in the right direction.

The two main approaches that come to mind are implementing this as a derived 
concept via some sort of desugaring, or by encoding this into generic info 
itself. In the former case, using #:methods with a generic interface that 
included “super-interfaces” would simply expand to a separate #:methods clause 
linking the method definitions together. The latter would build it into the 
system in a more fundamental way.

As a final note, this does introduce the classic problem of multiple 
inheritance. If my understanding is correct, this problem could also possibly 
occur using Haskell’s typeclasses, so I do plan to investigate what the 
resulting behavior is in that case. That said, my guess is that true conflicts 
would be rare in practice (as is usually the case with systems that permit 
multiple inheritance), and providing the implementations as “defaults” rather 
than definitive method implementations would allow manual disambiguation. In 
fact, Java 8 supports precisely this sort of thing with its ability to define 
default interface method definitions, which should be pretty much directly 
analogous to this feature in generic interfaces.

Anyway, I apologize for the wall of text. I am highly interested in coming up 
with a solution to this problem, as I would like to continue work on my generic 
collections library, but I’m not sure how to proceed. I hope I’ve made myself 
clear without being too unnecessarily verbose. Feedback is, of course, 
appreciated, and any help even more so.

Thanks,
Alexis

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-dev+unsubscr...@googlegroups.com.
To post to this group, send email to racket-dev@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-dev/9755D974-195A-4A24-99AE-267752DE8C5E%40gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to