The base library of Julia contains a method which finds the element type of an array. This dispatch is fine for instances of AbstractArray or any of its children.
eltype{T,n}(::AbstractArray{T,n}) = T
But what about if you supply a type instead of an instance? Naively, you could do this, and duck type the children:
eltype{T<:AbstractArray}(::Type{T}) = T.parameters[1] # BAD
But that relies on all the concrete implementations working that way, so isn't particularly generic.
Instead, provide an implementation for the highest type in the hierarchy:
eltype{T,n}(::Type{AbstractArray{T,n}}) = T
And for children, go one step up the type chain. If you haven't reached the highest type in the chain yet, this will recursively call itself until eventually the first dispatch matches.
eltype{T<:AbstractArray}(::Type{T}) = eltype(super(T))
In languages with algebraic data types, a common technique is to create a simple sum type—a type that is exactly one of its children, but contains no fields—and perform a pattern match to run different code depending on which type you get. It is possible to perform a similar operation with multiple dispatch.
The Options framework uses this to distinguish different ways of dealing with potentially unused options to a function.
First, declare the types:
abstract OptionsChecking
type CheckNone <: OptionsChecking; end
type CheckWarn <: OptionsChecking; end
type CheckError <: OptionsChecking; end
You can then write multiple methods to match on the type when it is appropriate to do so.
function docheck(o::Options{CheckNone},checkflag::Vector{Bool})
clearcheck(o,checkflag)
end
function docheck(o::Options{CheckWarn},checkflag::Vector{Bool})
unused, msg = docheck_common(o,checkflag)
if any(unused)
println("Warning: ",msg)
end
clearcheck(o,checkflag)
end
function docheck(o::Options{CheckError},checkflag::Vector{Bool})
unused, msg = docheck_common(o,checkflag)
clearcheck(o,checkflag) # in case it's in a try/catch block...
if any(unused)
error(msg)
end
end
eval
(and @eval
) are powerful, but sometimes one has to take care to achieve the desired aim. For example, one can define functions programmatically like this:
for (fname, val) in
((:plus2, 2),
(:plus3, 3))
@eval begin
function ($fname)(x)
x + $val
end
end
end
julia> plus2(4)
6
julia> plus3(4.1)
7.1
However, what if you want to retain a variable as a symbol? Consider this example:
julia> fname = :ddot_
ddot_
julia> dlsym(Base.libblas, fname)
Ptr{Void} @0x00007f4b6a7da720
julia> @eval begin
dlsym(Base.libblas, $fname)
end
ddot_ not defined
Why did this happen? The problem is that $fname
leads to an attempt to evaluate the symbol, and there is no value assigned to a variable named ddot_
. A way around this is to use the $(expr(:quote, varname))
syntax:
julia> @eval begin
dlsym(Base.libblas, $(expr(:quote, fname)))
end
Ptr{Void} @0x00007f4b6a7da720
Another problem can arise in creating tuple expressions. For example, consider this:
julia> argtypes = (Int32, FileOffset, Int32)
(Int32,Int64,Int32)
julia> @eval begin
function mylseek(fd, offset, ref)
cpos = ccall(:lseek, FileOffset, $argtypes, fd, offset, ref)
end
end
syntax error: ccall argument types must be a tuple; try (T,)
The compiler is not recognizing argtypes
as a tuple. The trick is to create a tuple expression: substitute ($(argtypes...),)
in place of $argtypes
above.