是否可以在不使用 `eval()` 的情况下在 Julia 中实现类型工厂?
Is it possible to implement a type factory in Julia without using `eval()`?
例如,我有一个基本类型的抽象类型,我想实现一个类型工厂,在这个抽象类型下动态创建具体类型,名称(和其他类型特征)由用户输入参数给出。
abstract type AbstractFoo end
function TypeFactory(typename::String, supertype::DataType)
...
return ConcreteSubtype
end
函数 TypeFactory
采用 typename
和 supertype
参数并创建属于 supertype
且与 typename
同名的具体类型.
我知道这种 class 工厂在 Python 中实现起来并不难(例如 How can I dynamically create derived classes from a base class). In Julia, it can be imitated by using eval(parse())
to evaluate string expressions (Is it possible to create types in Julia at runtime?)。但在我看来,这个解决方案并不是面向对象意义上的真正类型工厂。是否有可能在 Julia 中有一个类型工厂,其行为类似于 OOP 语言(Python、C# 等)?
编辑 [2018 年 2 月 8 日]:
表达不清晰是我的错。我是 Julia 的新手,最近才开始用它编写我的项目。我知道不支持继承,也不想在 Julia 中解决这个问题。
来自 Python 背景,我有一些感觉 eval()
通常用于原型设计而不是生产代码。当然 Julia 是不同的,并且比纯 Python 高效得多,但是给 eval()
的代码仍然必须在运行时编译(如果我错了请纠正我)。从性能的角度来看,它的使用也有点不鼓励 (Julia, speeding up eval)。
'user input' 我的意思不仅仅是命令行输入。它们可以由用户的配置文件提供。 (也就是说,@SalchiPapa 的宏解决方案既恰当又优雅!)
Is it possible to implement a type factory in Julia without using eval()
?
您可以使用宏:
Macros provide a method to include generated code in the final body of a program. A macro maps a tuple of arguments to a returned expression, and the resulting expression is compiled directly rather than requiring a runtime eval()
call.
julia> VERSION
v"0.7.0-DEV.2098"
julia> module Factory
export @factory
macro factory(type_name::Symbol, super_type::Symbol)
# ...
return quote
struct $type_name <: $(esc(super_type))
# ...
bar
end
return $(esc(type_name))
end
end
end
Main.Factory
julia> using Main.Factory: @factory
julia> abstract type AbstractFoo end
julia> @factory ConcreteFoo AbstractFoo
ConcreteFoo
julia> foo = ConcreteFoo(42)
ConcreteFoo(42)
julia> foo.bar
42
julia> ConcreteFoo <: AbstractFoo
true
julia> supertype(ConcreteFoo)
AbstractFoo
根据@Gnimuc在评论中的理解进行编辑,使用input
:
julia> module Factory
export @factory
function input(prompt::String="")::String
print(prompt)
return chomp(readline())
end
macro factory(type_name = input("Type name: "))
AbstractT = Symbol(:Abstract, type_name)
ConcreteT = Symbol(:Concrete, type_name)
return quote
abstract type $(esc(AbstractT)) end
struct $ConcreteT <: $(esc(AbstractT))
bar
end
return $(esc(AbstractT)), $(esc(ConcreteT))
end
end
end
Main.Factory
julia> using Main.Factory: @factory
julia> @factory
Type name: Foo
(AbstractFoo, ConcreteFoo)
julia> @factory
Type name: Bar
(AbstractBar, ConcreteBar)
julia> @factory Baz
(AbstractBaz, ConcreteBaz)
julia> foo = ConcreteFoo(42)
ConcreteFoo(42)
julia> foo.bar
42
julia> ConcreteFoo <: AbstractFoo
true
julia> supertype(ConcreteFoo)
AbstractFoo
julia> @macroexpand @factory
Type name: Qux
quote
#= REPL[1]:13 =#
abstract type AbstractQux end
#= REPL[1]:14 =#
struct ConcreteQux <: AbstractQux
#= REPL[1]:15 =#
bar
end
#= REPL[1]:17 =#
return (AbstractQux, ConcreteQux)
end
julia> eval(ans)
(AbstractQux, ConcreteQux)
例如,我有一个基本类型的抽象类型,我想实现一个类型工厂,在这个抽象类型下动态创建具体类型,名称(和其他类型特征)由用户输入参数给出。
abstract type AbstractFoo end
function TypeFactory(typename::String, supertype::DataType)
...
return ConcreteSubtype
end
函数 TypeFactory
采用 typename
和 supertype
参数并创建属于 supertype
且与 typename
同名的具体类型.
我知道这种 class 工厂在 Python 中实现起来并不难(例如 How can I dynamically create derived classes from a base class). In Julia, it can be imitated by using eval(parse())
to evaluate string expressions (Is it possible to create types in Julia at runtime?)。但在我看来,这个解决方案并不是面向对象意义上的真正类型工厂。是否有可能在 Julia 中有一个类型工厂,其行为类似于 OOP 语言(Python、C# 等)?
编辑 [2018 年 2 月 8 日]:
表达不清晰是我的错。我是 Julia 的新手,最近才开始用它编写我的项目。我知道不支持继承,也不想在 Julia 中解决这个问题。
来自 Python 背景,我有一些感觉 eval()
通常用于原型设计而不是生产代码。当然 Julia 是不同的,并且比纯 Python 高效得多,但是给 eval()
的代码仍然必须在运行时编译(如果我错了请纠正我)。从性能的角度来看,它的使用也有点不鼓励 (Julia, speeding up eval)。
'user input' 我的意思不仅仅是命令行输入。它们可以由用户的配置文件提供。 (也就是说,@SalchiPapa 的宏解决方案既恰当又优雅!)
Is it possible to implement a type factory in Julia without using
eval()
?
您可以使用宏:
Macros provide a method to include generated code in the final body of a program. A macro maps a tuple of arguments to a returned expression, and the resulting expression is compiled directly rather than requiring a runtime
eval()
call.
julia> VERSION
v"0.7.0-DEV.2098"
julia> module Factory
export @factory
macro factory(type_name::Symbol, super_type::Symbol)
# ...
return quote
struct $type_name <: $(esc(super_type))
# ...
bar
end
return $(esc(type_name))
end
end
end
Main.Factory
julia> using Main.Factory: @factory
julia> abstract type AbstractFoo end
julia> @factory ConcreteFoo AbstractFoo
ConcreteFoo
julia> foo = ConcreteFoo(42)
ConcreteFoo(42)
julia> foo.bar
42
julia> ConcreteFoo <: AbstractFoo
true
julia> supertype(ConcreteFoo)
AbstractFoo
根据@Gnimuc在评论中的理解进行编辑,使用input
:
julia> module Factory
export @factory
function input(prompt::String="")::String
print(prompt)
return chomp(readline())
end
macro factory(type_name = input("Type name: "))
AbstractT = Symbol(:Abstract, type_name)
ConcreteT = Symbol(:Concrete, type_name)
return quote
abstract type $(esc(AbstractT)) end
struct $ConcreteT <: $(esc(AbstractT))
bar
end
return $(esc(AbstractT)), $(esc(ConcreteT))
end
end
end
Main.Factory
julia> using Main.Factory: @factory
julia> @factory
Type name: Foo
(AbstractFoo, ConcreteFoo)
julia> @factory
Type name: Bar
(AbstractBar, ConcreteBar)
julia> @factory Baz
(AbstractBaz, ConcreteBaz)
julia> foo = ConcreteFoo(42)
ConcreteFoo(42)
julia> foo.bar
42
julia> ConcreteFoo <: AbstractFoo
true
julia> supertype(ConcreteFoo)
AbstractFoo
julia> @macroexpand @factory
Type name: Qux
quote
#= REPL[1]:13 =#
abstract type AbstractQux end
#= REPL[1]:14 =#
struct ConcreteQux <: AbstractQux
#= REPL[1]:15 =#
bar
end
#= REPL[1]:17 =#
return (AbstractQux, ConcreteQux)
end
julia> eval(ans)
(AbstractQux, ConcreteQux)