Takeshi
Takeshi

Reputation: 130

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

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

Answers (1)

HarmonicaMuse
HarmonicaMuse

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

Related Questions