[ANN] Announcing MethodForwarding.jl

Hello,

I’m announcing AutoForward.jl MethodForwarding.jl(still in the registering 3-day waiting period), which should make composing objects much more hasslefree. It’s a bit what I’ve always wanted @forward from Lazy.jl to be.

The package exports a single @forward macro. The behaviour is probably best explained with an example (for details check the README in the repo):

struct A
    x::Int
end
value(a::A) = a.x
doubleA(a::A) = 2 * value(a)

@forward A struct NamedA
    a::A
    name::String
end
name(na::NamedA) = na.name

a = A(10)
named_A = NamedA(a, "name")
@assert value(named_A) == 10
@assert doubleA(named_A) == 20

We can also make a wrapper type splat into function arguments:

function1(a::Int, b::Int) = a + b
function2(a::Int, b::Int, c::Int) = a + b + c

@forward {Int, Int},
struct Point2
    x::Int
    y::Int
end

p = Point2(1,1)
@assert function1(p) == 2
@assert function2(p, 1) == function2(1, p) == 3

By default only the functions defined in the current module are forwarded, but it is possible to specify a list of modules and/or functions for which to forward their methods:

module MainModule

module InnerModule
  export innerfunc
  innerfunc(a::Int, b::Int) = a + b + 100
  anotherfunc(a::Int, b::Int) = a + b + 200
end
using .InnerModule

function1(a::Int, b::Int) = a + b
function2(a::Int, b::Int, c::Int) = a + b + c

@forward {Int, Int},
struct Point2
	x::Int
	y::Int
end function1
# Generates: 
# function1(p)

@forward {Int, Int},
struct Point2
	x::Int
	y::Int
end InnerModule # forwards only the public/exported methods of InnerModule
# Generates: 
# innerfunc(p)

@forward {Int, Int},
struct Point2
	x::Int
	y::Int
end InnerModule.anotherfunc # forwards a specific method of a specific module
# Generates: 
# anotherfunc(p)

@forward {Int, Int},
struct Point2
	x::Int
	y::Int
end (InnerModule, function2) # exports all public functions of InnerModule, and  function2
# Generates: 
# function2(a, p)
# function2(p, c)
# innerfunc(p)

end # module

This package has not been used in the wild very much and it’s still highly experimental, so don’t expect flawless outputs.

I am also not married to the name, if the name @forward or AutoForward MethodForwarding rubs you the wrong way i’m open to suggestions (while still in the package registry waiting period if possible)!

9 Likes

Hi, congrats on the package!
I have no opinions on the content, just want to point out that the name AutoForward could suggest that it has to do with automatic differentiation. It sounds a lot like a backend name from ADTypes.jl (like AutoForwardDiff). I’m not opposing the registration on these grounds but if you have alternative name ideas (AutoCompose?) it might be less confusing.

12 Likes

I guessed correctly from the name what the package does when I saw the announcement…but had to second-guess when I saw @gdalle’s face in the thread :slight_smile:

4 Likes

That is definitely a valid concern. I’ll workshop another name for the package.

1 Like

Would something like MethodForwarding.jl be more descriptive? At least something that describes what is being forwarded?

7 Likes

Perhaps something with ‘Extends’, as it basically extends an existing struct with more fields, while keeping the original methods?

Yes! That’s the name I’ve landed on as well.
Today I’ll make the switch. Thanks for all the suggestions!

I can’t decide whether it’s a good thing or a bad thing that this name sounds so similar to ForwardMethods.jl by @curtd.

The two packages some similar functionality, so it would make sense that their names are similar, but I can also see myself never being able to remember which one has the specific features I want in a particular case, and constantly getting confused between the two.

1 Like

I see the potential headache, but given the extremely narrow focus of this package, a precise and descriptive name like MethodForwarding.jl I think would be better than anything more abstract. I’ve toyed with the idea of borrowing Rust derive terminology, given the neighbouring concepts, so something like DeriveMethods.jl with the macro called @derive instead. But I am not completely convinced, given that the term derive is very generic.

Maybe in a future the headache could be solved by merging the packages!

1 Like

Is this package suitable for something like this PR:

Use similar methods from ArrayPartion for NamedArrayPartition by SouthEndMusic · Pull Request #431 · SciML/RecursiveArrayTools.jl

where I basically apply a method on a field of a wrapper and then reapply the wrapper.

No, not at the current state. Something like

@forward ArrayPartition,
struct NamedArrayPartition
    arr::ArrayPartition
    name::String
end

would result in

similar(A::NamedArrayPartition) = similar(A.arr)

This raises a good point though. I’ve already excluded constructors from the automatic forwarding for exactly this reason, that usually constructing objects needs more customized behaviour. But I didn’t think about constructor-like methods such as similar.

One could add some customized generators for special cases like this, something like
similar(w::Wrapper) = Wrapper( W.field1, ..., similar(forwarded field), W.fieldn, ...)
could work, but I think that culd be a slippery slope.

1 Like

The package has been merged into the registry and updated to 0.2.0. It should be availble for use.

The new version added support for:

  • parametric forwarding:
# we only forward wrappers of arrays of integers.
@forward Array{T,N} where {T<:Integer,N},
struct MyArray{T,N}
    a::Array{T,N}
    some_attr::Int
    MyArray(T, dims::NTuple{N,Int}) where {N} = new{T,N}(zeros(T, dims...), 1)
end, (Base.size,)

afloat = MyArray(Float64, (2, 2, 2))
aint = MyArray(Int, (2, 2, 2))

@assert size(aint) == (2,2,2)
size(afloat) # results in a method error.
  • keyword arguments passthrough
  • anonymous arguments

happy forwarding!

1 Like