Edit: @lmiq beat me to it by one minute ![]()
The distinction is that the first method requires that the two characters have the same type (and that that must be some subtype of Character. Here’s a simpler example:
julia> function f(x::T, y::T) where {T <: Integer}
x + y
end
f (generic function with 1 method)
julia> f(Int32(1), Int64(1))
ERROR: MethodError: no method matching f(::Int32, ::Int64)
Closest candidates are:
f(::T, ::T) where T<:Integer at REPL[6]:1
Stacktrace:
[1] top-level scope
@ REPL[8]:1
julia> function g(x::Integer, y::Integer)
x + y
end
g (generic function with 1 method)
julia> g(Int32(1), Int64(1))
2
A further difference is that having an explicit type parameter lets you express more complicated relationships, such as:
julia> function h(x::T, y::Matrix{T}) where {T}
x * y
end
h (generic function with 1 method)
julia> h(2.0, ones(Float64, 2, 2))
2×2 Matrix{Float64}:
2.0 2.0
2.0 2.0
here h is a function that will take any x and a y whose scalar type is the same as the type of x, whatever that type may be. That’s not a relationship you can express via dispatch without type variables.