Better support for wrapper types

I’m sure I cannot be the only one who sometimes feels that writing wrappers for types in Julia that I just want to either extend with some custom methods or change the functionality of just a few built-in functions can sometimes get a bit bothersim. With multiple dispatch being such an integral feature of Julia, it feels a bit silly to write functions like Base.getindex(ob::MyType, idx) = getindex(ob.vec, idx) so many times.

Wouldn’t it make sense to have some built in pattern (or macro) that allows users to automatically forward all function that are already defined for some type, to one of its fields?

1 Like

See this discussion for further discussion and, in particular, its recommendation of ForwardMethods.jl.

While sometimes I think about forwarding literally all methods, I usually end up deciding that’s too broad a brush. It also tends to break down. For example, if you wrap a <:Number, you might want sqrt to unwrap the value, apply sqrt and re-wrap it. But you don’t want to do this for another function like >=(0), since you want that to return a Bool.

While doing this only for all functions explicitly defined for the wrapped type sounds more sensible, recall that many methods are duck-typed, so that list is very incomplete (for example, it would miss any of the many methods that take arbitrary arguments and promote them).

If you aren’t using a package’s macro but instead defining forwards manually for an explicit list, metaprogramming can be useful:

# unary functions
for f in [:abs, :abs2, :sqrt, :exp, :log, :sin, :cos, :tan] # and many more
	@eval Base.$f(x::MyType) = MyType(Base.$f(x.value))
end

# binary functions
for f in [:+, :-, :*, :/, :^] # and many more
	@eval Base.$f(x::MyType, y::Number) = MyType(Base.$f(x.value, y))
	@eval Base.$f(x::Number, y::MyType) = MyType(Base.$f(x, y.value))
	@eval Base.$f(x::MyType, y::Number) = MyType(Base.$f(x.value, y.value))
end
7 Likes

or not, if you have:

struct PositiveFloat
  # assert it's >= 0.0, I'm not sure how to do this in code, or if possible:
  num::Float32
end

then you want it to work on it directly (because of multiple dispatch), and NOT unwrap it first, then you would disable the assert, because it will provide faster code, i.e. should give you access to a single fast assembly instruction without any checks or possible error throwing.

From my experience, if the type is just a wrapper, then it often has different semantics from the data it contains, and forwarding all methods would be wrong. If it is not just a wrapper but also something else, e.g. an AbstractArray, the methods to be defined come from an informal interface spec. But in that case the data layout is an implementation detail and also the method implementations can be hard to automate.