# ERROR: MethodError: no method matching decompose()

**URL:** <https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904>\
**Category:** New to Julia\
**Created:** [November 10, 2020, 10:52am UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904 "2020-11-10T10:52:38Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![stefkuypers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefkuypers/32/19185_2.png) [@stefkuypers](https://discourse.julialang.org/u/stefkuypers)\
**Post date:** [November 10, 2020, 10:52am UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/1 "2020-11-10T10:52:38Z")

</div>

Hi,

I have defined the following types:

```julia
struct Percentage <: Real
    value::Float64
    Percentage(x) = x < 0 ? new(0) : x > 1 ? new(1) : new(round(x, digits = 6))
end

Base.show(io::IO, x::Percentage) = print(io, "$(x.value * 100)%")

Base.convert(::Type{Percentage}, x::Real) = Percentage(x)
Base.convert(::Type{Percentage}, x::Percentage) = x

Base.promote_rule(::Type{T}, ::Type{Percentage}) where T <: Real = Percentage

import Base: +, -, *, /, <, >, <=, >=, ==

+(x::Percentage, y::Percentage) = Percentage(x.value + y.value)
-(x::Percentage) = Percentage(-x.value)
-(x::Percentage, y::Percentage) = Percentage(x.value - y.value)
*(x::Percentage, y::Real) = Percentage(x.value * y)
/(x::Percentage, y::Real) = Percentage(x.value / y)
<(x::Percentage, y::Percentage) = x.value < y.value
<=(x::Percentage, y::Percentage) = x.value <= y.value
>(x::Percentage, y::Percentage) = x.value > y.value
>=(x::Percentage, y::Percentage) = x.value >= y.value
==(x::Percentage, y::Percentage) = x.value == y.value

Base.max(x::Percentage, y::Percentage) = Percentage(max(x.value, y.value))
Base.min(x::Percentage, y::Percentage) = Percentage(min(x.value, y.value))

mutable struct Health <: Real
    current::Percentage
    Health(current=1) = new(current)
end

Base.convert(::Type{Health}, x::Real) = Health(x)
Base.convert(::Type{Health}, x::Health) = x

Base.promote_rule(::Type{T}, ::Type{Health}) where T <: Real = Health
Base.promote_rule(::Type{Health}, ::Type{Percentage}) = Health

import Base: +, -, *, /, <, >, <=, >=, ==

+(x::Health, y::Health) = Health(x.current + y.current)
-(x::Health) = Health(-x.current)
-(x::Health, y::Health) = Health(x.current - y.current)
*(x::Health, y::Real) = Health(x.current * y)
/(x::Health, y::Real) = Health(x.current / y)
<(x::Health, y::Health) = x.current < y.current
<=(x::Health, y::Health) = x.current <= y.current
>(x::Health, y::Health) = x.current > y.current
>=(x::Health, y::Health) = x.current >= y.current
==(x::Health, y::Health) = x.current == y.current

Base.max(x::Health, y::Health) = Health(max(x.current, y.current))
Base.min(x::Health, y::Health) = Health(min(x.current, y.current))

```

When I run the following code

```julia
h = Health(0.3)
h.current += Health(0.1)

```

I get the following error:

```julia
ERROR: MethodError: no method matching decompose(::Health)

```

I’m guessing it has to do something with the promote rules. Is there a way to fix this? I’m not using that line of code literally; in the real code it looks like:

```julia
h.current += x

```

where x has been assigned a value of type Health.

Thanks in advance,  
Stef

---

<div class="post-metadata">

**Author:** ![hendri54](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hendri54/32/9621_2.png) [@hendri54](https://discourse.julialang.org/u/hendri54)\
**Post date:** [November 10, 2020, 1:32pm UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/2 "2020-11-10T13:32:30Z")

</div>

Adding

```julia
Base.convert(::Type{Percentage}, x :: Health) = x.current

```

does the trick. But `h += Health(0.1)` feels more logical anyway.

I can’t help feeling that this is a lot of boilerplate code for what it does. One way of reducing the boilerplate is

```julia
for op = (:+, :-)
    eval(quote
        Base.$op(a::Health, b::Health) = Health($op(a.current, b.current))
    end)
end

```

and similar (see [the docs](https://docs.julialang.org/en/v1/manual/metaprogramming/#Code-Generation) on code generation.

---

<div class="post-metadata">

**Author:** ![stefkuypers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefkuypers/32/19185_2.png) [@stefkuypers](https://discourse.julialang.org/u/stefkuypers)\
**Post date:** [November 10, 2020, 1:44pm UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/3 "2020-11-10T13:44:51Z")

</div>

Thanks for the trick to reduce the boiler plate code!

> [@hendri54](#):
>
> `Base.convert(::Type{Percentage}, x :: Health) = x.current`

If I add that line I get the following:

```julia
ERROR: StackOverflowError:
Stacktrace:
 [1] *(::Float64, ::Health) at ./promotion.jl:312
 [2] *(::Percentage, ::Health) at /Users/stef/Programming/Julia Projects/loreco-abm/cockpit/utilities/percentage.jl:18
 [3] *(::Health, ::Health) at /Users/stef/Programming/Julia Projects/loreco-abm/cockpit/utilities/health.jl:20
 ... (the last 3 lines are repeated 26660 more times)

```

Maybe I need to add the code I’m actually executing:

```julia
using Main.Types

abstract type Lifecycle end

struct Restorable <: Lifecycle
    health::Health
    damage_thresholds::Vector{Tuple{Percentage, Float64}}
    restoration_thresholds::Vector{Tuple{Percentage, Float64}}
    wear::Float64
    Restorable(
        health=1;
        damage_thresholds=[(1, 1)],
        restoration_thresholds=[(1, 1)],
        wear=0) = new(health,
                    complete(damage_thresholds),
                    complete(restoration_thresholds),
                    wear)
end

function complete(thresholds::Vector)
    thresholds = sort(thresholds)

    if length(thresholds) == 0
        push!(thresholds, (1, 1))
    elseif thresholds[end][1] != 1
        push!(thresholds, (1, thresholds[end][2]))
    end

    return thresholds
end

@enum Direction up down

function change_health(lifecycle::Lifecycle, change::Real, direction::Direction)
    if direction == up
        thresholds = lifecycle.restoration_thresholds
    else
        thresholds = lifecycle.damage_thresholds
    end

    # It's easy when there is only the 100% threshold
    if length(thresholds) == 1
        real_change = thresholds[1][2] * change
        surplus_change = nothing
    else
        multiplier = nothing
        max_change = nothing
        index = length(thresholds) - 1

        while index > 0 && multiplier == nothing
            if lifecycle.health > thresholds[index][1]
                multiplier = thresholds[index + 1][2]

                if direction == up && index + 1 < length(thresholds)
                    # No surplus change can happen above 100%
                    max_change = thresholds[index + 1][1] - lifecycle.health
                elseif direction == down
                    max_change = lifecycle.health - thresholds[index][1]
                end
            end

            index -= 1
        end

        if multiplier == nothing
            # Health is below lowest threshold
            real_change = thresholds[1][2] * change
        else
            real_change = change * multiplier
        end

        if max_change != nothing && real_change > max_change
            surplus_change = (real_change - max_change) / multiplier
            real_change = max_change
        else
            surplus_change = nothing
        end
    end

    if direction == up
        lifecycle.health.current += real_change
    else
        lifecycle.health.current -= real_change
    end

    return change_health(lifecycle, surplus_change, direction)
end

function change_health(lifecycle::Lifecycle, change::Nothing, direction::Direction)
    return lifecycle
end

function use!(lifecycle::Restorable)
    damage!(lifecycle, lifecycle.wear)
end

function damage!(lifecycle::Lifecycle, damage::Real = 0.01)
    change_health(lifecycle, damage, down)
end

function restore!(lifecycle::Lifecycle, damage::Real = 0.01)
    change_health(lifecycle, damage, up)
end

```

The problem occurs when the following lines, at the end of the change\_health() function, are executed:

```julia
 if direction == up
        lifecycle.health.current += real_change
    else
        lifecycle.health.current -= real_change
    end

```

---

<div class="post-metadata">

**Author:** ![thisrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thisrod/32/10642_2.png) [@thisrod](https://discourse.julialang.org/u/thisrod)\
**Post date:** [November 11, 2020, 4:33am UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/4 "2020-11-11T04:33:48Z")

</div>

> [@stefkuypers](#):
>
> If I add that line I get the following:

In psedocode, the infinite recursion is as follows:

- Evaluate `Float64 * Health`

- Promote `Float64` to `Health`, and evaluate `Health * Health`

- `Health` is a subtype of `Real`, so apply `*(x::Health, y::Real) = Health(x.current * y)`, and evaluate `x.current * y`

- `x.current` is a `Percentage`, and `y` is a `Health` is a `Real`, so apply `*(x.current::Percentage, y::Real) = Percentage(x.current.value * y)`, and evaluate `x.current.value * y`

- `x.current.value` is a `Float64`, and we’re back where we started.

Since values have to be `Float64`, you could likely define `*(::Health, ::Float64)` instead of `*(::Health, ::Real)` without breaking anything. That would prevent the recursion. But I think you’re fighting against the design intent of Julia by making `Health` a subtype of `Real`, and you might run in to other problems.

---

<div class="post-metadata">

**Author:** ![stefkuypers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefkuypers/32/19185_2.png) [@stefkuypers](https://discourse.julialang.org/u/stefkuypers)\
**Post date:** [November 11, 2020, 2:32pm UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/5 "2020-11-11T14:32:06Z")

</div>

Thanks @thisrod! That got me thinking. I eventually solved the problem by redefining the types and managed to compact the code significantly thanks to the suggestion from @hendri54.

```julia
module Types

export Percentage, Health

struct Percentage <: Real
    value::Float64
    Percentage(x) = x < 0 ? new(0) : x > 1 ? new(1) : new(round(x, digits = 6))
end

Base.show(io::IO, x::Percentage) = print(io, "$(x.value * 100)%")

Base.convert(::Type{Percentage}, x::Real) = Percentage(x)
Base.convert(::Type{Percentage}, x::Percentage) = x

Base.promote_rule(::Type{T}, ::Type{Percentage}) where T <: Real = Percentage

mutable struct Health
    current::Percentage
    Health(current=1) = new(current)
end

value(x::Percentage) = x.value
value(x::Health) = value(x.current)

import Base: +, -, *, /, <, >, <=, >=, ==, max, min

for type in (Percentage, Health)
    for op in (:+, :-, :max, :min)
        eval(quote
            Base.$op(x::$type, y::$type) = $type($op(value(x), value(y)))
            Base.$op(x::$type, y::Real) = $type($op(value(x), y))
            Base.$op(x::Real, y::$type) = $type($op(x, value(y)))
        end)
    end

    for op in (:*, :/)
        eval(quote
            Base.$op(x::$type, y::Real) = $type($op(value(x), y))
            Base.$op(x::Real, y::$type) = $type($op(x, value(y)))
        end)
    end

    for op in (:<, :<=, :>, :>=)
        eval(quote
            Base.$op(x::$type, y::$type) = $op(value(x), value(y))
            Base.$op(x::$type, y::Real) = $op(value(x), y)
            Base.$op(x::Real, y::$type) = $op(x, value(y))
        end)
    end
    eval(quote
        ==(x::$type, y::$type) = value(x) == value(y)
        ==(x::$type, y::Real) = value(x) == y
        ==(x::Real, y::$type) = x == value(y)
    end)
end

end

```

---

<div class="post-metadata">

**Author:** ![hendri54](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hendri54/32/9621_2.png) [@hendri54](https://discourse.julialang.org/u/hendri54)\
**Post date:** [November 11, 2020, 2:38pm UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/6 "2020-11-11T14:38:50Z")

</div>

Funny - it runs for me:

```julia
struct Percentage <: Real
    value::Float64
    Percentage(x) = x < 0 ? new(0) : x > 1 ? new(1) : new(round(x, digits = 6))
end

Base.show(io::IO, x::Percentage) = print(io, "$(x.value * 100)%")

Base.convert(::Type{Percentage}, x::Real) = Percentage(x)
Base.convert(::Type{Percentage}, x::Percentage) = x

Base.promote_rule(::Type{T}, ::Type{Percentage}) where T <: Real = Percentage

import Base: +, -, *, /, <, >, <=, >=, ==

+(x::Percentage, y::Percentage) = Percentage(x.value + y.value)
-(x::Percentage) = Percentage(-x.value)
-(x::Percentage, y::Percentage) = Percentage(x.value - y.value)
*(x::Percentage, y::Real) = Percentage(x.value * y)
/(x::Percentage, y::Real) = Percentage(x.value / y)
<(x::Percentage, y::Percentage) = x.value < y.value
<=(x::Percentage, y::Percentage) = x.value <= y.value
>(x::Percentage, y::Percentage) = x.value > y.value
>=(x::Percentage, y::Percentage) = x.value >= y.value
==(x::Percentage, y::Percentage) = x.value == y.value

Base.max(x::Percentage, y::Percentage) = Percentage(max(x.value, y.value))
Base.min(x::Percentage, y::Percentage) = Percentage(min(x.value, y.value))

mutable struct Health <: Real
    current::Percentage
    Health(current=1) = new(current)
end

Base.convert(::Type{Health}, x::Real) = Health(x)
Base.convert(::Type{Health}, x::Health) = x
Base.convert(::Type{Percentage}, x :: Health) = x.current

Base.promote_rule(::Type{T}, ::Type{Health}) where T <: Real = Health
Base.promote_rule(::Type{Health}, ::Type{Percentage}) = Health

import Base: +, -, *, /, <, >, <=, >=, ==

# +(x::Health, y::Health) = Health(x.current + y.current)
-(x::Health) = Health(-x.current)
# -(x::Health, y::Health) = Health(x.current - y.current)
*(x::Health, y::Real) = Health(x.current * y)
/(x::Health, y::Real) = Health(x.current / y)
<(x::Health, y::Health) = x.current < y.current
<=(x::Health, y::Health) = x.current <= y.current
>(x::Health, y::Health) = x.current > y.current
>=(x::Health, y::Health) = x.current >= y.current
==(x::Health, y::Health) = x.current == y.current

for op = (:+, :-)
    eval(quote
        Base.$op(a::Health, b::Health) = Health($op(a.current, b.current))
    end)
end

Base.max(x::Health, y::Health) = Health(max(x.current, y.current))
Base.min(x::Health, y::Health) = Health(min(x.current, y.current))

h = Health(0.3)
h.current += Health(0.4)

```

No stack overflow. No error.

---

<div class="post-metadata">

**Author:** ![thisrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thisrod/32/10642_2.png) [@thisrod](https://discourse.julialang.org/u/thisrod)\
**Post date:** [November 12, 2020, 12:27am UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/7 "2020-11-12T00:27:44Z")

</div>

That looks neater, and it fixes the problem for `Health`.

What happens when you multiply two percentages? I expect you’ll get an ambiguity error, because the method dispatcher could treat either argument as a `Percentage` and the other as a `Real`. The error message will suggest how to fix that.

---

<div class="post-metadata">

**Author:** ![stefkuypers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefkuypers/32/19185_2.png) [@stefkuypers](https://discourse.julialang.org/u/stefkuypers)\
**Post date:** [November 12, 2020, 9:53am UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/8 "2020-11-12T09:53:57Z")

</div>

> [@thisrod](#):
>
> What happens when you multiply two percentages?

That made me think 🙂. The way I’m using these types it actually doesn’t make sense to multiply them but I guess I should not force that logic on everyone who would want to use these types. So I added the \* and / operators to the first loop so that there are now \* and / operators which take two Health and two Percentage types. I’ll leave up to the context in which these are used whether it makes sense or not.

---

<div class="post-metadata">

**Author:** ![stefkuypers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefkuypers/32/19185_2.png) [@stefkuypers](https://discourse.julialang.org/u/stefkuypers)\
**Post date:** [November 12, 2020, 9:55am UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/9 "2020-11-12T09:55:59Z")

</div>

> [@hendri54](#):
>
> Funny - it runs for me:
> 
> No stack overflow. No error.

What version of Julia are you running? I was using 1.5.2 at the time I posted. Upgraded to 1.5.3 now but haven’t tested that code anymore since I did a complete overhaul.

---

<div class="post-metadata">

**Author:** ![hendri54](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hendri54/32/9621_2.png) [@hendri54](https://discourse.julialang.org/u/hendri54)\
**Post date:** [November 12, 2020, 7:35pm UTC](https://discourse.julialang.org/t/error-methoderror-no-method-matching-decompose/49904/10 "2020-11-12T19:35:35Z")

</div>

I’m on 1.5.2 (MacOS)
