# Differing construction and conversion for subtypes of Number

**URL:** <https://discourse.julialang.org/t/differing-construction-and-conversion-for-subtypes-of-number/119886>\
**Category:** General Usage\
**Tags:** numbers, constructors\
**Created:** [September 26, 2024, 12:18am UTC](https://discourse.julialang.org/t/differing-construction-and-conversion-for-subtypes-of-number/119886 "2024-09-26T00:18:48Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![brainandforce](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brainandforce/32/211054_2.png) [@brainandforce](https://discourse.julialang.org/u/brainandforce)\
**Post date:** [September 26, 2024, 12:18am UTC](https://discourse.julialang.org/t/differing-construction-and-conversion-for-subtypes-of-number/119886/1 "2024-09-26T00:18:48Z")

</div>

After reading a few threads here, I’ve come to understand that for subtypes of `Number`, construction and conversion should have nearly identical behavior (with one exception: it is expected that construction always returns a new object – relevant for types that are not pure bits).

I’ve been writing packages that implement new `Number` subtypes, and I’ve come across some cases where it may be desirable for conversion and construction behavior to differ.

## Case 1: construction as grade projection in CliffordNumbers.jl

My package, CliffordNumbers.jl, implements several types representing a Clifford number (multivector), elements of a Clifford algebra (geometric algebra). Clifford algebras admit a basis spanned by `2^D` `k`-blades (`k` wedge products of 1-blades), where the _grade_ `k` ranges from zero to `D`, the dimension of the space.

The `CliffordNumber` type is a dense representation of all coefficients associated with each basis blade of a multivector. But in practice, you can get away with sparser representations only representing blades of even grade (`EvenCliffordNumber`), odd grade (`OddCliffordNumber`), or a single grade `K` (`KVector{K}`).

Construction of a `KVector{K,Q}` from an `EvenCliffordNumber{Q}` is interpreted as grade projection: drop all coefficients for basis blades that are not grade `K`. This operation always succeeds, even if the result does not represent the same value as the input:

```julia-repl
julia> x = EvenCliffordNumber{VGA(3)}(1, 2, 3, 4)
4-element EvenCliffordNumber{VGA(3), Int64}:
1 + 2e₁e₂ + 3e₁e₃ + 4e₂e₃

julia> KVector{2}(x) # Grade 0 (scalar) portion is dropped
3-element KVector{2, VGA(3), Int64}:
2e₁e₂ + 3e₁e₃ + 4e₂e₃

julia> ans == x
false

```

This operation always succeeds, unlike conversion, which fails if the value is not representable as a `KVector{K,Q}`, throwing an `InexactError`:

```julia-repl
julia> convert(KVector{2}, x)
ERROR: InexactError: convert(KVector{2}, EvenCliffordNumber{VGA(3), Int64}(1, 2, 3, 4))
Stacktrace:
 [1] convert(T::Type{KVector{2}}, x::EvenCliffordNumber{VGA(3), Int64, 4})
   @ CliffordNumbers ~/.julia/packages/CliffordNumbers/hka3J/src/convert.jl:4
 [2] top-level scope
   @ REPL[49]:1

```

## Case 2: `Sign` type

My unregistered package [SignType.jl](https://github.com/brainandforce/SignType.jl) implements a `Sign` type: an abstraction of the signbit of a signed integer, float, or other real number – logically identical to a `Bool` but arithmetically distinct. (Perhaps I could also call it `Int1` since it subtypes `Signed`.)

In general, `Sign(x::Real)` is identical to `reinterpret(Sign, signbit(x))`. This provides reasonable defaults for inputs that are zero:

```julia-repl
julia> Sign(0)
Sign(+)

julia> Sign(-0.0)
Sign(-)

```

However, conversion of a zero element to a `Sign` throws an `InexactError`, even if the type can represent signed zero:

```julia-repl
julia> convert(Sign, -0.0)
ERROR: InexactError: convert(Sign, -0.0)
Stacktrace:
 [1] convert(::Type{Sign}, x::Float64)
   @ SignType ~/git/SignType.jl/src/SignType.jl:147
 [2] top-level scope
   @ REPL[68]:1

```

The reason for this is that `Sign` can only represent the values +1 and -1. Even if the input type has a signed representation of zero, `Sign` cannot represent 0.

(Note: right now, `convert(Sign, x)` does not throw an error if `x` represents something other than +1 or -1, and just calls `Sign(x)`. The behavior may change to reflect the logic above, so that only representations of +1 and -1 can be converted to `Sign`. I still haven’t finalized the design.)

## The questions

[From the Julia manual](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Conversion-vs.-Construction):

> However, there is a key semantic difference: since `convert` can be called implicitly, its methods are restricted to cases that are considered “safe” or “unsurprising”.

Offhand, I anticipate it would be surprising in some circumstances if 0 supplied to a constructor for a type containing a `Sign` field was implicitly converted to +1 or -1. And while CliffordNumbers.jl is a niche library, I know there are definitely times where implicit grade projection done by the constructor can cause serious issues.

From my interpretation, in general:

- the conversion `convert(T, x)` means “represent the value `x` using the type `T`”. While there may be some loss of precision, you’d expect its result to be equal (`==` or `≈`) to `x`.
- the constructor `T(x)` means “produce an instance of `T` using information from `x`.” This does not imply that `T(x)` needs to be equal to `x` in any sense.

Although the default implementation for a `Number` treats them as the same operation, I think I’ve found cases where it makes sense for them to differ. However, unlike iterators and arrays, there isn’t a documented interface for numbers as far as I know. So my questions are:

- Is my interpretation of the semantics of construction vs. conversion correct?
- Are these semantics necessarily more narrow for `Number`?
- What, if anything, can break if construction and conversion differ for a subtype of `Number`?
