If I’m understanding the question properly, the problem is moreso that you can’t have multiple inheritance so in some cases you can’t easily generalize the types. Sometimes what you want to support is an Operator
type which could only exist in some library. If you want to <:Operator
, then you have to have the library which defines Operator
as a dependency. So you have to make a choice:
- Hope that everyone subtypes the right things (i.e. make your iterative solver work on
Union{Matrix,Function}
and tell other libraries they have to subtypeFunction
with their weird overloaded types). - Not try to support any non-Base type.
- Leave your function untyped.
For library building, leaving the function as untyped (duck-typing) tends to be the preferred option since it generalizes the applicability. Of course, there are the drawbacks you’ve mentioned (the errors are more obscure since they will always attempt to use the function, and the documentation won’t be as specific), but…
Yes, the “true” answer I think will come from traits. For now, interfaces are informal. However, we have setup in JuliaDiffEq using SimpleTraits.jl (macros for Holy traits) where traits are automatically applied when certain dispatches are implemented (and they are inferred by the compiler), and so ImplementsIterator
dispatches are possible using this kind of thing, i.e. you have make a function
f(x::::ImplementsIterator) = ...
and everything about the traits are known at compile time (so no dynamic dispatch).
I think this form of trait-constraints for documentation/errors + automatically/dynamically applied traits (but when the function compiles, so it compiles to fully-performant code as seen by @code_llvm
) for ease of use and “auto-extensability” is the middleground. Of course, Julia doesn’t have a base implementation for traits yet so this is still experimental (but very useful! I’m not sure it plays well with pre-compilation either), but I see a bright future.