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.