Argument validation using Struct

I am having trouble executing the code snippet below that was contributed on an old thread.

My objective is to define a struct that I can use to validate arguments inside function definitions. Any argument that is not the correct type should throw an error. The key point is that arguments are validated using types, not in the body of the function definition.

I realise I could validate the arguments in a separate “validation function”, which would be called in the body of each function definition. In my case this may provide a really neat, concise and readable solution as I have many functions with similar argument types.

    value :: Int

    function OddInt(x :: Int)
        if isodd(x)
            return new(x)
        else
            throw(DomainError("$x is not odd"))
        end
    end
end

function thisoddball(n :: OddInt)
    return n
end

thisoddball(3)

The last call to thisodball(3) returns a method type error:
Error: Methoderror: No method matching this oddball(::Int64)

As I understand it …thisoddball is testing to see that the argument n is of type OddInt. The structure OddInt however is returning a type Int64, and so the it produces a Method Error.

Calling OddInt(3) or OddInt(2) work just as expected.
Calling a thisoddball(3) produces the method error described.

I should add that my situation has nothing to do with Odd numbers. This is just a self contained example that explains the issue as simply as possible.

Sorry the posted code snippet missed first line. I have reposted in full below.

struct OddInt
    value :: Int

    function OddInt(x :: Int)
        if isodd(x)
            return new(x)
        else
            throw(DomainError("$x is not odd"))
        end
    end
end

function thisoddball(n :: OddInt)
    return n
end

thisoddball(3)

The structure OddInt however is returning a type Int64, and so the it produces a Method Error.

The structure OddInt returns an object of type OddInt, not Int64.

Everything works as expected if you pass an OddInt object to the function thisoddball, because there in no method thisoddball defined for Int:

julia> thisoddball(OddInt(3))
OddInt(3)

The rest of your code can be shortened like this

struct OddInt
    value :: Int

    OddInt(x :: Int) =
        isodd(x) ? new(x) : throw(DomainError("$x is not odd"))
end

thisoddball(n :: OddInt) = n

Thanks, passing an OddInt object does indeed work.
To help me understand … I was thinking that I could call “thisoddball(3)”. Not have to use “thisoddball(OddInt(3))”.

If I have the correct terminology, I thought that:

  1. OddInt was an inner constructor for the structure OddInt??
  2. (n :: OddInt) was a type assertion over the argument n?? In my head, that would call the OddInt constructor, and in the process it would execute any validation logic contained in that constructor (is n odd?). The “new” part of the constructor would then create the validated anonymous object??

Instead it feels like I am using a function (OddInt) as a parameter to another function “thisoddball”?

In a simpler scenario; for a function defined as mydouble(x::Int) = x * 2
I would make the call ‘mydouble(2)’, not `mydouble(Int(2))'.

Dispatch does not work that way. If you define a method foo(x::Bar), that means it will only accept inputs of type Bar (or subtypes of Bar if it is an abstract type), it will not attempt to convert input to Bar.

When you define

thisoddball(n :: OddInt) = n

and you call thisoddball(3), the compiler gets the type of the input, which is Int64, and checks if there is a method thisoddball(::Int). There is not such method, so you get an error.

What you can do instead if this:

thisoddball(n::OddInt) = n
thisoddball(n::Int) = thisoddball(OddInt(n))

As for this:

2 is already of type Int so mydouble(2) should work, while mydouble(2.0) or mydouble(0x2) should not.

2 Likes

In case you want to use thisoddball(3) you have to define it: thisoddball(n::Int) = OddInt(n).

  1. correct: OddInt is in the inner constructor;
  2. wrong: you should read (n :: OddInt) as part of the definition of thisoddball(n :: OddInt). There is only one function that only takes OddInt.
1 Like

Thank you both (DNF & mzaffalon) for thoughtful, concise responses. Both answers help me to better understand topics I have grappled with. Hopefully it may also help others.

DNF: (and similar answer from mzafflon) - where you propose what I could do instead… I think of this as multiple dispatch. In my head, you are defining two methods for thisoddball. One method with an OddInt Object argument type, the second with an Int argument type.

Other parts of your answers also resonated with me. (Not how dispatch works, doesn’t attempt to convert input, part of the definition of thisoddball…)

Thanks again.

Yes, exactly. This is a very common pattern in Julia code.