jlapeyre / DotCall.jl

MIT License
6 stars 0 forks source link

DotCall

Build Status Coverage Aqua QA JET QA

This package facilitates using the "dot notation" for calling methods that is common in class-based object oriented langauges. It provides a macro @dotcallify which allows you to call an existing method.

f(a::A, args...)

like this

a.f(args...)

Example

module Amod

using DotCall: @dotcallify

struct A
  x::Int
end

@dotcallify A (f, g)

f(a::A, x, y) = a.x + x + y
g(a::A) = a.x

end # module Amod

Then you can write either Amod.f(a, 1, 2) or a.f(1, 2).

Performance

Benchmarking has not revealed any runtime performance penalty in calling a method using dot notation.

For example the script examples/smalltest.jl

push!(LOAD_PATH, "./test/MyAs/", "./test/MyBs/")
using MyAs
using BenchmarkTools
const y2 = MyA(5)
@btime [y2.sx(i) for i in 1:100];
@btime [MyAs.sx(y2, i) for i in 1:100];

Prints:

  40.218 ns (1 allocation: 896 bytes)
  40.025 ns (1 allocation: 896 bytes)

Package that uses DotCall

DotCall.jl was previously named CBOOCall.jl. But only the name changed, not the API. (other than minor updates and fixes)

The package QuantumCircuits.jl uses CBOOCall.jl.

The main motivation is to make it easy to call many functions with short names without bringing them into scope. For example s.x(1), s.y(3), s.z(3), etc. We want to do this without claiming x, y, z, among others. This is all the package does.

For example, in building quantum computing circuits programmatically, people really want to write circ.x(1) to add an X gate on wire 1. You could do

using QCircuit: add!  # ok to import this probably

add!(circ, QCircuit.x, 1) # But, I really don't want to import x, y, z, etc.

Here is an example from an application

@dotcallify QCircuit (x, sx, y, z, h, cx, s, sdg, t, tdg, u, u3, rx, ry, rz, rzx, u4, barrier, measure)

Usage

Functions and macros

See doc strings for the following macros and methods.

@dotcallify, add_dotcalls, is_dotcallified, whichmodule, dotcallified_properties.

@dotcallify doc string

@dotcallify(Type_to_dotcallify, (f1, f2, fa = Mod.f2...), callmethod=nothing, getproperty=getfield)

Allow functions of the form f1(s::Type_to_dotcallify, args...) to also be called with s.f1(args...) with no performance penalty.

callmethod and getproperty are keyword arguments.

If an element of the Tuple is an assignment sym = func, then sym is the property that will call func. sym must be a simple identifier (a symbol). func is not required to be a symbol. For example myf = Base._unexportedf.

If callmethod is supplied, then s.f1(args...) is translated to callmethod(s, f1, args...) instead of f1(s, args...).

@dotcallify works by writing methods (or clobbering methods) for the functions Base.getproperty and Base.propertynames.

getproperty must be a function. If supplied, then it is called, rather than getfield, when looking up a property that is not on the list of functions. This can be useful if you want further specialzed behavior of getproperty.

@dotcallify must by called after the definition of Type_to_dotcallify, but may be called before the functions are defined.

If an entry is not function, then it is returned, rather than called. For example @dotcallify MyStruct (y=3,). Callable objects meant to be called must be wrapped in a function.

Examples

module Amod
import DotCall

struct A
    x::Int
end

DotCall.@dotcallify A (w, z)

w(a::A, y) = a.x + y
z(a::A, x, y) = a.x + y + x
end # module
julia> a = Amod.A(3);

julia> Amod.w(a, 4) == a.w(4) == 7
true

julia> DotCall.whichmodule(a)
Main.Amod

julia> DotCall.dotcallified_properties(a)
(w = Main.Amod.w, z = Main.Amod.z)
@dotcallify(Type_to_dotcallify, (f1, f2, ...))

@dotcallify(Type_to_dotcallify, (f1, f2, ...), callmethod=nothing, getproperty=getfield)