4

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!)

3
  • What do you mean by "not a true type factory in an object-oriented sense"? IIUC, you're looking for something like type() in Python, to me, that's exactly the createTypeAST+eval in the approach 2 in the linked answer. It's a helper function that allows users to dynamically create a new type at runtime, I'm wondering how does this connect to OOP. Commented Feb 8, 2018 at 2:31
  • @Gnimuc I think he is referring to inheritance. Commented Feb 8, 2018 at 6:38
  • 1
    @Gnimuc Thanks for asking. I was not being clear. I think I wanted it to be function-like, which returns the concrete type, not an expression evaluated with eval(). One reason is that precompiled code is faster than code that is eval-ed at the runtime. Also, I think writing functions (and macros) in production code is more elegant, though it may just be a personal preference. Commented Feb 8, 2018 at 23:00

1 Answer 1

3

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)
Sign up to request clarification or add additional context in comments.

3 Comments

As the OP said "with names given by user input arguments", maybe we need to add type_name = readline(STDIN) |> Symbol in the macro. But I think that's kinda runtime info, is it the best practice to insert the readline function into the macro implementation?
I'm curious how Python implements its type() function.
@Gnimuc I've updated my answer, taking into account your comments.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.