fundispatch

- snippet.juliarepl
julia> pkgchk.

**(****[****"**julia**"**=> v**"**1.0.3**"****]****)**;

- 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.

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.

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:

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.

- 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'.

- 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

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**"**;

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:

- 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

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:

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: ...

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

- Multiple comma-separated
`where`

's make it possible to catch multiple types for multiple arguments. `myfun(x::Vector{T}, x::T) where {T}= [v..., x]`

can dispatch`myfun( [1.0, 2.0], 3.0 )`

. [https://docs.julialang.org/en/stable/manual/methods/#Methods-1]- It can be useful to have a
`where T <: Any`

, as in the Numbers Permutation example.

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**"**

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

- 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**"**

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: **M**issing is a type, **m**issing is a value.

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

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**{**#s24**,**1**}**where #s24<: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.

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.

- 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`

- 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:

- snippet.juliarepl
julia> myreal

**(**x::Real**)**= typeof**(**x**)**; julia> myreal**(**10.0**)****,**myreal**(**Int8**(**6**)****)****(**Float64**,**Int8**)**

- 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: ...

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**"**

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.

- 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**}****"**

- 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

- 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**"**

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.

- 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

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**'**

- 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).

- snippet.juliarepl
julia> f0

**(**x::Union**{**Nothing**,**VersionNumber**}****)**= println**(****"**ok**"****)**f0**(**generic function with 1 method**)**julia> f0**(**v**"**1.0**"****)**ok julia> f1**(**x::Pair**{**String**,**VersionNumber**}****)**= println**(****"**ok**"****)**f1**(**generic function with 1 method**)**julia> f1**(****"**me**"**=> v**"**1.0**"****)**ok julia> f2**(**x::Pair**{**String**,**Union**{**Nothing**,**VersionNumber**}****}****)**= println**(****"**ok**"****)**f2**(**generic function with 1 method**)**julia> f2**(****"**me**"**=> v**"**1.0**"****)**ERROR: MethodError: no method matching f2**(**::Pair**{**String**,**VersionNumber**}****)**Closest candidates are: f2**(**::Pair**{**String**,**Union**{**Nothing**,**VersionNumber**}****}****)**at REPL**[**5**]**:1 Stacktrace:**[**1**]**top-level scope at none:0

fundispatch.txt · Last modified: 2018/12/27 14:38 (external edit)