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

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

Is it possible to implement a type factory in Julia without using eval()?
You could use a macro:
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.
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)

Related

Why Dot Notation For "Method" Variables and Not For Others?

Let's say I create the following class in Python:
class SomeClass(object):
variable_1 = "Something"
def __init__(self, variable_2):
self.variable_2 = variable_2 + self.variable_1
I do not understand why variables lying outside the method (should I say function?) definition, but inside the body of class, are not named using the dot notation like variable_1, and variables inside the method definition are named - for example: self.variable_2 - or referenced - for example: self.variable_1 using the dot notation. Is this just a notation specific to Python or is this for some other reason?
It is not something you will find in C++ or java. It is not only a notation. See the documentation documentation:
classes partake of the dynamic nature of Python: they are created at
runtime, and can be modified further after creation.
And it is the same for functions.
Here is a valid way to define a class:
def f1(self, x, y):
return min(x, x+y)
class C:
text = 'hello world'
f = f1
def g(self):
return self.text
h = g
f, gand hare attributes of C. To make it work, it is important to pass the object as an argument of the functions. By convention, it is named self.
This is very powerful. It allows to really use a function as an attribute. For example:
c = C()
class B:
text = 'hello word 2'
g = c.g
The call to B.g() will return 'hello word 2'. It is only possible because of the use of self.
For the record, you may also read the documentation of global and of the variable scope in general.

Declaring types based on types of other values

At the moment, I have the following Scala code using the Scala Graph library:
val g = Graph.from(nodes, edges)
// nodes: List[Table]; edges: List[LkUnDiEdge[Table,String]]
// inferred - g: Graph[Table, LkUnDiEdge]
...
def nodeTransformer(innerNode: Graph[Table, LkUnDiEdge]#NodeT) = {
val node = innerNode.value
Some( root,
DotNodeStmt(node.name, style(node)) )
}
You may notice the parameter to nodeTransformer is an inner type of the type of g. Similar code repeats the concrete Graph type or some of its type parameters in other places in the codebase.
Type inference does not work in all places, and I'd like a way to express this type dependency without repeating the same explicit types throughout the code. As an example, the C++ typeof operator would allow me to rewrite the function as follows:
def nodeTransformer(innerNode: typeof(g)#NodeT) = {
...
}
What is the sane way to express such static type dependencies in Scala when type inference fails?
What about:
def nodeTransformer(innerNode: g.NodeT) = {
...
}
In contrast with C++ typeof, you should pass innerNode exactly for g, but not any other Graph instance (even with same type): What is meant by Scala's path-dependent types?
P.S. To extract type from generic (without type member inside) - How to capture T from TypeTag[T] or any other generic in scala?

Contravariance vs Covariance in Scala

I just learned Scala. Now I am confused about Contravariance and Covariance.
From this page, I learned something below:
Covariance
Perhaps the most obvious feature of subtyping is the ability to replace a value of a wider type with a value of a narrower type in an expression. For example, suppose I have some types Real, Integer <: Real, and some unrelated type Boolean. I can define a function is_positive :: Real -> Boolean which operates on Real values, but I can also apply this function to values of type Integer (or any other subtype of Real). This replacement of wider (ancestor) types with narrower (descendant) types is called covariance. The concept of covariance allows us to write generic code and is invaluable when reasoning about inheritance in object-oriented programming languages and polymorphism in functional languages.
However, I also saw something from somewhere else:
scala> class Animal
 defined class Animal
scala> class Dog extends Animal
 defined class Dog
scala> class Beagle extends Dog
 defined class Beagle
scala> def foo(x: List[Dog]) = x
 foo: (x: List[Dog])List[Dog] // Given a List[Dog], just returns it
scala> val an: List[Animal] = foo(List(new Beagle))
 an: List[Animal] = List(Beagle#284a6c0)
Parameter x of foo is contravariant; it expects an argument of type List[Dog], but we give it a List[Beagle], and that's okay
[What I think is the second example should also prove Covariance. Because from the first example, I learned that "apply this function to values of type Integer (or any other subtype of Real)". So correspondingly, here we apply this function to values of type List[Beagle](or any other subtype of List[Dog]). But to my surprise, the second example proves Cotravariance]
I think two are talking the same thing, but one proves Covariance and the other Contravariance. I also saw this question from SO. However I am still confused. Did I miss something or one of the examples is wrong?
A Good recent article (August 2016) on that topic is "Cheat Codes for Contravariance and Covariance" by Matt Handler.
It starts from the general concept as presented in "Covariance and Contravariance of Hosts and Visitors" and diagram from Andre Tyukin and anoopelias's answer.
And its concludes with:
Here is how to determine if your type ParametricType[T] can/cannot be covariant/contravariant:
A type can be covariant when it does not call methods on the type that it is generic over.
If the type needs to call methods on generic objects that are passed into it, it cannot be covariant.
Archetypal examples:
Seq[+A], Option[+A], Future[+T]
A type can be contravariant when it does call methods on the type that it is generic over.
If the type needs to return values of the type it is generic over, it cannot be contravariant.
Archetypal examples:
`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]`
Regarding contravariance,
Functions are the best example of contravariance
(note that they’re only contravariant on their arguments, and they’re actually covariant on their result).
For example:
class Dachshund(
name: String,
likesFrisbees: Boolean,
val weinerness: Double
) extends Dog(name, likesFrisbees)
def soundCuteness(animal: Animal): Double =
-4.0/animal.sound.length
def weinerosity(dachshund: Dachshund): Double =
dachshund.weinerness * 100.0
def isDogCuteEnough(dog: Dog, f: Dog => Double): Boolean =
f(dog) >= 0.5
Should we be able to pass weinerosity as an argument to isDogCuteEnough? The answer is no, because the function isDogCuteEnough only guarantees that it can pass, at most specific, a Dog to the function f.
When the function f expects something more specific than what isDogCuteEnough can provide, it could attempt to call a method that some Dogs don’t have (like .weinerness on a Greyhound, which is insane).
That you can pass a List[Beagle] to a function expecting a List[Dog] is nothing to do with contravariance of functions, it is still because List is covariant and that List[Beagle] is a List[Dog].
Instead lets say you had a function:
def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int
This function counts all the legs in a list of dogs. It takes a function that accepts a dog and returns an int representing how many legs this dog has.
Furthermore lets say we have a function:
def countLegsOfAnyAnimal(a: Animal): Int
that can count the legs of any animal. We can pass our countLegsOfAnyAnimal function to our countDogsLegs function as the function argument, this is because if this thing can count the legs of any animal, it can count legs of dogs, because dogs are animals, this is because functions are contravariant.
If you look at the definition of Function1 (functions of one parameter), it is
trait Function1[-A, +B]
That is that they are contravariant on their input and covariant on their output. So Function1[Animal,Int] <: Function1[Dog,Int] since Dog <: Animal
Variance is used to indicate subtyping in terms of Containers(eg: List). In most of the languages, if a function requests object of Class Animal, passing any class that inherits Animal(eg: Dog) would be valid. However, in terms of Containers, these need not be valid.
If your function wants Container[A], what are the possible values that can be passed to it? If B extends A and passing Container[B] is valid, then it is Covariant(eg: List[+T]). If, A extends B(the inverse case) and passing Container[B] for Container[A] is valid, then it is Contravariant. Else, it is invariant(which is the default). You could refer to an article where I have tried explaining variances in Scala
https://blog.lakshmirajagopalan.com/posts/variance-in-scala/

Higher-kinded type constructors with and without '_'

This signature declares higher-kinded type:
case class MyContainer[A, M[_]](el: M[A])
Now, I can create instance of it:
scala> val mc1 = MyContainer[Int, Option](Some(3))
mc1: MyContainer[Int,Option] = MyContainer(Some(3))
I can also declare MyContainer as:
case class MyContainer[A, M[A]](el: M[A])
which produces same instance as mc1:
mc1: MyContainer[Int,Option] = MyContainer(Some(3))
What is the difference between these approaches and which should be used when?
According to the language specification (§4.4 Type Parameters), these are equivalent:
[M[X], N[X]]
[M[_], N[_]] // equivalent to previous clause
This paragraph describes the reasoning behind this syntax:
Higher-order type parameters (the type parameters of a type parameter t) are only visible in their immediately surrounding parameter clause (possibly including clauses at a deeper nesting level) and in the bounds of t. Therefore, their names must only be pairwise different from the names of other visible parameters. Since the names of higher-order type parameters are thus often irrelevant, they may be denoted with a _, which is nowhere visible.
Note that the A in M[A] is ignored in this case. It could be T and it would work as well:
scala> case class MyContainer[A, M[T]](el: M[A])
defined class MyContainer
scala> val mc1 = MyContainer[Int, Option](Some(3))
mc1: MyContainer[Int,Option] = MyContainer(Some(3))
To prevent confusion I would always use [_], or at least not reuse the names.

Is it possible to defer macro expansion until abstract type is bound to specific type

I might be using incorrect terminology, but here is example what I would like to achieve. Lets say I have the following macro:
def generateField[T]: AnyRef =
macro generateFieldImpl[T]
def generateFieldImpl[T: c.AbsTypeTag](c: Context): c.Expr[AnyRef] = {
/**
* here I'm looking at the type T by reflection to see now many members it has
* and based on that I'm generating TupleN[Array[Byte], ...](null, ...)
* where N is number of members in class represented by type T
*/
}
I'm planning to use only case classes as T.
When I use this macro with case class it works great, but now I'd like to add a level of abstraction:
trait WithGeneratedField[T] {
val _myField = generateField[T]
}
The problem I'm having is that macro gets expanded when trait is being declared and at that point T is known as an abstract type 'T'. Is there any way to defer macro expansion until I mix-in this trait with something concrete? For instance:
case class MyClass(a: String, b: Int) extends WithGeneratedField[MyClass]
At the end my goal is to use macro to add a generated field to a case class. Maybe there is a better way of doing that?
I would be surprised if this turns possible in 2.10.0.
With macro types or macro annotations (http://scalamacros.org/future.html) it should be pretty straightforward. We're going to work on these new flavors of macros as soon as we release 2.10.0-final, but it's hard to predict the ETA. Maybe winter of 2012 - spring of 2013.