User Tools

Site Tools


fundispatch
snippet.juliarepl
julia> pkgchk( [ "julia" => v"1.0.2" ] )

Function Argument Dispatch

  • Recall that chapter Functions described how to interrogate functions.

Julia is built around the idea that one may want to define many functions with the same name but differently typed arguments. Ergo, a function like sqr (square) could be defined one way for integers, another way for Float64, another for Float32, another for real/imaginary numbers, and yet another for a user structure named BuildingVector (or whatever). The compiler will search for the most appropriately matched function, and invoke it.

This is called generic programming, and nowadays prominently employed by the C++ STL. Originally, C++ was an object-oriented programming language. With the arrival of its STL, everything changed. One could now easily design functions that would work, e.g., with stacks of objects, regardless of what type the objects were. One did not have to write one search function for strings, another for integers, and so on (or worse, pass around a void pointer to arbitrary memory with the hope that the program would remember what type the pointer was about). While generic programming was a “bolt-on” to C++, it was a first-order design feature for Julia.

Writing Generic or Specific Functions?

When should user functions be generic (applicable to any type that the user throws at them), and when should they be specific (applicable only to specific types)? My answer here is that they should be as specific as required, but no more. This is because it helps catch errors earlier and it helps the compiler create faster code.

For example, on one end of the spectrum, your program could define a generic function and it would work:

snippet.juliarepl
julia> f(x,y)= sqrt( x^2 + y^2 )				## (Any, Any) -> Any
f (generic function with 1 method)

Alas, because you may only want this function to work with Float64 in your own application (to make sure the program does not accidentally lose precision!). In this case, your user program can define

snippet.juliarepl
julia> f(x::Float64,y::Float64)::Float64= sqrt( x^2 + y^2 )	## (Float64,Float64) -> Float64
f (generic function with 1 method)

Now you will get compile-time(!) errors if you try to invoke f with any Float32, an integer, character, complex number, etc.—or if you accidentally assigned the result to an integer variable (both inside the function and in the function call). Specificity also eliminates the need for the compiler to determine whether it needs to execute a run-time dispatch or whether a faster compile-time dispatch will do.

On the other end of the spectrum, generics can be very useful, too. For example, you may want to define a function

snippet.juliarepl
julia> p(x)= println("Your progam used $x");			## (Any) -> Nothing

This p is a function that you may want to use with all sorts of different input types. In this case, the generic functionality is great.

Genericism tends to be more useful when writing packages that will be used and reused as inputs elsewhere for all sorts of applications that the package author cannot foresee. Specificity tends to be more useful when writing one's own (final) application.

The middle of the spectrum is often the right location. You may want

snippet.juliarepl
julia> f(x::AbstractFloat,y::AbstractFloat)::AbstractFloat= sqrt( x^2 + y^2 );	## (Float,Float) -> Float

julia> g(x::Vector)::Vector= sort( g );

(The latter function could be even more specific by declaring that the element types of x will also be the element types of the function output. For example,

snippet.juliarepl
julia> function g(x::Vector{T})::Vector{T} where T <: Number; sort( g ); end#function##
g (generic function with 1 method)

IMPORTANT In sum, although type specificity is optional for function arguments and return values, it is almost always better for end users to be specific. Whenever you can, type-define your function arguments.

The Return Type is *Not* a Dispatch Selector

WARNING A caller cannot request a specific dispatch based on the return type.

snippet.juliarepl
julia> function g( a::Int )::Int 3 end;##function	## g() barfs when not fed an Int

julia> f()::Int=1 				        ## f() returns an Int, ... briefly until
f (generic function with 1 method)

julia> f()::AbstractFloat= 1.0				## no more. this f() s not a second method, but overwrites f()
f (generic function with 1 method)

julia> g( f() )						## ergo, g() now receives a float function and value
ERROR: MethodError: no method matching g(::Float64)
Closest candidates are:
  g(!Matched::Int64) at none:1
Stacktrace:

Declaring Return Types

The return type can be forced not only onto the function declaration, but also on the return statement. Again, it can help catch bugs early, sometimes even at compile time:

snippet.juliarepl
julia> f()::Int= 42			## return value is Int
f (generic function with 1 method)

julia> meaningoflife= f()		## ok
42

julia> y= Vector{Int}[ 1.0, f() ]	## user bug, because f() did not give an Int.  hail the compiler.
ERROR: MethodError: Cannot `convert` an object of type Float64 to an object of type Array{Int64,1}
Closest candidates are:
  convert(::Type{T<:Array}, !Matched::AbstractArray) where T<:Array at array.jl:489
  convert(::Type{T<:AbstractArray}, !Matched::T<:AbstractArray) where T<:AbstractArray at abstractarray.jl:14
  convert(::Type{T<:AbstractArray}, !Matched::LinearAlgebra.Factorization) where T<:AbstractArray at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/factorization.jl:46
  ...
Stacktrace:

Here is another bug catcher:

snippet.juliarepl
julia> f()::Int= sqrt(12.0)
f (generic function with 1 method)

julia> f()
ERROR: InexactError: Int64(Int64, 3.4641016151377544)
Stacktrace:

Now here is a reallly bad function.

snippet.juliarepl
julia> x=12; g()= x::Float64			## compiler infers that return value is Float64
g (generic function with 1 method)

julia> g()
ERROR: TypeError: in g, in typeassert, expected Float64, got Int64
Stacktrace:

The inside of function g cannot know what x is. This will terribly slow down the Julia program. In this context, it is also useful to mention that one should never to have one function (accidentally) return different return types based on its inputs or calculations.

snippet.juliarepl
julia> y=3.0
3.0

julia> f()= (y>0) ? 1 : 2.0	## Don't do this.  Bad logic and speed consequences.
f (generic function with 1 method)
  • It is good practice to have only one return statement at the end of the function (especially if the function declaration has not typed its output), assuming this is reasonable from the perspective of the algorithm.

Declaring Functions With Arguments

Ordinary Functions with Arguments (and Names and Types and Default Values)

snippet.juliarepl
julia> function f( a::Int, b::Int=2; c::Int=3 )::Int; a+b+c ; end#function f
f (generic function with 2 methods)

julia> methods(f)						## one definition created multiple methods
 2 methods for generic function "f":
[1] f(a::Int64, b::Int64; c) in Main at none:1
[2] f(a::Int64) in Main at none:1

julia> f( a::Int , b::Int=2 ; c::Int=3 )::Int= a+b+c		## same definition
f (generic function with 2 methods)
  • a is an ordinary argument, that must be an integer.
  • b must be an integer and has a default value. The caller can omit it if it wants '2'
  • c is a “named argument” (which must have a default value). To change its value, the caller must name 'c'.

Typing Abstract Containers (like Arrays or Dicts)

snippet.juliarepl
julia> sumprod( x::Vector )= sum( prod(x) );			## catch any Vector.  but why allow String Vector?

julia> function sumprod( x::Vector{T} )::T where T<:Number; sum( prod(x) ); end;##function##  better

Null Defaults

Julia does not have null pointers, which are natural defaults in languages like C. But this functionality is easy to accomplish with two functions:

snippet.juliarepl
julia> f( x::Int)= "argument $x";

julia> f( )= "no argument call";

julia> ( f(20), f() )
( "argument 20", "no argument call" )

Another alternative is to make up your own default value. Typical choices may be

snippet.juliarepl
julia> f( x::Int= typemin(Int) )= (x == typemin(Int)) ? "no argument" : "argument $x";

julia> f( x::AbstractFloat= NaN )= (isnan(x)) ? 0.0 : x;

julia> f( gradient::Function=error )= (gradient == error) || "do not use finite differences";

Keyword-Required Named Arguments

Arguments after the semi-colon must be have a default value (if they are not named) by the function call. Callers must never name the argument when the argument was declared before the semicolon; and must always name the argument when declared after.

snippet.juliarepl
julia> function f(x::Int; y::Int=0); println("f: $x $y"); end#function   ## semicolon starts named args
f (generic function with 1 method)

julia> f(1, y=2)                   ## on function calls, second argument must be named
f: 1 2

julia> f(1; y=2)                  ## for clarity, you can also separate this with semicolon
f: 1 2

julia> f(3)                       ## or, because we have a default, you can omit it
f: 3 0

julia> f(1,2)                     ## but you cannot give y like an ordinary argument
ERROR: MethodError: no method matching f(::Int64, ::Int64)
Closest candidates are:
  f(::Int64; y) at none:1

Stacktrace:

Functions Passing Along Arbitrary Named Keyword Arguments

snippet.juliarepl
julia> entry(x::AbstractFloat, y::AbstractFloat, z::AbstractFloat; arg1::Int=1, args...)= deeper(x, y+z; args...)
entry (generic function with 1 method)

julia> deeper(x::AbstractFloat, y::AbstractFloat; arg1::Int=1, arg2::Int=2, arg3::String="hi")=
     "Your function has $x and $y; with optargs $arg1 and $arg2 and $arg3" ##
deeper (generic function with 1 method)

julia> entry(1.0, 2.0, 3.0; arg1=10)
"Your function has 1.0 and 5.0; with optargs 1 and 2 and hi"

julia> entry(1.0, 2.0, 3.0; arg1=10, arg2=12)
"Your function has 1.0 and 5.0; with optargs 1 and 12 and hi"

julia> entry(1.0, 2.0, 3.0; arg1=10, badarg=122)		## cannot work, because the first f has nowhere to pass this onto
ERROR: MethodError: no method matching deeper(::Float64, ::Float64; badarg=122)
Closest candidates are:
  deeper(::AbstractFloat, ::AbstractFloat; arg1, arg2, arg3) at none:1 got unsupported keyword argument "badarg"
Stacktrace:
 [1] (::#kw##deeper)(::Array{Any,1}, ::#deeper, ::Float64, ::Float64) at ./<missing>:0
 [2] #entry#1(::Int64, ::Array{Any,1}, ::Function, ::Float64, ::Float64, ::Float64) at ./none:1
 [3] (::#kw##entry)(::Array{Any,1}, ::#entry, ::Float64, ::Float64, ::Float64) at ./<missing>:0

The "Where" Approach to Constraining Multiple Argument and/or Return Types

Julia also offers an even more flexible where. The principal use of where is in generic function, in which you want to require multiple arguments (or the return value) to be of the same type as one of the input argument.

snippet.juliarepl
julia> function normed_distance(p::Pair{T,T})::T where T<:Real; sqrt(first(p)^2+last(p)^2); end;#function##

julia> normed_distance( Pair( 2.0, 3.0 ) )
3.605551275463989

julia> normed_distance( Pair( 2, 3.0 ) )	## does not match two same inputs in the pair
ERROR: MethodError: no method matching normed_distance(::Pair{Int64,Float64})
Closest candidates are:
  normed_distance(!Matched::Pair{T<:Real,T<:Real}) where T<:Real at none:1
Stacktrace:

Basic Example

The following could be done more easily with Vector{<:Real}, but illustrates the method:

snippet.juliarepl
julia> f( x::Vector{T} ) where T <: Real= "where vector is $(typeof(x))"	## Basic Use
f (generic function with 1 method)

julia> f( [1,2] )
"where vector is Array{Int64,1}"

julia> f( [1.0,2.0] )
"where vector is Array{Float64,1}"

julia> f( Vector{Float32}([1.0,2.0]) )
"where vector is Array{Float32,1}"

julia> f( ["a","b"] )
ERROR: MethodError: no method matching f(::Array{String,1})
Closest candidates are:
  f(!Matched::Array{T<:Real,1}) where T<:Real at none:1
Stacktrace:
 ...

Constraints Among Arguments and Returns

A definition where where is more useful is

snippet.juliarepl
julia> function f( x::Vector{T}, y::T )::Vector{T} where T<:AbstractFloat;  vcat(x,y) .+ 1;  end##function
f (generic function with 1 method)

julia> f( [1.0, 2.0], 3.0 )	## x, y, and the return are all of type Float64
3-element Array{Float64,1}:
 2.0
 3.0
 4.0

julia> f( Vector{Float32}([1.0, 2.0]), Float32(3.0) )	## x, y, and the return are all of type Float32
3-element Array{Float32,1}:
 2.0
 3.0
 4.0

julia> f( [1, 2], 3 ) 		## fails: Integers are not AbstractFloats
ERROR: MethodError: no method matching f(::Array{Int64,1}, ::Int64)
Stacktrace:
 [1] top-level scope at none:0

julia> f( [1.0, 2.0], Float32(3.0) )		## Float64 and Float32 = mixed call.  fails.
ERROR: MethodError: no method matching f(::Array{Float64,1}, ::Float32)
Closest candidates are:
  f(::Array{T<:AbstractFloat,1}, !Matched::T<:AbstractFloat) where T<:AbstractFloat at none:1
Stacktrace:
 [1] top-level scope at none:0

julia> f( [1, 2], Float32(3.0) )		## mixed call.  fails.
ERROR: MethodError: no method matching f(::Array{Int64,1}, ::Float32)
Closest candidates are:
  f(!Matched::Array{T<:AbstractFloat,1}, ::T<:AbstractFloat) where T<:AbstractFloat at none:1
Stacktrace:
 [1] top-level scope at none:0

Dispatch for both Scalar and Array

Remember that if you define a function, you will automatically also have a dot form. In many cases, this is what you want a similar vector function to do, releasing you from the need to define it.

Just define two functions with the same name:

snippet.juliarepl
julia> function dispatch( x::AbstractFloat ); "Float Scalar"; end#function
dispatch (generic function with 1 method)

julia> function dispatch( x::Vector{Float64} ); "Float Vector"; end#function
dispatch (generic function with 2 methods)

julia> dispatch( [ 1.0, 2.0 ] )
"Float Vector"

julia> dispatch( [ 1.0 2.0; 3.0 4.0 ] )   	## but Vector does not work on Matrices
ERROR: MethodError: no method matching dispatch(::Array{Float64,2})
Closest candidates are:
  dispatch(!Matched::Array{Float64,1}) at none:1
  dispatch(!Matched::AbstractFloat) at none:1
Stacktrace:
 ...

julia> dispatch.( [ 1.0 2.0; 3.0 4.0 ] )  	## dot-postfix invokation feeds array elements as scalars
2×2 Array{String,2}:
 "Float Scalar"  "Float Scalar"
 "Float Scalar"  "Float Scalar"

julia> function dispatch( x::Array{Float64,2} ); "Float Matrix"; end#function
dispatch (generic function with 3 methods)

julia> dispatch( [ 1.0 2.0; 3.0 4.0 ] )		## but now we have a matching native matrix method
"Float Matrix"

Conversion and Promotion

Julia is fairly strictly typed. When a function requests a Float32 argument, it fails when it receives a Float64. However, magic seems to happen when the user types

snippet.juliarepl
julia> x= [ 1.0, 2 ]		## ever wonder how a [ Float64, Int ] can become a [ Float64, Float64 ] ??
2-element Array{Float64,1}:
 1.0
 2.0

This works due to behind-the-scene magic. First, Julia has a system to declare that an Int can be converted into a Float64. This is accomplished via the julia convert. Second, Julia has a system to declare how it should react when it encounters multiple values of different kinds. In this case, there was a rule that said that “when you see a list of Float64,Int within the [array], promote them both to Float64. Voi-la. This is explained in detail in the official Julia docs.

The promotion aspect can sometimes be useful even in userland. For example, you can take advantage of the built-in Julia conversions in either of these two ways

snippet.juliarepl
julia> f( xx::AbstractFloat,yy::AbstractFloat )= sqrt( xx^2+yy^2 );

julia> f( xx, yy )= sqrt( Float64(xx), Float64(yy) );	## choice 1: you provide some intelligence

julia> f( xx,yy )= f( promote(xx,yy) );		## choice 2: promote() provides the intelligence

Passing a Function as an Argument to Another Function

snippet.juliarepl
julia> workonfun( f::Function )= "Your function is ", f, " and operating on 10 it yields ", f(10)
workonfun (generic function with 1 method)

julia> workonfun(sqrt)
("Your function is ", sqrt, " and operating on 10 it yields ", 3.1622776601683795)
  • For dispatch purposes, Julia treats all functions the same. That is, you cannot write one dispatch that applies to “functions that take an integer argument” and another dispatch that applies to “functions that take a float vector argument”. Instead, you should check the arguments upon entry into the function, and then dispatch yourself to the appropriate destination function.
  • It seems difficult to discover whether a method of function f() exists for a particular type argument. You are probably better off simply trapping the function for a MethodError.
snippet.juliarepl
julia> function workon( f::Function, argument )
           (isa(argument,Real)) && return f(argument)
           try
           	f(argument)
           catch err
                isa(err, MethodError) ? "f does not work on an argument of type $(typeof(argument))" : "something else went wrong: $err"
           end#try
        end;#function##

julia> workon( sqrt, 2 )
1.4142135623730951

julia> workon( sqrt, "a" )
"f does not work on an argument of type String"

Allowing Missing in Function Arguments

Missing is now a core feature of Julia. Ergo, it is important to understand how to dispatch when missing values can be involved. See also Missings and NaN. Reminder: Missing is a type, missing is a value.

Adding Missing Handling To Scalar Arguments

Define one function for the scalar type, and another for the missing type.

snippet.juliarepl
julia> function mysqrt(in::AbstractFloat)::AbstractFloat; sqrt(in) end#function
mysqrt (generic function with 1 method)

julia> function mysqrt(in::Missing)::Missing; in; end#function   ## any call with missing returns missing
mysqrt (generic function with 2 methods)

julia> mysqrt.( [1.0, missing, NaN, 4.0] )
4-element Array{Union{Missing, Float64},1}:
   1.0
    missing
 NaN
   2.0

Adding Missing (Unions) To Array Arguments

The <: operator is smart enough to understand “union with missing” versions, too:

snippet.juliarepl
julia> MReal= Union{Real,Missing}
Union{Missing, Real}

julia> filtermissing( x::Vector{<:MReal} )= filter( !ismissing, x )
filtermissing (generic function with 1 method)

julia> filtermissing( [ 1.0, 2.0, missing, 3.0 ] )
3-element Array{Union{Missing, Float64},1}:
 1.0
 2.0
 3.0

julia> filtermissing( [ Float32(1.0), missing] )    	## Note: preserves return type
1-element Array{Union{Missing, Float32},1}:
 1.0f0

julia> filtermissing( [ "a", missing] )			## Strings are not Reals
ERROR: MethodError: no method matching filtermissing(::Array{Union{Missing, String},1})
Closest candidates are:
  filtermissing(!Matched::Array{#s23,1} where #s23<:Union{Missing, Real}) at none:1
Stacktrace:
 [1] top-level scope at none:0
  • You could define one function for Real and another for MReal types.

Common Dispatch Needs

Dispatching on Common Numeric Scalars

The heart of Julia is its ability to overload functions based on the types of call arguments (but not return type).

Reminder: A Real is any Float, Integer, Irrational, or Rational, but not a Complex. An Int is not an AbstractFloat. A Number is the supertype of all numerics.

Only Floats (All Precisions)

snippet.juliarepl
julia> myfloat( x::AbstractFloat )= typeof(x);  	## integers not welcome

julia> myfloat(1.0), Float16(2.0)
(Float64, Float16(2.0))

julia> myfloat(1)
ERROR: MethodError: no method matching myfloat(::Int64)
Closest candidates are:
  myfloat(!Matched::AbstractFloat) at none:1
Stacktrace:
 ...
  • You could also dispatch on specific Floats or Integers, such as Float64

Only Ints (All Precisions)

snippet.juliarepl
julia> myint( x::Integer )= typeof(x);			## floats not welcome

julia> myint( 1 ), myint( Int8(2) )
(Int64, Int8)

julia> myint( 32.0 )
ERROR: MethodError: no method matching myint(::Float64)
Closest candidates are:
  myint(!Matched::Integer) at none:1
Stacktrace:

Only Ints or Floats (but not Complex)

snippet.juliarepl
julia> myreal( x::Real )= typeof(x);

julia> myreal(10.0), myreal( Int8(6) )
(Float64, Int8)

Only High (64-bit) Precision Ints or Floats

snippet.juliarepl
julia> Prec64= Union{Int64,Float64};			## could add Missing

julia> f( x::Prec64 )= "x is $(typeof(x))"
f (generic function with 1 method)

julia> f( 1.0 )
"x is Float64"

julia> f( 1 )
"x is Int64"

julia> f( Float32(1.0) )
ERROR: MethodError: no method matching f(::Float32)
Closest candidates are:
  f(!Matched::Union{Float64, Int64}) at none:1
Stacktrace:
 ...

Different Numeric Type-Specific Behavior

Overloading the function.

snippet.juliarepl
julia> function dispatch( x ); "to Any f"; end#function
dispatch (generic function with 1 method)

julia> function dispatch( x::Float64 ); "to Float64 f"; end#function
dispatch (generic function with 2 methods)

julia> function dispatch( x::Real ); "to Real f"; end#function
dispatch (generic function with 3 methods)

julia> dispatch("a"), dispatch(0.2), dispatch(1), dispatch( Float32(1.0) )
("to Any f", "to Float64 f", "to Real f", "to Real f")

julia> dispatch( [ 1.0, 2.0, NaN ] )     ## no dot-postfix.  argument type is Vector (of Floats).  no such dispatch() yet defined.
"to Any f"

julia> dispatch.( [ 1.0, 2.0, NaN ] )    ## with dot-postfix, array arguments are multi-fed as calls with scalars
3-element Array{String,1}:
 "to Float64 f"
 "to Float64 f"
 "to Float64 f"

Dispatching on *Arrays* of Common (Numeric) Types

Remember that if you define a function, you will automatically also have a dot form. In many cases, this is what you want a similar vector function to do, releasing you from the need to define it.

Any Type of Array

snippet.juliarepl
julia> f(x::Vector)::String= "vector of $(typeof(x))";		## could also dispatch on broader Matrix or Array

julia> f( [ "a", 1] )
"vector of Array{Any,1}"

Writing a Function that Works on Lists Or Arrays

snippet.juliarepl
julia> work( x::Int, y::Int, z::Int )::Int= x+2*y+3*z ;

julia> work( 1, 2, 3 )
14

julia> x= [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> work( x... )						## splatter operator on vector x
14

julia> work( v::Vector{Int} )::Int= work(v...);			## or define directly as a function of splatterer

julia> work( x )
14

Primitive Numeric Types

snippet.juliarepl
julia> f( x::Vector{Float64} )= "vector of 64-bit floats"		## most straightforward
f (generic function with 1 method)

julia> f( [1.0,2.0] )
"vector of 64-bit floats"

Broader Types: Floats, Ints, and/or Both

The following is probably not what you want:

snippet.juliarepl
julia> f( x::Vector{Real} )= "vector of reals"
f (generic function with 1 method)

julia> f( [1.0,2.0] )					## it does not catch a Vector of Float64's !!
ERROR: MethodError: no method matching f(::Array{Float64,1})
Closest candidates are:
  f(!Matched::Array{Real,1}) at none:1
Stacktrace:
 ...

This is because you can define a Vector of Reals, and this is what f catches:

snippet.juliarepl
julia> f( x::Vector{Real} )= "vector of reals"
f (generic function with 1 method)

julia> x= Vector{Real}( [ 1.0, 3.0 ] )			## this is because you can define a Vector of Reals
2-element Array{Real,1}:
 1.0
 3.0

julia> x[2]=4; x					## see?  a Real Vector can contain different types!
2-element Array{Real,1}:
 1.0
  4

julia> f(x)						## so it dispatches what you asked for.
"vector of reals"

Instead, what you probably meant was to take a unitype vector of Real “or less”:

snippet.juliarepl
julia> f( x::Vector{<:Real} )= "vector of $(typeof(x))"
f (generic function with 1 method)

julia> f( [1.0, 2.0] )					## it can catch a vector of Float64s
"vector of Array{Float64,1}"

julia> f( [1, 2] )					## or a vector of Int64s
"vector of Array{Int64,1}"
  • This approach also works with Missing types, see below.

Example: A Lag Function

snippet.juliarepl
julia> function lag( v::Vector{T}, n::Int=1 )::Vector{T} where T <: Union{Missing,Float64}
           @assert( n < length(v), "n must be less than the length of the vector." )
           vcat( fill( NaN, n), v[1:(end-n)] )
       end#function##
lag (generic function with 2 methods)

julia> function lag( v::Vector{T}, n::Int=1 )::Vector{Union{Missing,T}} where T
           @assert( n < length(v), "n must be less than the length of the vector." )
           vcat( fill( missing, n), v[1:(end-n)] )
       end#function##
lag (generic function with 4 methods)

julia> lag( [ 1.0, 2.0, 3.0 ] )			## caught by first lag fun.  result is still Float only
3-element Array{Float64,1}:
 NaN
   1.0
   2.0

julia> lag( [ 1.0, missing, 2.0, 3.0 ] )	## caught by first lag fun.  result still has missing
4-element Array{Union{Missing, Float64},1}:
 NaN
   1.0
    missing
   2.0

julia> lag( [ "a", "b", "c" ] )			## caught by second fun.  extends type.
3-element Array{Union{Missing, String},1}:
 missing
 "a"
 "b"

julia> lag( [ 1, missing, 2, 3 ] )		## caught by second lag.  keeps type 
4-element Array{Union{Missing, Int64},1}:
  missing
 1
  missing
 2

Again? NaN for Floats, Missing for Others

Define a nada() function that returns NaN for floats and missing for non-floats, which are the respective kinds of “bad obs” value that we want.

snippet.juliarepl
julia> nada(::Type{T}) where {T <: AbstractFloat}= T(NaN)
nada (generic function with 1 method)

julia> nada(::Type)= missing
nada (generic function with 2 methods)

julia> nada(Float32) , nada(Int)
(NaN32, missing)

julia> function lag(v::AbstractVector{T}, n::Int=1) where T
           @assert n < length(v) "n must be less than the length of the vector."
           vcat( fill( nada(T), n ), v[1:(end-n)] )
       end#function##
lag (generic function with 2 methods)

julia> lag( [ 1.0, 2.0, 3.0 ] )                         ## Floats use NaN
3-element Array{Float64,1}:
 NaN
   1.0
   2.0

julia> lag( [ 'a', 'b', 'c' ] )                         ## Other types use missing
3-element Array{Union{Missing, Char},1}:
 missing
 'a'
 'b'

Backmatter

Useful Packages on Julia Repository

Notes

  • In Computer Science, sometimes the arguments (parameters) in the function definition are named “formal parameters,” and the arguments (parameters) in the caller are named “actual parameters.” I wish the jargon was clearer.
  • Unfortunately, it is not possible to turn on a compiler warning whenever meaningful default type variability arises, e.g., due to global variables or untyped arguments. Use Traceur.
  • There are all sorts of ambiguities that can arise with abstract containers. Be specific (and/or read 15.9 of the Julia manual).
fundispatch.txt · Last modified: 2018/11/22 20:48 (external edit)