Basic concept about multiple dispatch

hello, I found myself got confused about multiple dispatch. Take the function rand as an example: there’re a lot of method definitions across different files:

in base/libc.jl:

rand() = ccall(:rand, Cint, ())
rand(::Type{UInt32}) = ((rand() % UInt32) << 16) ⊻ (rand() % UInt32)
rand(::Type{Float64}) = rand(UInt32) / 2^32

and in stdlib/Random/src/Random.jl:

rand(rng::AbstractRNG=GLOBAL_RNG, ::Type{X}=Float64) where {X} = rand(rng, Sampler(rng, X, Val(1)))

now I try to inspect which method definition is in use when calling rand(), which should give a random number in [0,1):

julia> @which rand()
rand() in Random at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v0.7/Random/src/Random.jl:222

seems like the above definition in Random.jl is in use. Now the questions are:

  1. why the plain rand() definition in libc.jl (i.e. the first one) not called?
  2. how do Julia decide the priority of the methods to dispatch if two or more methods all match the call? It could happen say one method has no parameter, and the other has parameters all having default values.
  3. what does fun(::Type{X}=Float64) where {X} means? why could the parameter has no name?

thanks.

It’s not the same generic function:

julia> Base.Libc.rand                                                                                                                                                  
rand (generic function with 3 methods)                                                                                                                                 

julia> rand                                                                                                                                                            
rand (generic function with 56 methods)                                                                                                                                

which happen to have the same name but are not the same.

wow!!! It gets even more confusing now!!!

how could functions having the same name come with different “branch” of multiple dispatches ???

Just like two different types can have the same name, say

module A
struct AB end
end
module B
struct AB end
end

so can functions

module A
f() = 1
end
module B
f() = 1
end

Check this thread:

3 Likes

Doesn’t really answer the question why the second is chosen:

julia> methods(Base.Libc.rand)
# 3 methods for generic function "rand":
[1] rand() in Base.Libc at libc.jl:374
[2] rand(::Type{UInt32}) in Base.Libc at libc.jl:376
[3] rand(::Type{Float64}) in Base.Libc at libc.jl:377

julia> methods(rand)
# 71 methods for generic function "rand":
...
[15] rand() in Random at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.0\Random\src\Random.jl:222
...
1 Like

Because that is what is being using in Main (i.e. the module of the REPL). In a module of yours you can choose which one you want:

julia> baremodule A
       using Random: rand
       Core.println(rand()) # uses Random.rand as at the REPL
       end                                                                                                                                                             
0.25909                                                                                                                                                                
Main.A                                                                                                                                                                 

julia> baremodule b
       using Base.Libc: rand
       Core.println(rand()) # uses Base.Libc.rand
       end                                                                                                                                                             
1408058198                                                                                                                                                             
Main.b                                                                                                                                                                 

could u be nice enough to point to where could I find the code of that using in module Main? (actually I don’t know which file corresponds to Main module).

Good question. Actually, it’s slightly different than I thought. The rand generic function is defined in Base:


then imported into Random, where the actual methods are then defined:

Thus it makes sense that @which rand returns Base.

2 Likes

it’s quite a way to define methods … (need to learn).
ah … so, there’s no such a file for module Main?

thus, having solved question 1, what about questions 2 and 3?

  1. how do Julia decide the priority of the methods to dispatch if two or more methods all match the call? It could happen say one method has no parameter, and the other has parameters all having default values.
  2. what does fun(::Type{X}=Float64) where {X} means? why could the parameter has no name ?

I don’t know where main is defined, maybe in base/client.jl.

  1. it uses the most specific method. See https://docs.julialang.org/en/v1/manual/methods/ and watch https://youtu.be/Y95fAipREHQ.

  2. fun(::Type{X}=Float64) where {X} = X will return the Type, defaulting to Float64.

1 Like

Note that rand is porbably one of the more complicated functions with respect to it’s distribution in source files. The reason is that although most random number stuff is in the Random stdlib rand itself is considered to be so essential that it has been decided to make it available in base julia.

Also to 2., it can throw an ambiguity error in case it doesn’t know what to do. (See for example: https://github.com/JuliaDiffEq/DiffEqTutorials.jl/issues/26)

could u please give me an example that throws “ambiguity error”? In the following, no error was thrown, and seems like the first method can never be used:

julia> function f()
           println("000")
       end
f (generic function with 1 method)

julia> function f(a=1)
           println("111")
       end
f (generic function with 2 methods)

julia> methods(f)
# 2 methods for generic function "f":
[1] f() in Main at none:2
[2] f(a) in Main at none:2

julia> f()
111

You should read https://docs.julialang.org/en/v1/manual/methods/#man-ambiguities-1, which indeed has such an example. If you make it through that section of the docs you’ll also find this and understand why the first method you define is not called.

2 Likes

following [this] manual, the following function

function f(a=1)
  println("111")
end

should translate into

function f(a)
  println("111")
end

f() = f(1)

however, the definition of f() = f(1) is duplicated, i.e. there was another defintion of f() already! The problem is it does not throw any error and the earlier definition is simply overrided?

as a simple test, I try the following:

julia> g() = 1
g (generic function with 1 method)

julia> g() = 2
g (generic function with 1 method)

and it’s the same: the redefinition of g() gave no error nor warning. Is it a desired behavior?

In practice redefinition never happens as one usually uses multiple dispatch with respect to all argument types. But redefinition is rather useful in interactive environments as REPL and Jupyter notebooks. So I see that as a desired behavior.

1 Like

hm… I think, as a matter of safety, redefinition of a function should throw a warning.

We could intentionally suppress the warning if we want. But the warning would be a very good alert.

A warning is only thrown when redefining a method inside a package. It was thought that for interactive development the warnings were too noisy.

3 Likes

It does — as @mauro3 said above — for redefinitions inside packages (modules). The lesson from this is that one should develop code inside packages, using Revise.jl.

A bit late, but here is a minimal example with an ambiguity error:

f(x::Int, y::Any) = println("int")
f(x::Any, y::String) = println("string")
f(3, "test")

will give you

MethodError: f(::Int64, ::String) is ambiguous. Candidates:
  f(x, y::String) in Main at In[15]:2
  f(x::Int64, y) in Main at In[18]:1
Possible fix, define
  f(::Int64, ::String)
3 Likes