ChainRulesCore.jl icon indicating copy to clipboard operation
ChainRulesCore.jl copied to clipboard

Handling structs without the default constructor via `Expr(:new,...` ?

Open AlexRobson opened this issue 4 years ago • 3 comments

In FiniteDifferences, this PR allowed custom constructors to be handled as part of to_vec: https://github.com/JuliaDiff/FiniteDifferences.jl/pull/181 here

However iiuc construct in CRC here doesn't handle this. In this case however, the constructor requirements are mentioned in the docstring.

However, tests in CRTU will construct the new type as part of the call to FiniteDifferences but then fail later as the constructor is not found in the call to construct.

Allowing this here, or otherwise, it would be easier if these behaviours were consistent in terms of custom types as part of the CTRU testing pipeline.

EDIT: MWE:

struct CustomType{T}
           x
           function CustomType(X::T) where {T}
               new{T}(X)
           end
end

# This misses the constructor:
CustomType{Int64}(5) # ERROR: MethodError: no method matching CustomType{Int64}(::Int64)

val = CustomType(5) # CustomType{Int64}(5)
diff = Tangent{typeof(val)}(x = 5) # Tangent{CustomType{Int64}}(x = 5,)

val + diff #ERROR: MethodError: no method matching CustomType{Int64}(::Int64)

# FiniteDifferences handles this in the vec to x:

to_vec(CustomType(5))[2]([6.0]) #CustomType{Int64}(6.0)

Defining CustomType{T}(x) where {T} = CustomType(x) allows this pass.

cc @mzgubic

AlexRobson avatar Jul 22 '21 10:07 AlexRobson

Oh, that's interesting. Is the suggestion that we implement the same hack in construct? (We should probably consider a hack in CRC more carefully than in FD).

Could you provide an MWE where this fails to see if there is a way around it?

mzgubic avatar Jul 22 '21 10:07 mzgubic

Is the suggestion that we implement the same hack in construct? (We should probably consider a hack in CRC more carefully than in FD).

Agree with this. I wasn't going so far as to suggest this because of the this reason. However from a user perspective, I just found when working with a custom type, the constructor issue re-emerged later when testing because of, as it turns out, the above reason (if i've diagnosed it correctly).

In general, having these constructors would be prefereable rather than these workarounds. OTOH, this may make working with custom types a bit easier.

I've added a MWE.

AlexRobson avatar Jul 22 '21 11:07 AlexRobson

Yeah, we could do this. We have thought about doing this before. It does mean we could violate some assumptions.

My point on a unit circle example is one.

struct CirclePoint
    x::Float64
    y::Float64
    CirclePoint(angle) = new(cos(angle), sin(angle))
end

foo(c::CiclePoint) = 2c.x + c.y

dc = rrule(foo, CirclePoint(pi/2))[2](1.0)
dc == Tangent{CirclePoint}(x=2, 1)
CirclePoint(pi/2) + dc == some point that is not on the unit circle

oxinabox avatar Jul 22 '21 14:07 oxinabox