关于类:是否可以在不使用`eval()`的情况下在Julia中实现类型工厂?

Is it possible to implement a type factory in Julia without using `eval()`?

例如,我有一个基本类型的抽象类型,我想实现一个类型工厂,它动态地在这个抽象类型下创建具体的类型,名称(和其他类型特征)由用户输入参数提供。

1
2
3
4
5
6
abstract type AbstractFoo end

function TypeFactory(typename::String, supertype::DataType)
    ...
    return ConcreteSubtype
end

函数TypeFactory接受typenamesupertype参数,并创建一个属于supertype的具体类型,该类型与typename同名。

我知道这种类工厂在Python中实现并不太困难(例如,如何从基类动态创建派生类)。在Julia中,可以通过使用eval(parse())来计算字符串表达式来模拟(是否可以在运行时在Julia中创建类型?)但是在我看来,这个解决方案在面向对象的意义上并不是真正的类型工厂。是否可能在Julia中有一个类型工厂,其行为与OOP语言(Python、C_等)中的类型工厂类似?

编辑[2018年2月8日]:

我不善于表达清楚。我刚认识茱莉亚,最近刚开始用它编写我的项目。我知道继承权是不受支持的,也不打算在茱莉亚继承。

从python的背景来看,我觉得eval()通常用于原型设计,而不是用于生产代码。当然,Julia是不同的,而且比纯Python高效得多,但是提供给eval()的代码仍然必须在运行时编译(如果我错了,请纠正我)。从性能的角度来看,它的使用也被分为不鼓励(Julia,加速了eval)。

"用户输入"并不仅仅指命令行输入。它们可以由用户的配置文件提供。(这就是说,@salchipapa的宏观解决方案既贴切又优雅!)


Is it possible to implement a type factory in Julia without using eval()?

可以使用宏:

  • https://docs.julialang.org/en/stable/manual/metaprogramming/man-macros-1

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

根据评论中的@gnimu理解,使用input编辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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)