While I’m generally extremely happy with Julia’s type system, I have sometimes found myself wishing it were easier to efficiently deal with heterogeneous collections. For example, if I have a Vector of elements of different concrete types, it’s much slower to call a method on all of those elements than it would be if they were all the same type.
FunctionWrappers.jl makes this possible by allowing you to store concretely-typed function wrappers instead of the objects themselves, but that package is very low-level. I’ve been building on top of FunctionWrappers and have come up with an approach that might be more user-friendly: https://github.com/rdeits/Interfaces.jl
The core of the package is the @interface
macro, which is used like this:
@interface MyInterface{T}(self) begin
foo(y::T)::T = self.x + 1
bar()::T = self.x
end
This defines a new type, MyInterface
which looks something like:
struct MyInterface{T}
self::Any
foo::FunctionWrapper{T, Tuple{T}}
bar::FunctionWrapper{T, Tuple{}}
MyInterface{T}(self) = new(self, y -> self.x + y, () -> self.x)
end
and also defines foo(::MyInterface{T}, ::T)
and bar(::MyInterface)
methods.
The result is that you can construct a MyInterface
from any input self
that you want, and all MyInterface{T}
objects have the same concrete type regardless of the type of self
. Calling the foo()
and bar()
methods on a MyInterface
goes through the function wrappers, so there is no dynamic dispatch (even though self
is untyped). That means that you can call y .= bar.(::Vector{MyInterface{T}})
with no memory allocation and much better performance than y .= bar.(::Vector{Any})
.
I’ve put together a more complete demo here: https://github.com/rdeits/Interfaces.jl/blob/f729c534af987244348d08ea359c39fc47451885/demo.ipynb
I’m very curious if this is something that other people have wanted, and whether the design I’ve put together is one that would be useful. Please let me know!