Hi Sage folks! I gave that presentation on multiple dispatch at Strange Loop last year. Since someone pointed this thread out to me and the topic of multiple dispatch and type promotion is near and dear to my heart, I thought I'd chime in. Maybe my comments will be of some use. (Btw, since some people prefer their notebooks with some explanation, if anyone would care to see a video of a similar but not quite identical talk, this one <http://vimeo.com/84661077> from CodeMesh last year is pretty close, although it spends less time on multiple dispatch and type promotion.)
On Thursday, July 17, 2014 10:35:32 PM UTC-7, Robert Bradshaw wrote: > > I'm not sure multimethods alone are enough to solve issues with > Sage's type system (e.g. coercion, where the result of a+b may happen in > a completely new domain) but they could certainly help. I'm not familiar with Sage's issues with coercion (or even what you mean exactly by coercion), so take this with a grain of salt. I'll hazard a guess that you mean the problem of how to let the user specify what to do when it encounters something like a + b where a and b are different types, one or both of which are user defined, and which may or may not know anything about each other. When developing Julia, initially we didn't realize that multiple dispatch could handle this kind of problem so elegantly, but it turns out it can, which I attempted to explain in my talk and which I will say a bit about below. On Thursday, July 17, 2014 11:56:43 PM UTC-7, Nils Bruin wrote: > > In a dynamically typed language, multiple dispatch has a rather high cost. > LISP MOP implementations have a lot of experience. > If Julia has shown anything, it's that you *can* have ubiquitous multiple dispatch in a dynamic language and have it be very fast – it is commonly a zero-cost abstraction in Julia. First let me explain how multiple dispatch allows a promotion system that users can hook into for their own types by explaining how 1 + 2.5 works in Julia. 1 and 2.5 are of type Int and Float64 respectively, both of which are defined in Julia <https://github.com/JuliaLang/julia/blob/876e0d3cc3d0b7198875778a9dde3abb0c5f8444/base/boot.jl#L178-L200> just like any user-defined type would be. When you do 1 + 2.5, there is no specific method for +(::Int, ::Float64), so a very generic fallback method <https://github.com/JuliaLang/julia/blob/00528ae4a9cccbbd908fa2b8180f612d3e87b72e/base/promotion.jl#L158> is called: +(x::Number, y::Number) = +(promote(x,y)...) This tries to add a pair of generic numbers by calling promote(1,2.5) to convert 1 and 2.5 to a common type, and then calling the + function on the resulting like-typed pair. The promote function itself is just a generic function, defined in Julia <https://github.com/JuliaLang/julia/blob/00528ae4a9cccbbd908fa2b8180f612d3e87b72e/base/promotion.jl#L122-L124>, that calls the promote_rule function, another generic function, which acts as a user-extensible lookup table, mapping pairs of argument types the desired promotion type. In this case, there's a promote_rule method <https://github.com/JuliaLang/julia/blob/master/base/float.jl#L18-L31> indicating that promote(1,2.5) should convert its arguments to Float64 (using the convert function – yet another user-extensible generic function). The resulting Float64 pair (1.0,2.5) is added in the usual way. That is nice and all, but how can it possibly be fast? The trick is that Julia's type inference knows about generic functions and multiple dispatch and can actually analyze the promote function's behavior, determine that all it does for (Int,Float64) arguments is convert the Int to Float64, then inline that behavior into code for the expression +(promote(x,y)...). As a result, the LLVM code that does 1 + 2.5 is just this: julia> @code_llvm 1 + 2.5 define double @"julia_+;19364"(i64, double) { top: %2 = sitofp i64 %0 to double, !dbg !1885 %3 = fadd double %2, %1, !dbg !1885 ret double %3, !dbg !1885 } I.e. just a signed int to floating-point conversion of the first argument and a floating-point add of the result of that with the second argument. Or in native x86_64 code: julia> @code_native 1 + 2.5 .section __TEXT,__text,regular,pure_instructions Filename: promotion.jl Source line: 158 push RBP mov RBP, RSP Source line: 158 vcvtsi2sd XMM1, XMM0, RDI vaddsd XMM0, XMM1, XMM0 pop RBP ret Despite the layers of abstraction, the meat of the addition method is just two instructions: vcvtsi2sd and vaddsd (the rest is function prologue and epilogue). One can be forgiven for suspecting that this might only work for simple built-in types like Int and Float64, but that's not the case – it's completely general. Among my favorite examples is the SIUnits package <https://github.com/Keno/SIUnits.jl>. This package defines an SIQuantity wrapper type <https://github.com/Keno/SIUnits.jl/blob/a5826ae4992adffb22f00f96f3d61945da5fa113/src/SIUnits.jl#L3-L5> that tracks SI units <https://en.wikipedia.org/wiki/International_System_of_Units> in the type system. To teach Julia to do 1s + 2.5s (where `s` is a constant exported by SIUnits bound to the SIQuantity representing one SI second), all one needs is to provide a method for generically adding <https://github.com/Keno/SIUnits.jl/blob/a5826ae4992adffb22f00f96f3d61945da5fa113/src/SIUnits.jl#L143-L147> two SIQuantity values with the same units: function +{T,S,m,kg,s,A,K,mol,cd}( x::SIQuantity{T,m,kg,s,A,K,mol,cd},y::SIQuantity{S,m,kg,s,A,K,mol,cd}) val = x.val+y.val SIQuantity{typeof(val),m,kg,s,A,K,mol,cd}(val) end It's a bit verbose, I'll admit, but it buys you an awful lot. For example: julia> using SIUnits, SIUnits.ShortUnits # allow `s` for `seconds` julia> 1s + 2.5s 3.5 s That's kind of cool, but what's really cool is that even with the additional layers of abstraction, this still boils down to just two CPU instructions: julia> @code_native 1s + 2.5s .section __TEXT,__text,regular,pure_instructions Filename: /Users/stefan/.julia/SIUnits/src/SIUnits.jl Source line: 139 push RBP mov RBP, RSP Source line: 139 vcvtsi2sd XMM1, XMM0, RDI vaddsd XMM0, XMM1, XMM0 Source line: 140 pop RBP ret With no further definitions you also get much more complex behavior, like, for example, this: julia> (7//5)m/s + (12873918273918273918273981273981273981273980 + 2im)m/s 64369591369591369591369906369906369906369907//5 + 2//1*im m s⁻¹ julia> typeof(ans) SIQuantity{Complex{Rational{BigInt}},1,0,-1,0,0,0,0} (constructor with 1 method) The SIUnits package doesn't mention Complex, Rational or BigInt, yet this works. On Thursday, July 17, 2014 11:56:43 PM UTC-7, Nils Bruin wrote: > > In the end it's just a little helper tool, though. It doesn't really do > things you can't otherwise do. > Of course, you can do anything in one turing complete language that can be done in any other, but that's clearly not a good argument, since if it was, we'd all be programming in machine code. If multiple dispatch is "just a little helper tool" then so are object orientation and functional programming. In a sense, both are subsumed by multiple dispatch / generic functions – multiple dispatch is the object-oriented side of the coin while generic functions are the functional side of the coin. So is this at all helpful for Sage? I'm not sure. The things that you need to make multiple dispatch really useful for something as basic as integer-float addition are: 1. The + operator as a generic function – otherwise the multiple dispatch can't come into play. 2. Enough type information and inlining to allow + to still be sufficiently fast. I'm not sure if either of these are possible in Sage. The speed requirement can be alleviated by only doing multiple dispatch on user-defined types where + doesn't need to be so fast. Alternatively, you can limit operations that are less performance critical than +, such as the EllipticCurve constructor in Nils' example. However, multiple dispatch should not be dismissed out of hand since it can provide elegant solutions to type promotion and mixed-type operations (and many other issues), and it can be made very fast, although this is admittedly not trivial to do. -- You received this message because you are subscribed to the Google Groups "sage-devel" group. To unsubscribe from this group and stop receiving emails from it, send an email to sage-devel+unsubscr...@googlegroups.com. To post to this group, send email to sage-devel@googlegroups.com. Visit this group at http://groups.google.com/group/sage-devel. For more options, visit https://groups.google.com/d/optout.