This julia package provides interception methods for expressions like
mydict.mykey = 42
(see JuliaLang/julia#1974)
It is implemented as a macro which modifies the ast of a given expression (see https://github.com/sneusse/DotOverload.jl/blob/master/src/DotOverload.jl )
So I can copy/paste matplotlib examples, as I have no clue how that works and I'm tired of writing brackets.
Basics
using DotOverload
DotOverload.getMember(t::Dict, k) = t[k]
DotOverload.setMember!(t::Dict, k, v) = t[k] = v
mydict = Dict()
@dotted mydict.mykey = 42
@dotted println(mydict.mykey) # prints 42
For bigger chunks of code I would recommend something like this:
using DotOverload
DotOverload.getMember(t::Dict, k) = t[k]
DotOverload.setMember!(t::Dict, k, v) = t[k] = v
mydict = Dict()
@dotted function doStuffWithDict(stuff)
mydict.mykey = stuff * 2
mydict.mykey += mydict.mykey
return mydict.mykey /= 5
end
println(Int(round(doStuffWithDict(53)))) # prints 42
If you want to go cracy all the way (like I prefer to do) you might want to try this:
# we also need this before defining a module
using DotOverload
@dotted module PlotTest
using DotOverload
using PyCall
using PyPlot
DotOverload.getMember(t::PyObject, k) = t[k]
DotOverload.setMember!(t::PyObject, k, v) = t[k] = v
DotOverload.getMember(t::Figure, k) = t[k]
DotOverload.setMember!(t::Figure, k, v) = t[k] = v
plt.ioff()
fig, ax = plt.subplots()
line, = ax.plot([],[])
plt.ion()
function init()
fig.show()
fig.canvas.draw()
plt.axis([0, 2*π, -2, 2])
end
function doStuff()
x = linspace(0,2*pi,1000);
y = sin(3*x + 4*cos(2*x))
line.set_xdata(x)
line.set_ydata(y)
ax.draw_artist(line)
fig.canvas.update()
end
end #module
# usage:
# using PlotTest
# PlotTest.init()
# PlotTest.doStuff()
Advanced
What if we want "dispatch on values" ?
using DotOverload
type Field{TName}
end
DotOverload.getMember(t::Dict, ::Type{Field{:a}}) = "a is always 42"
DotOverload.getMember{T}(t::Dict, f::Type{Field{T}}) = t[T]
DotOverload.getMember(t::Dict, f::Symbol) = DotOverload.getMember(t::Dict, Field{f})
DotOverload.setMember!(t::Dict, f::Symbol, v) = t[f] = v
@dotted begin
d = Dict()
d.b = 42
println(d.a) # note that d.a is not defined yet!
# prints "a is always 42"
println("b is: $(d.b) - this time")
# prints "b is: 42 - this time"
d.a = 43
d.b = 43
println(d.a)
# prints "a is always 42"
println("b is: $(d.b) - this time")
# prints "b is: 43 - this time"
end
More crazyness
Providing setters, getters and attached methods for your custom types (not properly implemented!)
using DotOverload
type Field{TName}
end
macro getter(ex::Expr)
# we need more sanity checks!
if (ex.head != :(=))
error("Nope")
end
# T is our type
T = ex.args[1].args[1]
# F is our property
F = ex.args[1].args[2]
# B is the block of code
B = ex.args[2]
# create method for field access
@eval DotOverload.getMember(t::$T, f::Symbol) = DotOverload.getMember(t::$T, Field{f})
# create method for the _specific_ field access
mygetter = @eval DotOverload.getMember(self::$T, ::Type{Field{$F}}) = $B
# create catch-all method to support access without a property
@eval DotOverload.getMember{Q}(t::$T, ::Type{Field{Q}}) = Base.getfield(t, Q)
return mygetter
end
macro setter(ex::Expr)
# we need more sanity checks!
if (ex.head != :(=))
error("Nope")
end
# T is our type
T = ex.args[1].args[1]
# F is our property
F = ex.args[1].args[2]
# B is the block of code
B = ex.args[2]
# create method for field access
@eval DotOverload.setMember!(t::$T, f::Symbol, value) = DotOverload.setMember!(t::$T, Field{f}, value)
# create method for the _specific_ field access
mysetter = @eval DotOverload.setMember!(self::$T, ::Type{Field{$F}}, value) = $B
# create catch-all method to support access without a property
@eval DotOverload.setMember!{Q}(t::$T, ::Type{Field{Q}}, value) = Base.setfield!(t, Q, value)
return mysetter
end
macro method(ex::Expr)
# we need more sanity checks!
if (ex.head != :(=))
error("Nope")
end
# T is our type
T = ex.args[1].args[1]
# F is our property
# FN is the name of our property
F = ex.args[1].args[2]
FN = F.args[1]
# P is our parameter or tuple of parameters
# B is the block of code
P = ex.args[2].args[1]
B = ex.args[2].args[2]
if (isa(P, Expr))
P = P.args
else
P = [P]
end
# create method for field access
@eval DotOverload.getMember(t::$T, f::Symbol) = DotOverload.getMember(t::$T, Field{f})
# create name for dispatchable function
funname = Symbol("_method_"*string(T)*"_"*string(FN))
# bind the function to the property
mymethod = eval( Expr(:function, Expr(:call, funname, P...), B) )
mymethod = @eval DotOverload.getMember(self::$T, ::Type{Field{$F}}) = $mymethod
# create catch-all method to support access without a property
@eval DotOverload.getMember{Q}(t::$T, ::Type{Field{Q}}) = Base.getfield(t, Q)
return mymethod
end
type MyType
_val::Int64
end
@getter MyType.Value = Int64(self._val / 2)
@setter MyType.Value = self._val = Int64(value * 2)
@method MyType.multi = function()
println("NOSTUFF!")
end
@method MyType.multi = function(stuff)
println("some stuff: $stuff")
end
@method MyType.multi = function(stuff, more)
println("first stuff: $stuff and more stuff: $more")
end
o = MyType(3)
@dotted begin
o = MyType(0)
o.Value = 42
println("o._val is $(o._val)")
println("o.Value is $(o.Value)")
o.multi()
o.multi(1)
o.multi(1,2)
end
See tests.
I'm not sure if I caught all the special cases (well, I only caught one so far):
# left side of '=' expression will be ignored -> something.somefunc(args)
# right side will become -> 'getMember(args, :field) * 2'
something.somefunc(args) = args.field * 2
In Julia v0.4 it is possible to define the getMember
overload after the block using the method. In v0.5 this does not seem to work and I'm not sure why (only tested the REPL)