Suboptimal in what way? They obviously aren’t actually restricted, if you could conceivably call them with dual numbers.
Note that there is no performance penalty for not specifying type.
julia> f(x) = x^2
f (generic function with 1 method)
julia> @code_native f(2)
.text
; Function f {
; Location: REPL[3]:1
; Function literal_pow; {
; Location: intfuncs.jl:243
; Function *; {
; Location: REPL[3]:1
imulq %rdi, %rdi
;}}
movq %rdi, %rax
retq
nopl (%rax,%rax)
;}
julia> @code_native f(2.0)
.text
; Function f {
; Location: REPL[3]:1
; Function literal_pow; {
; Location: intfuncs.jl:243
; Function *; {
; Location: REPL[3]:1
vmulsd %xmm0, %xmm0, %xmm0
;}}
retq
nopw %cs:(%rax,%rax)
;}
julia> @code_native f(2f0)
.text
; Function f {
; Location: REPL[3]:1
; Function literal_pow; {
; Location: intfuncs.jl:243
; Function *; {
; Location: REPL[3]:1
vmulss %xmm0, %xmm0, %xmm0
;}}
retq
nopw %cs:(%rax,%rax)
;}
julia> @code_native f(rand(2,2))
.text
; Function f {
; Location: REPL[3]:1
pushq %rax
movq %rsi, (%rsp)
; Function literal_pow; {
; Location: none
; Function macro expansion; {
; Location: none
; Function ^; {
; Location: dense.jl:366
movabsq $power_by_squaring, %rax
movq (%rsi), %rdi
movl $2, %esi
callq *%rax
;}}}
popq %rcx
retq
;}
julia> @code_native f("hi")
.text
; Function f {
; Location: REPL[3]:1
pushq %rax
movq %rsi, (%rsp)
; Function literal_pow; {
; Location: none
; Function macro expansion; {
; Location: none
; Function ^; {
; Location: basic.jl:674
movabsq $repeat, %rax
movq (%rsi), %rdi
movl $2, %esi
callq *%rax
;}}}
popq %rcx
retq
;}
Every time you call f
with a new combination of types, a new version of f
specialized for those types gets compiled. You can see that each version has different assembly above.
Dispatch (which version of f
to call) gets resolved at compile time, assuming your functions are type-stable. Therefore there is no run-time penalty to being generic.
However, if you want to make it clear the function only applies to numbers, you could say f(x::Number)
.
EDIT:
If you feel like getting experimental, there is Zygote. This is useful, because while you can always write your own code to be generic, but code from libraries wont always be.
julia> using Zygote
[ Info: Precompiling Zygote [e88e6eb3-aa80-5325-afca-941959d7151f]
julia> f64(x::Float64) = x^2
f64 (generic function with 1 method)
julia> Zygote.derivative(f64, 3.0)
6.0
julia> @code_native Zygote.derivative(f64, 3.0)
.text
; ┌ @ interface.jl:47 within `derivative'
; │┌ @ interface.jl:44 within `gradient'
; ││┌ @ interface.jl:38 within `#66'
; │││┌ @ REPL[3]:1 within `f64'
; ││││┌ @ intfuncs.jl:243 within `literal_pow'
; │││││┌ @ lib.jl:6 within `accum'
; ││││││┌ @ interface.jl:47 within `+'
vaddsd %xmm0, %xmm0, %xmm0
; │└└└└└└
retq
nopw %cs:(%rax,%rax)
; └
Zygote is slow to compile, but it often ends up producing optimal code – note it’s compiled down to only adding a number to itself (ie, multiply by 2)!