xfig
2020 年4 月 20 日 14:04
1
接着上一个帖子的操作: 运算符[]能否被重载?
定义了某波函数类 WaveFunc,实际存数据的是里面的成员变量 psi::Array{Complex, 1}
,
只是为了让 WaveFunc
对象在外界用起来像数组一样舒服,结果要把几乎所有的数组操作都重载一遍……
Julia 肯定是不让 WaveFunc
继承 Array
,但能不能指定里面的某变量,比如 psi
,让 WaveFunc
默认行为都和 psi
绑定,比如给 psi
声明成 super 或 parent 啥的,这样调用 ψ[i]
就等同调用 ψ.psi[i]
,达到变相继承的目的?
using Printf
struct WaveFunc
psi::Array{ComplexF64, 1}
function WaveFunc(n)
new(Array{ComplexF64}(undef, n))
end
end
# 下面这一堆的重载,实际都是 psi 这个数组的默认行为……
Base.getindex(ψ::WaveFunc, i::Int64) = ψ.psi[i]
Base.getindex(ψ::WaveFunc, r::UnitRange{Int64}) = ψ.psi[r]
Base.setindex!(ψ::WaveFunc, v::ComplexF64, i::Int64) = (ψ.psi[i] = v)
Base.setindex!(ψ::WaveFunc, v::Array{ComplexF64,1}, r::UnitRange{Int64}) = (ψ.psi[r] = v)
Base.size(ψ::WaveFunc) = size(ψ.psi)
Base.length(ψ::WaveFunc) = length(ψ.psi)
Base.copyto!(ψ::WaveFunc, rhs) = (copyto!(ψ.psi, rhs); ψ)
Base.iterate(ψ::WaveFunc, args...) = iterate(ψ.psi, args...)
Base.axes(ψ::WaveFunc) = axes(ψ.psi)
# 多重派发非常好,就是上面重复的重载比较蛋疼
function dump_to_file(ψ::WaveFunc, filename::String)
open(filename, "w") do file
for psi in ψ
@printf file "%le %le\n" real(psi) imag(psi)
end
end
end
为啥不让?
我看这里本质上还是 Array 那就取个别名不行么?
const WaveFunc = Vector{ComplexF64}
xfig
2020 年4 月 21 日 12:36
3
取个别名的话,那本质上就是一个东西,多重派发认不出差别
const WaveFunc = Vector{ComplexF64}
function write(psi::WaveFunc)
println("write WaveFunc")
end
function write(psi::Vector{ComplexF64})
println("write vector")
end
function main()
x = WaveFunc()
write(x)
end
main()
你觉得这个会出什么结果?
这样写后面的定义会覆盖前面的。
对就是一样的,你实际上的结构就一样。和 Matrix
一个道理
xfig:
结果要把几乎所有的数组操作都重载一遍……
julia 只通过多重派发继承方法。
所以还可以
多维用 AbstractArray{T}
julia> struct WaveFunc{T} <: AbstractVector{T} end
julia> WaveFunc(n) = Vector{ComplexF64}(undef, n)
WaveFunc
julia> WaveFunc(1)
1-element Array{Complex{Float64},1}:
1.611605853e-315 + 7.579948e-316im
julia> WaveFunc
WaveFunc
julia> WaveFunc |> typeof
UnionAll
julia> WaveFunc{ComplexF64}
WaveFunc{Complex{Float64}}
julia> WaveFunc{ComplexF64} == Vector{ComplexF64}
false
julia> f(w::WaveFunc) = println("write WaveFunc")
f (generic function with 1 method)
julia> f(w::Vector{ComplexF64}) = println("write vector")
f (generic function with 2 methods)
使用
julia> wf = WaveFunc(10)
10-element Array{Complex{Float64},1}:
1.788715027e-315 + 1.788719296e-315im
1.788719454e-315 + 1.78883044e-315im
1.788725304e-315 + 1.78872546e-315im
1.78883123e-315 + 1.788768307e-315im
1.788831547e-315 + 1.79979443e-315im
1.79981451e-315 + 0.0im
0.0 + 0.0im
0.0 + 0.0im
0.0 + 0.0im
0.0 + 0.0im
julia> size(wf)
(10,)
julia> length(wf)
10
julia> wf[1]
1.788715027e-315 + 1.788719296e-315im
julia> wf[1] = 10im
0 + 10im
julia> wf[1]
0.0 + 10.0im
julia> axes(wf)
(Base.OneTo(10),)
julia> [c.re for c = wf]
10-element Array{Float64,1}:
0.0
1.788719454e-315
1.788725304e-315
1.78883123e-315
1.788831547e-315
1.79981451e-315
0.0
0.0
0.0
0.0
xfig
2020 年4 月 22 日 10:41
5
这种写法,WaveFunc 对象还是 Array,而不是自己定义的新类型
struct WaveFunc{T} <: AbstractVector{T}
end
WaveFunc(n::Int64) = Vector{ComplexF64}(undef, n)
# 自定义针对 WaveFunc 类型的 write 函数
function write(psi::WaveFunc)
println("write WaveFunc")
end
function main()
psi::WaveFunc = WaveFunc(10)
# 以下会报错,认为 psi 是 Array{ComplexF64,1} (父类型),而非自定义的 WaveFunc 类型
write(psi) # 不会调用上面定义的 write(psi::WaveFunc)
end
main()
翻了下文档。
https://docs.juliacn.com/latest/manual/interfaces/#man-interface-array-1
size
, getindex
, setindex!
还是得手工实现,继承一下 <: AbstractArray
的好处在于在实现了这些函数的基础上无需实现其他函数就能使用。
所以并没有什么好的办法。
xfig
2020 年4 月 22 日 12:30
7
你说的文档上的情况,是确实想改变默认方法中的行为。
而我们这里,其实就是想直接用默认行为,对数组的映射方式都没变。所以这样写就真的很麻烦很冗余。
当然,从用户角度上确实没办法,连 DifferentialEquation 下面的底层库好像都是这么处理的
update_coefficients!(L::DiffEqArrayOperator,u,p,t) = (L.update_func(L.A,u,p,t); L)
setval!(L::DiffEqArrayOperator, A) = (L.A = A; L)
isconstant(L::DiffEqArrayOperator) = L.update_func == DEFAULT_UPDATE_FUNC
# propagate_inbounds here for the getindex fallback
Base.@propagate_inbounds Base.convert(::Type{AbstractMatrix}, L::DiffEqArrayOperator) = L.A
Base.@propagate_inbounds Base.setindex!(L::DiffEqArrayOperator, v, i::Int) = (L.A[i] = v)
Base.@propagate_inbounds Base.setindex!(L::DiffEqArrayOperator, v, I::Vararg{Int, N}) where {N} = (L.A[I...] = v)
Base.eachcol(L::DiffEqArrayOperator) = eachcol(L.A)
Base.eachrow(L::DiffEqArrayOperator) = eachrow(L.A)
Base.length(L::DiffEqArrayOperator) = length(L.A)
Base.iterate(L::DiffEqArrayOperator,args...) = iterate(L.A,args...)
Base.axes(L::DiffEqArrayOperator) = axes(L.A)
Base.IndexStyle(::Type{<:DiffEqArrayOperator{T,AType}}) where {T,AType} = Base.IndexStyle(AType)
Base.copyto!(L::DiffEqArrayOperator, rhs) = (copyto!(L.A, rhs); L)
Base.Broadcast.broadcastable(L::DiffEqArrayOperator) = L
Base.ndims(::Type{<:DiffEqArrayOperator{T,AType}}) where {T,AType} = ndims(AType)
ArrayInterface.issingular(L::DiffEqArrayOperator) = ArrayInterface.issingular(L.A)
抽象类型继承给我的感觉是,
不是真的想让用户去衍生出一个可以自定义的类型,
而是在已有类型的架构上,改变原有方法的行为方式
但没办法在原架构上额外添加方法
其实不用继承的概念挺好
我也更喜欢组合的思路
例如
struct WaveFunc
psi::Array{ComplexF64,1}
var1::Int # some other members
var2::Int #
WaveFunc(n::Int) = (new(Array{ComplexF64,1}(undef, n), 1, 2))
end
wf = WaveFunc(10)
println(wf.psi[4])
把 WaveFunc
认为是一个 Array{ComplexF64,1}
与两个 Int
的组合,简单又清楚
但它的一个问题是,当用到 WaveFunc
的核心数据 psi
时,不得不写成 wf.psi
。
这还只是一层组合。如果把 WaveFunc
类型作为一个成员,再合成别的新类型时,用的时候会变成 some_new_obj.wf.psi
之类
如果开发者能考虑一个语法糖,将定义的类型绑定到一个核心成员(比如用 super、key 之类的关键字),当对类型作用时,如果找不到显式定义的变量或方法,则会找对核心成员的默认操作,比如这里的 psi
对应的 Array
类型。
在使用的时候,遇上 wf[4]
,如果用户没有设置 getindex
,那就默认等价于 wf.psi[4]
,这样话会方便很多,实际上这也相当于用组合实现了继承,与 Julia 默认思路不违背。