Wrap and inherit Number

question

#7

No no, I understand. But I had hoped that if I wanted to mimic, say, Float64, then there would have been an easy way to do that. Kind of like, in stead of starting from scratch: “here is a new type, you don’t know anything about it, let me tell you all you need to know about it”, starting from the top and “adjusting down”: “here is a new type, it’s exactly like Float64, except in this and this and that case”…


#8

You could do something along the lines of

module PseudoNumbers

export PseudoNumber, getnumber  # this is the interface

abstract type PseudoNumber end

function getnumber end

import Base: +                  # and the rest ...

# should define for univariate and multivariate operators using macros,
# this is just an example
+(a::T, b::T) where {T <: PseudoNumber} = T(getnumber(a) + getnumber(b))

end

and then all the code you would need to write is

using PseudoNumbers

struct MyFloat{T} <: PseudoNumber
    x::T
end

PseudoNumbers.getnumber(mf::MyFloat) = mf.x

MyFloat(1.0) + MyFloat(2.0) # works out of the box

but I expect you would have to think about corner cases (lifting reals, how to combine two different types, etc).


#9

One option that we discussed a while back (https://github.com/JuliaLang/julia/pull/3292) is for a package to define a @delegate macro, so that you could do e.g.:

struct A{T<:Number}
    x::T
end
@delegate A.x Number

and it would use methodswith(Number, Base) to find all of the (exported) ::Number methods and define corresponding a::A methods that pass through a.x.

I don’t think anyone ever actually tried implementing such a thing, but it would be an interesting experiment.

(TypedDelegation.jl might be a reasonable place to work on this.)


#10

I think that would be useful. Before you suggest I do that, I’ll just add: I suck at meta programming…


#11

While you can implement all the methods that a normal number has for your custom type, there is one thing where it gets tricky: do you want to inherit from Number or not? If you don’t, you can’t pass instances of your type to functions that only accept Numbers, even if your type behaves like a number. I don’t think there is a good solution to this right now, we’ll probably have to wait for either traits or interfaces in some future julia version for an elegant solution.


#12

automatic promotion?


#13

Automatic promotion should be avoided for custom types. They are good only for built-in types.


#14

A few weeks ago @Per shared the intention to work on a library that may suit your needs, if your type will be a subtype of AbstractFloat (but it may not be the case):


#15

Ref this issue https://github.com/JuliaLang/julia/issues/9821.


#16

Sounds great. I’m working on the tests now, but this whole thing is just for my idea with the Angle type. I defined all the functions needed for the conversions and the trigonometry, but now “all” I need is for this Angle type to behave like a Number (or AbstractFloat) in every other situation…


#17

That’s totally wrong. The whole point of Julia’s promotion mechanism is that it is not just for “built-in” types, and is equally extensible to user-defined types. https://docs.julialang.org/en/latest/manual/conversion-and-promotion/

The real problem is that promotion rules are defined for mixed operations on different types, e.g. if you do a + b where a and b are different Number types then it will call promote(a,b) by default. This doesn’t help you at all for exp(x), for example, or for non-Number types, where the promotion code is not called in the default methods.


#18

The @forward macro from Lazy.jl makes this a bit easier by allowing you to delegate a given list of functions to a specific field of a type: https://github.com/MikeInnes/Lazy.jl/blob/master/src/macros.jl#L267

(this is not nearly as fancy as the hypothetical @delegate, but at least it exists right now)


#19

DataStructures.jl has an implementation of @delegate. If there’s interest, that could be turned into a (1 or 2 macro) package.

Cheers,
Kevin


#20

To be clear, that implementation of delegate is not as full featured as @stevengj’s described version above.


#21

Very cool.

I’d like to add some clarity as well:
The “thing” that would be most useful (and maybe I should open a new topic for this), is if we could just have something that acts exactly like a Number (in every meaning), but that we can override some of the functions that already accept it as an argument.

So here’s an instance of Float64:
Float64(33.0)
We can do all sorts of things with it. Here is an instance of a custom type A:
A(33.0)
We can do exactly the same things like the Float64 version from before. Now I change the existing sin function to the following (silly):
sin(x::A) = 2x.
There. Instances of A do everything, say, floats can do, and I modified the behavior of a few select functions. Winning.


Angle package
#22

How would you address https://github.com/JuliaLang/julia/issues/9821#issuecomment-70381499?


#23

I liked the idea of TypedDelegation.jl very much, will these macros be in Base by any chance at some point?


#24

Almost certainly not, since they are unlikely to be needed for anything in Base.


#25

Thank you @stevengj, good to know.


#26

It’s not totally true, see e.g length, size etc. for Generator in generator.jl. Some time ago I started a branch with this delegate functionality, but thought I indeed needed to find more cases to push that into Base! In my case, I needed indirection for the iteration protocol (similar to the iter function in python).