Reputation: 130
For example, I have an abstract type for the base type, and I want to implement a type factory that dynamically creates concrete types under this abstract type, with names (and other type traits) given by user input arguments.
abstract type AbstractFoo end
function TypeFactory(typename::String, supertype::DataType)
...
return ConcreteSubtype
end
The function TypeFactory
takes typename
and supertype
arguments and creates a concrete type that belongs to the supertype
and has the same name as typename
.
I know this kind of class factory is not too difficult to implement in Python (e.g., 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?). But it looks to me that this solution is not a true type factory in an object-oriented sense. Is it possible to have a type factory in Julia that behaves like those in OOP languages (Python, C#, etc.)?
Edit [8 Feb 2018]:
My bad for not expressing things clearly. I'm new to Julia and had just recently started coding my project in it. I knew that inheritance is not supported and did not meant to get around with that in Julia.
Coming from a Python background, I had some feeling that eval()
is usually meant for prototyping but not for production code. Of course Julia is different and is far more efficient than pure Python, but the code given to eval()
still has to be compiled at the runtime (correct me if I'm wrong). And its use is sorted of discouraged too, from the perspective of performance (Julia, speeding up eval).
And by 'user input' I didn't mean solely command-line input. They could be supplied by a user's config file. (That being said, @SalchiPapa's macro solution is both apt and elegant!)
Upvotes: 4
Views: 557
Reputation: 7893
Is it possible to implement a type factory in Julia without using
eval()
?
You could use a macro:
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
Edit according to @Gnimuc understanding in the comments, using 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)
Upvotes: 3