如何在 julia 中创建一个行为类似于标准 Java Class 和 public / 私有字段和方法的 "single dispatch, object-oriented Class"
How to create a "single dispatch, object-oriented Class" in julia that behaves like a standard Java Class with public / private fields and methods
我在一本书中读到 "you can't create traditional 'classes' in julia with single-dispatch-style methods like obj.myfunc()
" ... 我认为这听起来更像是一个挑战而不是事实。
所以这是我的 JavaClass
类型,带有 public / 私有字段和方法,只是为了在 Julia 中出现这样丑陋的东西而感到震惊和恐惧,毕竟开发人员已经解决了所有问题避免它:
type JavaClass
# Public fields
name::String
# Public methods
getName::Function
setName::Function
getX::Function
getY::Function
setX::Function
setY::Function
# Primary Constructor - "through Whom all things were made."
function JavaClass(namearg::String, xarg::Int64, yarg::Int64)
# Private fields - implemented as "closed" variables
x = xarg
y = yarg
# Private methods used for "overloading"
setY(yarg::Int64) = (y = yarg; return nothing)
setY(yarg::Float64) = (y = Int64(yarg * 1000); return nothing)
# Construct object
this = new()
this.name = namearg
this.getName = () -> this.name
this.setName = (name::String) -> (this.name = name; return nothing)
this.getX = () -> x
this.getY = () -> y
this.setX = (xarg::Int64) -> (x = xarg; return nothing)
this.setY = (yarg) -> setY(yarg) #Select appropriate overloaded method
# Return constructed object
return this
end
# a secondary (inner) constructor
JavaClass(namearg::String) = JavaClass(namearg, 0,0)
end
使用示例:
julia> a = JavaClass("John", 10, 20);
julia> a.name # public
"John"
julia> a.name = "Jim";
julia> a.getName()
"Jim"
julia> a.setName("Jack")
julia> a.getName()
"Jack"
julia> a.x # private, cannot access
ERROR: type JavaClass has no field x
julia> a.getX()
10
julia> a.setX(11)
julia> a.getX()
11
julia> a.setY(2) # "single-dispatch" call to Int overloaded method
julia> a.getY()
2
julia> a.setY(2.0)
julia> a.getY() # "single-dispatch" call to Float overloaded method
2000
julia> b = JavaClass("Jill"); # secondary constructor
julia> b.getX()
0
本质上,构造函数变成了闭包,这就是创建 "private" 字段和方法/重载的方式。
有什么想法吗? ("OMG Why??? Why would you do this??"除外)
还有其他方法吗?
您可以设想任何可能会失败的场景吗?
虽然这当然不是在 julia 中创建对象和方法的惯用方式,但也没有什么可怕的错误。在任何带有闭包的语言中,您都可以像这样定义自己的 "object systems",例如,请参阅在 Scheme 中开发的许多对象系统。
在 julia v0.5 中,有一种特别巧妙的方法可以做到这一点,因为闭包会自动将捕获的变量表示为对象字段。例如:
julia> function Person(name, age)
getName() = name
getAge() = age
getOlder() = (age+=1)
()->(getName;getAge;getOlder)
end
Person (generic function with 1 method)
julia> o = Person("bob", 26)
(::#3) (generic function with 1 method)
julia> o.getName()
"bob"
julia> o.getAge()
26
julia> o.getOlder()
27
julia> o.getAge()
27
奇怪的是,您必须 return 一个函数来执行此操作,但就是这样。这得益于许多优化,例如为您确定精确字段类型的语言,因此在某些情况下我们甚至可以内联这些 "method calls"。另一个很酷的功能是函数的底线控制哪些字段是"public";那里列出的任何内容都将成为对象的一个字段。在这种情况下,您只获得方法,而不是名称和年龄变量。但是,如果您将 name
添加到列表中,那么您也可以添加 o.name
。当然,这些方法也是多种方法;您可以为 getOlder
等添加多个定义,它会像您期望的那样工作。
很好,但是正如评论中提到的,字段可能会被装箱,这很烦人。
这个问题有一个更好的解决方案。
备选方案 1(与问题中提出的方法基本相同):
# Julia
mutable struct ExampleClass
field_0
field_1
method_0
method_1
method_2
function ExampleClass(field_0, field_1)
this = new()
this.field_0 = field_0
this.field_1 = field_1
this.method_0 = function()
return this.field_0 * this.field_1
end
this.method_1 = function(n)
return (this.field_0 + this.field_1) * n
end
this.method_2 = function(val_0, val_1)
this.field_0 = val_0
this.field_1 = val_1
end
return this
end
end
ex = ExampleClass(10, 11)
ex.method_0()
ex.method_1(1)
ex.method_2(20, 22)
备选方案 2:
mutable struct ExampleClass
field_0
field_1
function ExampleClass(field_0, field_1)
this = new()
this.field_0 = field_0
this.field_1 = field_1
return this
end
end
function Base.getproperty(this::ExampleClass, s::Symbol)
if s == :method_0
function()
return this.field_0 * this.field_1
end
elseif s == :method_1
function(n)
return (this.field_0 + this.field_1) * n
end
elseif s == :method_2
function(val_0, val_1)
this.field_0 = val_0
this.field_1 = val_1
end
else
getfield(this, s)
end
end
ex = ExampleClass(10, 11)
ex.method_0()
ex.method_1(1)
ex.method_2(20, 22)
备选方案 1 看起来更好,但备选方案 2 性能更好。
我对此事进行了更深入的分析,您可以在这里查看:https://acmion.com/blog/programming/2021-05-29-julia-oop/
我在一本书中读到 "you can't create traditional 'classes' in julia with single-dispatch-style methods like obj.myfunc()
" ... 我认为这听起来更像是一个挑战而不是事实。
所以这是我的 JavaClass
类型,带有 public / 私有字段和方法,只是为了在 Julia 中出现这样丑陋的东西而感到震惊和恐惧,毕竟开发人员已经解决了所有问题避免它:
type JavaClass
# Public fields
name::String
# Public methods
getName::Function
setName::Function
getX::Function
getY::Function
setX::Function
setY::Function
# Primary Constructor - "through Whom all things were made."
function JavaClass(namearg::String, xarg::Int64, yarg::Int64)
# Private fields - implemented as "closed" variables
x = xarg
y = yarg
# Private methods used for "overloading"
setY(yarg::Int64) = (y = yarg; return nothing)
setY(yarg::Float64) = (y = Int64(yarg * 1000); return nothing)
# Construct object
this = new()
this.name = namearg
this.getName = () -> this.name
this.setName = (name::String) -> (this.name = name; return nothing)
this.getX = () -> x
this.getY = () -> y
this.setX = (xarg::Int64) -> (x = xarg; return nothing)
this.setY = (yarg) -> setY(yarg) #Select appropriate overloaded method
# Return constructed object
return this
end
# a secondary (inner) constructor
JavaClass(namearg::String) = JavaClass(namearg, 0,0)
end
使用示例:
julia> a = JavaClass("John", 10, 20);
julia> a.name # public
"John"
julia> a.name = "Jim";
julia> a.getName()
"Jim"
julia> a.setName("Jack")
julia> a.getName()
"Jack"
julia> a.x # private, cannot access
ERROR: type JavaClass has no field x
julia> a.getX()
10
julia> a.setX(11)
julia> a.getX()
11
julia> a.setY(2) # "single-dispatch" call to Int overloaded method
julia> a.getY()
2
julia> a.setY(2.0)
julia> a.getY() # "single-dispatch" call to Float overloaded method
2000
julia> b = JavaClass("Jill"); # secondary constructor
julia> b.getX()
0
本质上,构造函数变成了闭包,这就是创建 "private" 字段和方法/重载的方式。
有什么想法吗? ("OMG Why??? Why would you do this??"除外)
还有其他方法吗?
您可以设想任何可能会失败的场景吗?
虽然这当然不是在 julia 中创建对象和方法的惯用方式,但也没有什么可怕的错误。在任何带有闭包的语言中,您都可以像这样定义自己的 "object systems",例如,请参阅在 Scheme 中开发的许多对象系统。
在 julia v0.5 中,有一种特别巧妙的方法可以做到这一点,因为闭包会自动将捕获的变量表示为对象字段。例如:
julia> function Person(name, age)
getName() = name
getAge() = age
getOlder() = (age+=1)
()->(getName;getAge;getOlder)
end
Person (generic function with 1 method)
julia> o = Person("bob", 26)
(::#3) (generic function with 1 method)
julia> o.getName()
"bob"
julia> o.getAge()
26
julia> o.getOlder()
27
julia> o.getAge()
27
奇怪的是,您必须 return 一个函数来执行此操作,但就是这样。这得益于许多优化,例如为您确定精确字段类型的语言,因此在某些情况下我们甚至可以内联这些 "method calls"。另一个很酷的功能是函数的底线控制哪些字段是"public";那里列出的任何内容都将成为对象的一个字段。在这种情况下,您只获得方法,而不是名称和年龄变量。但是,如果您将 name
添加到列表中,那么您也可以添加 o.name
。当然,这些方法也是多种方法;您可以为 getOlder
等添加多个定义,它会像您期望的那样工作。
这个问题有一个更好的解决方案。
备选方案 1(与问题中提出的方法基本相同):
# Julia
mutable struct ExampleClass
field_0
field_1
method_0
method_1
method_2
function ExampleClass(field_0, field_1)
this = new()
this.field_0 = field_0
this.field_1 = field_1
this.method_0 = function()
return this.field_0 * this.field_1
end
this.method_1 = function(n)
return (this.field_0 + this.field_1) * n
end
this.method_2 = function(val_0, val_1)
this.field_0 = val_0
this.field_1 = val_1
end
return this
end
end
ex = ExampleClass(10, 11)
ex.method_0()
ex.method_1(1)
ex.method_2(20, 22)
备选方案 2:
mutable struct ExampleClass
field_0
field_1
function ExampleClass(field_0, field_1)
this = new()
this.field_0 = field_0
this.field_1 = field_1
return this
end
end
function Base.getproperty(this::ExampleClass, s::Symbol)
if s == :method_0
function()
return this.field_0 * this.field_1
end
elseif s == :method_1
function(n)
return (this.field_0 + this.field_1) * n
end
elseif s == :method_2
function(val_0, val_1)
this.field_0 = val_0
this.field_1 = val_1
end
else
getfield(this, s)
end
end
ex = ExampleClass(10, 11)
ex.method_0()
ex.method_1(1)
ex.method_2(20, 22)
备选方案 1 看起来更好,但备选方案 2 性能更好。
我对此事进行了更深入的分析,您可以在这里查看:https://acmion.com/blog/programming/2021-05-29-julia-oop/