The (missing) harm of manual dispatch in Julia
September 24, 2020
Just a short one today: New users of Julia coming from other dynamically typed languages might write functions like
function foo(x)
if x isa Int
x + x
elseif x isa Float64
x / 2.0
elseif x isa String
length(x)
else
1
end
end
This is clearly unidiomatic and should be written as
bar(x::Int) = x + x
bar(x::Float64) = x / 2.0
bar(x::String) = length(x)
bar(x) = 1 # or bar(x::Any), to be explicit
because it is nicer to read and very easy to extend for other types of x
.
Well, you technically can extend foo
the same way as bar
, but reading the definition of foo
one would expect to then know its complete behavior, so it would be misleading.
However, my point is that there is actually (maybe surprisingly) no harm at runtime for code as in foo
!
The Julia compiler isn't tricked that easily and will still produce optimal machine code for each type of the argument:
julia> @code_native debuginfo=:none bar(1)
.text
leaq (%rdi,%rdi), %rax
retq
nopw %cs:(%rax,%rax)
julia> @code_native debuginfo=:none foo(1)
.text
leaq (%rdi,%rdi), %rax
retq
nopw %cs:(%rax,%rax)
Both functions basically compile down to a single adding instruction (leaq
; retq
is for returning from the function and nopw
is an operation that does nothing and is there for technical reasons).
Think about it this way:
When Julia compiles foo(1)
, it knows that 1
is of type Int
, can evaluate the x isa Int
expression at compile time to true
, and discard everything but the x + x
without a problem.
"Appendix"
For completeness, here is what's produced in the other cases:julia> @code_native debuginfo=:none bar(1.0)
.text
movabsq $140581530607976, %rax # imm = 0x7FDBB031A168
vmulsd (%rax), %xmm0, %xmm0
retq
nop
julia> @code_native debuginfo=:none foo(1.0)
.text
movabsq $140581530608088, %rax # imm = 0x7FDBB031A1D8
vmulsd (%rax), %xmm0, %xmm0
retq
nop
julia> @code_native debuginfo=:none bar("abc")
.text
pushq %rax
movabsq $"ncodeunits;", %rax
callq *%rax
popq %rcx
retq
nop
julia> @code_native debuginfo=:none foo("abc")
.text
pushq %rax
movabsq $"ncodeunits;", %rax
callq *%rax
popq %rcx
retq
nop
julia> @code_native debuginfo=:none bar([])
.text
movl $1, %eax
retq
nopw %cs:(%rax,%rax)
julia> @code_native debuginfo=:none foo([])
.text
movl $1, %eax
retq
nopw %cs:(%rax,%rax)