3

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

Takeshi
  • 120
  • 1
  • 6
  • 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. – Gnimuc Feb 08 '18 at 02:31
  • @Gnimuc I think he is referring to inheritance. – HarmonicaMuse Feb 08 '18 at 06: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. – Takeshi Feb 08 '18 at 23:00

1 Answers1

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)
HarmonicaMuse
  • 7,633
  • 37
  • 52
  • 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? – Gnimuc Feb 08 '18 at 06:57
  • I'm curious how Python implements its `type()` function. – Gnimuc Feb 08 '18 at 06:59
  • 1
    @Gnimuc I've updated my answer, taking into account your comments. – HarmonicaMuse Feb 08 '18 at 15:52