Julia: Override method to add functionality - julia

I would like to extend an abstract type's method in a concrete type.
I can do it with a new method but this introduces more complexity:
abstract type AbstractTask end
function complete(task::AbstractTask)
complete_concrete_task(task)
println("Done!")
end
struct Task <: AbstractTask
name::String
end
complete_concrete_task(task::Task) = println(task.name)
coding = Task("coding")
complete(coding)
In Python, I would use the super operator. Is there an equivalent in Julia?
Thanks in advance!

I would say that using dispatch the way you did introduces much less cognitive complexity than a call to super. But you can use invoke select methods of supertypes:
julia> abstract type AbstractTask end
julia> function complete(task::AbstractTask)
println("Done!")
end
complete (generic function with 1 method)
julia> struct Task <: AbstractTask
name::String
end
julia> complete(task::Task) = (println(task.name); invoke(complete, Tuple{AbstractTask}, task))
complete (generic function with 2 methods)
julia> coding = Task("coding")
Task("coding")
julia> complete(coding)
coding
Done!
Of course this requires you not to forget to add it to every subtype method, which is the additional complexity I mean.
The good thing is that invoke with a constant type parameter will be compiled away (or so I have read), so there's not even overhead from dispatch.

Related

Can you create a singleton in Julia?

I know that Julia does not have OOP but that multiple dispatch enables similar ideas. Given how seemingly contentious the use of singletons are in Python, I am wondering if there is a similar idea Julia (i.e. a struct that can only be instantiated once).
I am wondering if there's a way to have the constructor keep track of the number of times an object was instantiated with a global var or something like that? Or it's altogether not be possible?
The main way people make singletons in Julia is to define an empty struct (which means that it has 0 size), and define methods that return information for it.
struct Singleton
end
data(::Singleton) = "whatever you want it to do"
etc.
From this book, a singleton can be defined as a type without fields:
struct MySingleton end
julia> MySingleton() === MySingleton()
true
You can also use Val, which can receive any value (of bit type):
julia> Val(1) === Val(1)
true
julia> Val(:foo) === Val(:foo)
true
using Val you can write something like this:
julia> do_something(::Val{:asymbol}) = println("foo")
julia> do_something(::Val{:anothersymbol}) = println("bar")
julia> do_something(s::String) = do_something(Val{Symbol(s)})
julia> do_something("asymbol")
foo
The definition of Val is:
struct Val{x} end
So, for a more clear readability of your code, you could define your own singleton type as, for example:
struct Command{x} end

Does Julia have function decorators?

In some languages (like Python) there are function decorators which appear like macros and sit above a function definition. Decorators provide some additional functionality to the function itself.
Does Julia support this idea of function decorators in any way? Would it be possible to use macros to accomplish the same goal?
Here is one way to achieve this with a macro using ExprTools.jl:
import ExprTools
macro dec(decorator, inner_def)
inner_def = ExprTools.splitdef(inner_def)
outer_def = copy(inner_def)
fname = get(inner_def, :name, nothing)
if fname !== nothing
#assert fname isa Symbol
inner_def[:name] = Symbol(fname, :_inner)
end
outer_def[:body] = Expr(:call,
:($decorator($(ExprTools.combinedef(inner_def)))),
get(outer_def, :args, [])...,
get(outer_def, :kwargs, [])...,
)
return esc(ExprTools.combinedef(outer_def))
end
julia> _time(f) = (x...) -> (t0=time(); val=f(x...); println("took ", time()-t0, " s"); val)
_time (generic function with 1 method)
julia> #dec _time function foo(x, y)
return x + y
end
foo (generic function with 1 method)
julia> foo(1, 2)
took 9.5367431640625e-7 s
3
However, at least in my experience, this comes up surprisingly rarely in idiomatic Julia programs. I would first think about whether your specific problem might be better solved just by multiple dispatch with plain higher-order functions and traits, which will usually lend itself to better composability.
To expand the comment of #SilvioMayolo, Julia has pretty elegant macro capability, that is more general than the Python decorator.
Indeed Julia's macro take as input any expression, and the macro can hook over the Abstract Syntax Tree to return a modified expression.
The keyword you want to look for is "metaprogramming".
Here you can find the official documentation and here a convenient tutorial.

Abstract Parametric Types in Julia

Is it possible in Julia to have two structs with the same name but be assigned different types and thus be distinguishable?
I have been reading https://docs.julialang.org/en/v1/manual/types/#Parametric-Types-1 and it seems to be leading towards what I want but I can't get it to work...
In force-fields for molecular simulation there are dihedral parameters to describe torsion angles in molecules. There are different kinds for example purposes lets limit them to 2 kinds: proper and improper. I would like to have two structures, both called dihedral, but given the types "proper" and "improper". I would then have methods specific to each type to calculate the forces due to dihedrals. I think abstract parametric types get me the closest to what I want but I can't get them sorted...
abstract type proper end
abstract type improper end
struct Dihedral <: proper
ai::Int64
kparam::Vector{Float64}
end
struct Dihedral <: improper
ai:Int64
kparam::Float64
end
The above code does not work... I have tried using
abstract type dihedral end
abstract type proper <: dihedral end
abstract type improper <: dihedral end
struct Dihedral <: dihedral{proper}
...
end
struct Dihedral <: dihedral{improper}
...
end
But I always get in trouble for redefining Dihedral
ERROR: LoadError: invalid redefinition of constant Dihedral
Stacktrace:
[1] top-level scope at none:0
My thought is that I can add in more types of dihedrals and all i need to do is also add in their methods and the simulation will automatically use the new dihedral.methods. If I try making structs of different names, then I start having to use if statements to direct the program to the correct structure and later to the correct methods... This is what I want to avoid i.e.,
if dihedraltype == "proper"
struct_proper(...)
elseif dihedraltype =="improper"
struct_improper()
elseif dihedraltype == "newStyle"
struct_newStyle()
end
using this method I would have to find all places in my code where I call dihedral and add in the new type... dihedral is just an example, there are many "phenomenas" that have different methods for calculating the phenomena.
I would use the following approach if you want to use a parametric type:
abstract type DihedralType end
struct Proper <: DihedralType
ai::Int64
kparam::Vector{Float64}
end
struct Improper <: DihedralType
ai::Int64
kparam::Float64
end
struct Dihedral{T<:DihedralType}
value::T
end
Dihedral(ai::Int64, kparam::Vector{Float64}) = Dihedral(Proper(ai, kparam))
Dihedral(ai::Int64, kparam::Float64) = Dihedral(Improper(ai, kparam))
and now you can write e.g.:
Dihedral(1, [1.0, 2.0])
Dihedral(1, 1.0)
The parameter of type Dihedral passes you information what kind of the object you are working with. Then some methods may be generic and call Dihedral e.g.:
julia> ai(d::Dihedral) = d.value.ai
ai (generic function with 1 method)
julia> ai(Dihedral(1, 1.0))
1
julia> ai(Dihedral(1, [1.0, 2.0]))
1
julia> kparam(d::Dihedral) = d.value.kparam
kparam (generic function with 1 method)
julia> kparam(Dihedral(1, 1.0))
1.0
julia> kparam(Dihedral(1, [1.0, 2.0]))
2-element Array{Float64,1}:
1.0
2.0
and some may be type parameter specific:
julia> len(d::Dihedral{Proper}) = length(kparam(d))
len (generic function with 1 method)
julia> len(Dihedral(1, [1.0, 2.0]))
2
julia> len(Dihedral(1, 1.0))
ERROR: MethodError: no method matching len(::Dihedral{Improper})
Closest candidates are:
len(::Dihedral{Proper}) at REPL[15]:1
Stacktrace:
[1] top-level scope at none:0
Does this approach give you what you have expected?
EDIT
Actually maybe an even simpler approach may be enough for you (depending on the use case). Just define:
abstract type AbstractDihedral end
struct Proper <: AbstractDihedral
ai::Int64
kparam::Vector{Float64}
end
struct Improper <: AbstractDihedral
ai::Int64
kparam::Float64
end
and then implement methods in terms of DihedralType if they are generic for all dihedrals and if you want to add some specific method to a given concrete type just add this method with this concrete type in the signature. For example:
ai(d::AbstractDihedral) = d.ai
kparam(d::AbstractDihedral) = d.kparam
len(d::Proper) = length(d.kparam) # will not work for Improper
In this approach you do not need to use a parametric type. The difference is that in the parametric type approach you can extract out the parameters that are the same for all dihedrals to the "parent" struct and define only dihedral specific parameters in the "wrapped" struct. In the second approach you have define all fields every time for each struct.

Parametric return types

I'm trying to understand how parametric types work in Julia.
Suppose I have a function foo, which takes an Integer (i.e. non-parametric type), and I want to assert this function to return a vector, whose elements are subtypes of Real. From documentation I deduced that it should be implemented as follows:
function foo{T<:Real}(n::Integer)
# come code to generate variable vec
return vec::Vector{T}
end
But it is not working. What am I doing wrong?
Parametric types are used to parameterise the inputs to a function, not the outputs. If your function is type-stable (read about what that means in the performance tips), then the output of the function can be automatically inferred by the compiler based on the input types, so there should be no need to specify it.
So your function might look something like this:
function foo{T<:Real}(n::T)
#some code to generate vec
return(vec)
end
For example, you can generate a vector of the appropriate type within the body of your function, e.g. something like this:
function f1{T<:Number}(x::T)
y = Array(T, 0)
#some code to fill out y
return(y)
end
But note that T must appear in the argument definitions of the inputs to the function, because this is the purpose of parametric types. You'll get an error if T does not appear in the description of the inputs. Something like this doesn't work:
f{T<:Number}(x::Vector) = x
WARNING: static parameter T does not occur in signature for f at none:1.
The method will not be callable.
f (generic function with 1 method)
But this does, and is type stable, since if the input types are known, then so are the outputs
f{T<:Number}(x::Vector{T}) = x
A parametric method or a constructor of a parametric type
A function is called using the traditional parenthesis syntax:
julia> f(2,3)
5
The above rule is true for both parametric and non-parametric methods.
So, one should not try to call a parametric method like: f{atype}(2,3). I think the source of this mall-usage could be the syntax of calling a parametric type constructor:
julia> typeof(Array{Int})
DataType
julia> Array{Int}(2)
2-element Array{Int32,1}:
57943068
72474848
But in the case of a parametric method:
julia> same_type{T}(x::T, y::T) = true;
julia> typeof(same_type)
Function
julia> same_type{Int}
ERROR: TypeError: Type{...} expression: expected Type{T}, got Function
So with this introduction it has become clear that something like:
function foo{T<:Real}(n::Integer)
.....
end
would be useless, because the value of T won't be determined from caller the arguments.
Make benefit of parametric methods
julia> same_type{T}(x::T, y::T) = true;
julia> same_type(x,y) = false;
julia> same_type(1, 2)
true
julia> same_type(1, 2.0)
false
The main purpose of parametric methods is to let dispatch find the right method to call with respect to argument types when calling a function, but also it has the following idiomatic side effect:
Method type parameters are not restricted to being used as the types
of parameters: they can be used anywhere a value would be in the
signature of the function or body of the function.
julia> mytypeof{T}(x::T) = T
mytypeof (generic function with 1 method)
julia> mytypeof(1)
Int64
Now, let's go back to the main problem of:
How to write a method with Parametric return types?
If your method is already parametric, you could use the parameter value as return type as in the above mytypeof sample or e.g:
julia> newoftype{T}(x::T,n) = T(n)
newoftype (generic function with 1 methods)
julia> newoftype([1,2],4)
4-element Array{Int32,1}:
57943296
72475184
141142104
1970365810
But making return type of a method, a function of arguments value is a straightforward functionality and could be simply done without need to a parametric method. really many of typical methods do this job e.g:
julia> Array(Int,4)
4-element Array{Int32,1}:
126515600
72368848
72474944
0
julia> Array(Float64,4)
4-element Array{Float64,1}:
-2.122e-314
0.0
5.12099e-292
5.81876e-292
It could be done using a argument type of Type e.g:
julia> myarray(T::Type,n::Int)=Array(T,n);

Can I build a parameterless constructor for a parametric type in an outer constructor?

In order to instantiate a type like x = MyType{Int}()
I can define a inner constructor.
immutable MyType{T}
x::Vector{T}
MyType() = new(T[])
end
Is it possible to achieve the same objective using an outer constructor?
This can be done using the following syntax:
(::Type{MyType{T}}){T}() = MyType{T}(T[])
The thing in the first set of parentheses describes the called object. ::T means "of type T", so this is a definition for calling an object of type Type{MyType{T}}, which means the object MyType{T} itself. Next {T} means that T is a parameter of this definition, and a value for it must be available in order to call this definition. So MyType{Int} matches, but MyType doesn't. From there on, the syntax should be familiar.
This syntax is definitely a bit fiddly and unintuitive, and we hope to improve it in a future version of the language, hopefully v0.6.
I may be wrong, but if you cannot build parameterless function like this:
julia> f{T}() = show(T)
WARNING: static parameter T does not occur in signature for f at none:1.
The method will not be callable.
f (generic function with 1 method)
therefore you won't be able to do this:
julia> immutable MyType{T}
x::Vector{T}
end
julia> MyType{T}() = MyType{T}(T[])
WARNING: static parameter T does not occur in signature for call at none:1.
The method will not be callable.
MyType{T}
julia> x = MyType{Int}()
ERROR: MethodError: `convert` has no method matching convert(::Type{MyType{Int64}})
...
Every outer constructor is also a function.
You can say
f(T::Type) = show(T)
and also
MyType(T::Type) = MyType(T[])
But julia needs to see the type in the call to know which you want.

Resources