定义尽可能少的方法以获得自定义对象的基本功能 (Julia)

Define as few methods as possible to get basic functionality for custom object (Julia)

我仍然是 Julia 的初学者,所以对 OOP 术语深表歉意。

XY为元素类型为MyType的两个用户定义矩阵。设 I 为单位矩阵。 MyType,在本例中,恰好是一个只有一个属性的对象,所有的操作都是通过这个属性完成的。 我正在尝试测试 ((X-I)')^-1 是否大约为 Y。我想通过定义尽可能少的函数来做到这一点。但我也不希望 MyType 继承任何东西。


我希望我只需要定义:

然而,在实践中,我需要的远不止这些。有什么方法可以减少为此实现的方法的数量?


这是我目前所拥有的。我花了一些时间才终于不再收到 MethodErrors。

# Define MyType
struct MyType  # Don't inherit from anything
    value
end
value(x::MyType) = x.value

# Define its methods
Base.one(::Type{MyType}) = MyType(1)
Base.zero(::Type{MyType}) = MyType(0)

Base.:+(x::MyType, y::MyType) = MyType(value(x)+value(y))
Base.:*(x::MyType, y::MyType) = MyType(value(x)*value(y))
Base.:-(x::MyType, y::MyType) = MyType(value(x)-value(y))
Base.:/(x::MyType, y::MyType) = MyType(value(x)/value(y))

Base.adjoint(x::MyType) = MyType(adjoint(value(x)))

function Base.isapprox(x::MyType, y::MyType; atol, rtol)
    return isapprox(value(x), value(y), atol=atol, rtol=rtol)
end
function Base.rtoldefault(::Type{MyType}, ::Type{MyType}, z)
    # I don't really want to be restricted to Float64 tolerance.
    return Base.rtoldefault(Float64, Float64, z)
end

# Shouldn't have to define these
Base.one(::MyType) = one(MyType)
Base.zero(::MyType) = zero(MyType)

Base.:+(x::MyType, y) = MyType(value(x)+y)
Base.:*(x::MyType, y) = MyType(value(x)*y)

Base.abs(x::MyType) = MyType(abs(value(x)))
Base.:<(x::MyType, y::MyType) = value(x) < value(y)
Base.inv(x::MyType) = MyType(inv(value(x)))

Base.promote_rule(::Type{Any}, ::Type{MyType}) = MyType
Base.convert(::Type{MyType}, x::MyType) = x
Base.convert(::Type{MyType}, x) = MyType(x)

Base.iterate(x::MyType) = (value(x), nothing)
Base.iterate(::MyType, ::Any) = nothing
Base.length(x::MyType) = 1

# Begin check for ((X-I)')^-1 ≈ Y
X = [
    0.4 1;
    -0.2 0.8;
]
X = map(x -> MyType(x), X)

Y = [
    -0.625 0.625;
    -3.125 -1.875;
]
Y = map(x -> MyType(x), Y)

using LinearAlgebra
println((X-I)')
println(inv((X-I)'))
println(inv((X-I)') ≈ Y)

我知道我可以使用宏来简化此操作。但这不是练习的重点。

我确信我犯了很多愚蠢的错误,尤其是 promote_ruleiterate。我试过多次阅读文档,所以我一定很愚蠢。

非常感谢任何帮助。

缺少回退方法有时是一种疏忽(您可以提交拉取请求来修复它),有时是故意的。让我们以 zero 为例:为什么没有回退 zero(x) = zero(typeof(x)),这样您就不必自己编写了?好吧,显然这并不难添加,而且有人可能会争辩说我们应该拥有它。但是考虑一下如果用户创建了一个新类型然后最终需要一个未定义的方法会发生什么:

julia> struct MyMatrix{T}
           buffer::Vector{T}
           sz::Tuple{Int,Int}
       end

julia> M = MyMatrix(rand(9), (3, 3));

julia> zero(M)
ERROR: MethodError: no method matching zero(::MyMatrix{Float64})
Closest candidates are:
  zero(::Union{Type{P}, P}) where P<:Dates.Period at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.7/Dates/src/periods.jl:53
  zero(::T) where T<:Dates.TimeType at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.7/Dates/src/types.jl:423
  zero(::SparseArrays.AbstractSparseArray) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.7/SparseArrays/src/SparseArrays.jl:55
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

现在,我可以很容易地想象出如何编写这个方法:我有元素类型,我有大小,所以我已经准备好了。假设我们定义了回退:

julia> Base.zero(M::MyMatrix) = zero(typeof(M))

julia> zero(M)
ERROR: MethodError: no method matching zero(::Type{MyMatrix{Float64}})
Closest candidates are:
  zero(::Union{Type{P}, P}) where P<:Dates.Period at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.7/Dates/src/periods.jl:53
  zero(::MyMatrix) at REPL[4]:1
  zero(::T) where T<:Dates.TimeType at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.7/Dates/src/types.jl:423
  ...
Stacktrace:
 [1] zero(M::MyMatrix{Float64})
   @ Main ./REPL[4]:1
 [2] top-level scope
   @ REPL[5]:1

好的,现在我坐下来实施 zero(::Type{MyMatrix{T}}) ... 并意识到我不知道应该创建多大的矩阵。如果我没有意识到我不应该从字面上理解错误信息,我现在真的有麻烦了。

有了 Number,您不需要任何额外的参数就可以知道该类型的零的含义,但这并不一定适用于所有情况。这突出了仅具有 合理 回退的优势。这在实践中意味着什么有点模糊,但子类型化为您提供了一种表达直觉的方式。