in some cases it can add some convert
calls and type assertions right? although yeah even when nonzero, performance impact should be negligible
Well, code with duck-typing (or multiple dispatch, which is what Solmantos was claiming to be more expensive) can have runtime overhead, but that has other causes, not duck-typing or multiple dispatch. Simple example:
module DuckTypingSlow
function foo(x::Base.RefValue{Any})
return x[] + 2
end
end
The performance argument is exactly my point - duck typing in Julia has no performance penalty.
There is, however, a fundamental conceptual difference between duck typing and multiple dispatch, which my simple example was trying to illustrate. The holy traits pattern adds complexity without performance benefits over duck typing. But the discussion seems stuck on pedantic implementation details and meaningless defensive quips
This has already been addressed:
Industry has evolved in the last 20 years. We are not designing languages in the 90s.
Welcome to the forums, @Solmantos! There is indeed recent thought on this matter — have you seen and contrasted your own thoughts against Keno’s roadmap for interfaces that was linked above? The most likely way to affect change here is to work in the context of existing efforts.
The same misconceptions are here, and it’s seriously affecting the soundness of your proposal. I’m evidently failing to help you clear those up, so I’ll leave this discussion here. I’m confident that after you study Julia and the other efforts toward formalizing traits or interfaces, you’ll understand that the design issues brought up here weren’t “pedantic implementation details,” but important steps to figuring out something we all want. I’m sure they’ll welcome your participation and contributions then.
Could you elaborate on this? In my understanding, they are almost completely separate, although there is an interplay of the lack of duck typing or specialization with the multiple dispatch system, i.e. specialized methods can be written leveraging multiple dispatch. That’s the multiple dispatch at the semantic (front-end) level that you may be thinking of primarily. But the interesting thing about Julia is that multiple dispatch (static or dynamic) exists in the background regardless of whether you have specialized methods or duck typed methods. Outside of specific exceptions, specialized method instances will be compiled for each combination of argument types, and even inlined if they can be statically inferred in the callee (again, there are exceptions to this). What is typically associated with performance penalty is runtime dispatch, but this penalty is also not universally bad if it doesn’t occur many times over in hot loops where it swamps actual computation time.
I do agree both that this is an important issue and that we shouldn’t strive for the perfect solution right away and end up with no solution.
That said, I do believe that it was somewhat easier to tack such a system onto Python where types had a much smaller importance in the language itself than in Julia.
So regarding multiple dispatch, I agree with @Benny that we need to see examples to understand the interplay of different types of contracts with idiomatic Julia code leveraging multiple dispatch both implicitly and explicitly.
Should there not be some more formal forum for proposals like this, the way Python has Pep?
I think technically the most “correct” place for big feature request threads is supposed to be Github Discussions although I forget who told me that
although empirically it seems like many big proposals start as HackMD docs (e.g. 1 2 3 4) that get linked here or in the Slack or Zulip communities and then discussion continues on the doc until it becomes refined enough to graduate to a PR, at which point discussion happens on the PR.
Julia had Juleps already, but they fell into disuse. This was a new attempt, but likewise didn’t seem to get any traction, I think:
This is seemingly parenthetical to the original discussion (sorry for derailing it somewhat), but I feel that the term “multiple dispatch” is often misused. This may be my misunderstanding, and I am happy to be corrected.
Multiple dispatch in julia means that methods are specialized for multiple argument types. If only one argument is involved in a call, this is no different from single dispatch, and f(x)
in Julia is equivalent to x.f()
in python. What makes julia differ from python’s dispatch is when we have f(x, y)
, which is different from python’s x.f(y)
, as python usually can’t specialize on y
automatically, whereas julia does specialize on both the argument types.
The example presented above:
module MultipleDispatch
function foo(x::Int32)::Int32
return x + 2
end
function foo(x::Int64)::Int64
return x + 3
end
function foo(x::Float64)::Float64
return x + 4
end
end
has different methods associated with each type, but this is still using single-dispatch on the only argument. From what I understand, the duck-typing example is where a generic method is provided to define the behavior for an abstract type, and julia automatically compiles specialized method instances for specific concrete types. If I understand correctly, multiple dispatch is an extension of this to have method instances specialize on more than one argument.
Perhaps the term is being used differently than what I might be familiar with, so it might help if this is clarified.
In any case, this isn’t the topic of discussion, and I agree that interfaces and static checking will be a great step. Looks like the devs are already thinking about this.
Sorry to continue the derailing but let me try to clarify the terminology and concepts involved a bit more. I think that this explanation might also be helpful for @Solmantos to understand these concepts better.
Generally speaking, calling a function in Julia involves 2 orthogonal concepts:
- Multiple dispatch. This describes the process by which Julia figures out which method of the function it should call.
- Function specialization. This is the process in which Julia creates an instance of a method for a set of concrete parameter types (which it then compiles).
Unfortunately these 2 concepts are somewhat mixed up most of the times and even the manual isn’t very clear about the terminology.
Let me walk you through the whole process with an example to clarify the terms and different steps.
- We define a function. This step is often times not done explicitly but I mention it here for conceptual reasons.
function foo end
- We then add methods to this function by further definitions:
function foo(a::Number)
print("This is a number: $a")
end
function foo(str::String)
print("Got a string: $str")
end
- Now we have 1 function with 2 methods. To verify you can do:
julia> methods(foo)
# 2 methods for generic function "foo" from Main:
[1] foo(str::String)
@ REPL[2]:1
[2] foo(a::Number)
@ REPL[1]:1
- Let us now call this function with an Int:
foo(5)
. What Julia does in the background is to first run the dispatch to figure out which method is the most specific one. This is where multiple dispatch plays a role in general. In this case it it isfoo(::Number)
. You can check with:
julia> @which foo(5)
foo(a::Number)
@ Main REPL[1]:1
- Now Julia will call this method. Generally, when calling a method Julia specializes the method for the concrete argument types (here:
Int
) which results in aMethodInstance
which is then executed. You can see this by inspecting theMethod
:
julia> foo(5) # run once to create the MethodInstance for Int
This is a number: 5
julia> typeof(@which foo(5))
Method
julia> (@which foo(5)).specializations
MethodInstance for foo(::Int64)
- (Extra): In my example, if you run that function again with the Float64
2.0
then this dispatches to the same method but, due to specialization, Julia will create and compile a newMethodInstance
:
julia> foo(2.0)
This is a number: 2.0
julia> (@which foo(5)).specializations # looks a bit different now, but just contains 2 MethodInstances
svec(MethodInstance for foo(::Int64), MethodInstance for foo(::Float64), nothing, nothing, nothing, nothing, nothing)
Julias performance now stems from 2 facts:
- If the types are known at compile time, multiple dispatch happens at compile time
- Due to specialization, every
MethodInstance
receives only concrete types and thus can be compiled in the most efficient way.