Multiple-dispatch depending on output type?

I was just thinking about multiple-dispatch and something just occurred to me. Currently, there is multiple-dispatch based on input types but what if it can dispatch on output type as well?

For example sqrt(-1) will fail currently but sqrt(-1)::Complex should succeed because sqrt is dispatching on output type. One way around this currently is to have the output type as a parameter e.g. sqrt(-1, outType = Complex) then it will dispatch based on Type{Complex}. For example

import Base.sqrt
sqrt(x, ::Type{Complex}) = sqrt(Complex(x))
sqrt(-1, Complex) # works fine

Of course this a very weird idea as most of the time we write

x = sqrt(-1)

and at this point, x isn’t defined, so it will fail; but

x::Complex = sqrt(-1)

should work if multiple-dispatch is on output type.

I can see how implementing this sort of things will rely on more magic, so may not be a good idea but I think this may be something not many people have talked about. So worth mentioning.

We already do this for some functions, but in the first argument, notably trunc, round, floor, ceil.

3 Likes

Ok. Is it available as a language feature or is it defined function by function in the core implementation?

No, it’s defined function by function. But the nice thing about Julia is that it is easy to add new methods to existing functions!

2 Likes

Are you talking about trunc(T, x) methods? I am not sure this is what @xiaodai was asking about, my understanding is that he would like

x::Complex = sqrt(-1)

to be lowered into a form where Complex becomes an argument to sqrt.

2 Likes

If, then it should probably be: x = sqrt(-1)::Complex.

But this is already taken for type assertion syntax.

Also, I am not saying that the original idea is a good one, just trying to interpret what was suggested.

I actually think that making caller context available to functions in this subtle, optional way is not a good design choice. If the function needs that information, it should be explicit in the argument list.

Haskell dispatches on output type (but I’m not saying Julia should).

One thing: typically in Julia, when making a method of a function where you want to dispatch on a type, in particular to indicate the return type, it is the first argument, for example, convert, rand, zero, etc., so if you did this, it probably should be something like: sqrt(Complex, -1)

2 Likes

What if your function accepta a function as argument. It that case do we put the type second to allow for the do notation?

https://github.com/JuliaLang/julia/issues/19206

1 Like

Am I missing something?

Why would someone want to write sqrt(Complex, -1) when they could instead write sqrt(Complex(-1))?

Doesn’t the latter more concisely and accurately describe the calculation you want to perform?

(Maybe this wasn’t a thing in 2017 - I realize by the standards of Julia this is a super old thread.)

Let’s not pick at old comments in old topics, please. We generally keep topics open here because they gather search rank and there could be relevant new information that comes to light, but we don’t need to resurrect threads to just argue about asides. You can see that nobody ran with that suggestion in the seven years since.

8 Likes

That’s precisely why I am asking the question…

Perhaps not with Complex, but one can imagine other types where there is overhead in creating T(-1) first. E.g. some field where sqrt is defined on some embedding of whole numbers.

1 Like

Can you explain this in more detail? I didn’t understand what point you wanted to make here

So, say you have some algebraic structure GAlgebra which allows square roots. Let’s say its representation is somewhat complicated, but the whole numbers can be embedded. You don’t want to write sqrt(GAlgebra(-1)) because creating GAlgebra(-1) is quite involved, and the general sqrt for GAlgebra is also quite involved. But, due to circumstances, constructing the square root of GAlgebra(-1) directly from -1 (or any other whole number) is simple.

You would likely want something like sqrt(GAlgebra, -1). The typical way in julia would be to make it general, with a fallback like sqrt(::Type{T}, x) where {T} = sqrt(convert(T, x)), and allow specializations for structs like GAlgebra or SMatrix{N, N} where N

1 Like

Two reasons:

  • overhead in creating T(-1) first

  • Converting a value or real type to complex is a loss of information, so the sqrt method you end up calling may quite possibly be less efficient, because it doesn’t know the imaginary part is zero.

A simple example is Complex{BigFloat}. It’s both costly to work with, and not ultimately implemented in Julia (the implementation relies on the venerable MPFR C library). The latter severely restricts the kinds of compiler optimizations that are possible.

1 Like

To solve the type instability problem?

Right I see, to simplify this a bit:

  • you want a type GAlgebra as an output
  • calculating sqrt(GAlgebra(-1)) would be slow in general because sqrt(GAlgebra(<anyfloat>)) is some complicated method
  • but there is a much simpler implementation sqrt(GAlgebra(-1)) because -1::GAlgrbra is a special case (excuse the abuse of notation)

This is a “runtime” special case. It depends on the value at runtime.

Therefore it should be implemented (it could be implemented?) in the function as a branch statement.

function sqrt(galgebra::GAlgebra)
    if galgebra.something_value == -1
        ...
    else
        ...

Julia doesn’t currently provide a way to dispatch methods using values as well as types. Nor (IMO) should it.

I’m aware that’s not what is being asked for with a syntax like

sqrt(GAlgebra, -1)

Can you get the same behavior by “overloading” sqrt (providing a method for sqrt) with a method which takes an argument type as the first argument?

sqrt(some_type, value)
    if some_type isa GAlgebra ...

Perhaps this is what was being suggested and I just missed the point being made or otherwise misunderstood here?