# Problem making two custom number types work together

**URL:** https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735
**Category:** General Usage
**Tags:** question, type, interface
**Created:** [August 1, 2024, 4:05pm UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735 "2024-08-01T16:05:14Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![brianguenter](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brianguenter/32/29519_2.png) [@brianguenter](https://discourse.julialang.org/u/brianguenter)
#### Post date: [August 1, 2024, 4:05pm UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735/1 "2024-08-01T16:05:14Z")

</div>

I’ve defined a type of symbolic number, FastDifferentiation.Node,

```julia
struct Node <: Real
    ...
end

```

that causes this error when used with `Quaternions.jl`:

```julia
julia> using FastDifferentiation,Quaternions

julia> @variables x y z w #creates 4 FastDifferentiation.Node instances
w

julia> q = Quaternion(x,y,z,w)
Quaternion{FastDifferentiation.Node}(x, y, z, w)

julia> a = q/w
ERROR: MethodError: /(::Quaternion{FastDifferentiation.Node}, ::FastDifferentiation.Node) is ambiguous.

Candidates:
  /(q::Quaternion, x::Real)
    @ Quaternions ~/.julia/packages/Quaternions/UZIvR/src/Quaternion.jl:153
  /(a::Number, b::FastDifferentiation.Node)
    @ FastDifferentiation ~/.julia/packages/FastDifferentiation/TzEqR/src/Methods.jl:58

Possible fix, define
  /(::Quaternion, ::FastDifferentiation.Node)

```

The suggested solution

```julia
Possible fix, define
  /(::Quaternion, ::FastDifferentiation.Node)

```

is not practical since it would force `FastDifferentiation` to take a dependency on `Quaternions` or vice versa.

Arithmetic methods for `FastDifferentiation.Node` are defined using the `number_methods` macro, cribbed from `SymbolicUtils.jl`. This creates a definition for / in the `FastDifferentiation.jl` package:

```julia
/(a::Number, b::FastDifferentiation.Node)

```

`Quaternion.jl` also defines a method for /:

```julia
/(q::Quaternion, x::Real)

```

These two methods appear to be the source of the ambiguity. I want the method defined in `Quaternion.jl` to be called but am having trouble figuring out how.

What is the best way to fix this?

---

<div class="post-metadata">

### Author: ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)
#### Post date: [August 1, 2024, 4:14pm UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735/2 "2024-08-01T16:14:59Z")

</div>

- You could modify `number_methods` to not define `/` on `FastDifferentiation.Node`
- You could use `invoke` at the call site to force a specific method of `/`: [Essentials · The Julia Language](https://docs.julialang.org/en/v1/base/base/#Core.invoke)
- ~~You could use `isdefined` to conditionally define the more specific `/`~~ as Oscar mentions below, this is a bad idea, and it’s better to use package extensions: [5. Creating Packages · Pkg.jl](https://pkgdocs.julialang.org/v1/creating-packages/#Conditional-loading-of-code-in-packages-(Extensions))

---

<div class="post-metadata">

### Author: ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)
#### Post date: [August 1, 2024, 4:31pm UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735/3 "2024-08-01T16:31:28Z")

</div>

This last suggestion is a pretty bad one. Hacking this based on symbols is very brittle. The better version of this would be a package extension.

---

<div class="post-metadata">

### Author: ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)
#### Post date: [August 1, 2024, 5:57pm UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735/4 "2024-08-01T17:57:17Z")

</div>

Without knowing more about your `Node`, it’s hard to say whether the following is practical and reasonable. But if it is, an idiomatic solution is to remove the definition of `/(::Number, ::Node)` (and likewise for other operators) and instead define a set of [`promote_rule`](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Defining-Promotion-Rules) to make this work. In this version, you would likely only define `/(::Node, ::Node)` and a variety of `promote_rule`.

---

<div class="post-metadata">

### Author: ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)
#### Post date: [August 2, 2024, 12:20am UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735/5 "2024-08-02T00:20:27Z")

</div>

Julia is quite good at having different numerical types interoperate.  
What you are seeing is the generality of `Number`; it is the most abstract supertype for all things that are in some manner “computationally algebraic”.

One sort is numerically valued  
e.g. {Int8, Float32, Rational{Int64}, Complex{Float64}, Quaternion{Float32}}

```julia
      Integer, Rational, AbstractFloat ∈ subtypes(Real)
      `Real`, Complex, Quaternion ∈ subtypes(Number)
 
      Complex and Quaternions are operationally available
          through algebras over 2-tuples and 4-tuples.
          [and in other more mathy ways]

```

The other sort is implicitly evaluable, while carrying no explicit value.  
These are the symbolic-numeric types. They may be symbols or structured pairs/triples…, or evaluation trees with symbolic or symbolic + guiding adornments as leaves. e.g. `FastDifferentiation.Node`. See:

- [symbolic utils](https://symbolicutils.juliasymbolics.org/stable/)
- [functions restricted to Numbers](https://symbolics.juliasymbolics.org/dev/manual/variables/#A-note-about-functions-restricted-to-Numbers)

====

Quaternion division by a scalar is definable using the operations  
abs2, complement, reciprocal, and quaternion construction.

While Nodes are not traditional (numerical) scalars, Real values are scalars.  
We have `supertype( FastDifferention.Node ) == Real`.

for illustrative purposes only way

```julia
using Quaternions
using FastDifferentiation

const Q = Quaternion
const Node = FastDifferentiation.Node

sq(q::Q) = q.s; 
xq(q::Q) = q.v1;
yq(q::Q) = q.v2; 
zq(q::Q) = q.v3;

complement(q::Q) = Q(sq(q), -xq(q), -yq(q), -zq(q))

function reciprocal(q::Q)
   a = abs2(q) # sum of |element|^2
   c = complement(q)
   Q( sq(c) / a, xq(c) / a, yq(c) / a, zq(c) / a )
end

Base.:(/)(q::Q, n::Node) =
    Q( sq(q) / n, xq(q) / n, yq(q) / n, zq(q) / n )

@variables a b c d
q = Quaternion(a, b, c, d)
qoa = q / a
# Quaternion{FastDifferentiation.Node}((a / a), (b / a), (c / a), (d / a))
qod = q / d
# Quaternion{FastDifferentiation.Node}((a / d), (b / d), (c / d), (d / d))

difference = qoa - qod
#=
Quaternion{FastDifferentiation.Node}(
((a / a) - (a / d)), ((b / a) - (b / d)), ((c / a) - (c / d)), ((d / a) - (d / d))
)
=#

product = qoa * qod
#=
Quaternion{FastDifferentiation.Node}(
((((a / a) * (a / d)) - ((c / a) * (c / d))) - (((b / a) * (b / d)) + ((d / a) * (d / d)))), 
((((a / a) * (b / d)) + ((b / a) * (a / d))) + (((c / a) * (d / d)) - ((d / a) * (c / d)))), 
((((a / a) * (c / d)) + ((c / a) * (a / d))) + (((d / a) * (b / d)) - ((b / a) * (d / d)))), 
((((a / a) * (d / d)) + ((d / a) * (a / d))) + (((b / a) * (c / d)) - ((c / a) * (b / d))))
)
=#

```

---

<div class="post-metadata">

### Author: ![brianguenter](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brianguenter/32/29519_2.png) [@brianguenter](https://discourse.julialang.org/u/brianguenter)
#### Post date: [August 4, 2024, 6:47pm UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735/6 "2024-08-04T18:47:05Z")

</div>

The problem does seem to be with the methods defined with `Number` arguments. I cribbed the `number_methods` macro from SymbolicUtils.jl to automatically generate arithmetic, trigonometric, etc., methods for the Node type.

The two lines marked `problematic` in this code snippet from `number_methods` are causing the problem:

```julia
  expr = quote
            (f::$(typeof(f)))(a::$T, b::$T) = $rhs2
            (f::$(typeof(f)))(a::$T, b::Real) = $rhs2
            (f::$(typeof(f)))(a::Real, b::$T) = $rhs2
            (f::$(typeof(f)))(a::$T, b::Number) = $rhs2 #This is a problematic line
            (f::$(typeof(f)))(a::Number, b::$T) = $rhs2 #This is a problematic line
        end

```

Are the two lines that generate functions with `Number` arguments necessary? I commented them out and all my tests pass. Maybe there are obscure cases where you need them? Does anybody know why they are there?

For sure they cause exceptions to be thrown for `Quaternion{Node}` and `Complex{Node}` types and if they are removed the errors go away.

---

<div class="post-metadata">

### Author: ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)
#### Post date: [August 4, 2024, 7:28pm UTC](https://discourse.julialang.org/t/problem-making-two-custom-number-types-work-together/117735/7 "2024-08-04T19:28:29Z")

</div>

I would err on the side of not including the `::Number` methods until you have an obvious use case for them.

`::Number` methods give a default implementation for how your type should interact with all other custom number types. I think this makes sense if your type is e.g. a simple wrapper over an Int or a Float, but if it’s anything more complex you probably want the user to make a deliberate decision.

You can also see that the `number_methods` macro has options `skip_basics` and `only_basics`. So the authors clearly expected some situations where you wouldn’t want to define the full set of methods.
