Struct with Multiple Dispatch

Hello,

I’m new to Julia and am watching the Juliacon 2020 video series. I have a question about the use of a mutable struct in this video here.

From what I am seeing, I think he defines a mutable struct with some fields, then defines two methods for that struct. So If I wanted to do something similar I could do

a = 3
b = 5
# Example of multiple dispatch with a mutable struct
begin
	mutable struct MyStruc
		C::Float64
		a::Int64
	end
	# Multiple dispatch of struct
	MyStruc(C::Float64; aValue=a) = MyStruc(C,aValue)
	MyStruc(C::Int64; bValue=b) = MyStruc(C,bValue)
end

That seems to work. But the syntax is strange to me, doing MyStruc = MyStruc. Am I correct there are two methods for this struct? I’m using Pluto and I don’t see “MyStruct with two methods” above the cell with the struct defined (unlike when I define a function with multiple methods and it does say the number of corresponding methods above the cell).

I also tried the following

# Example of multiple dispatch with a mutable struct
begin
	mutable struct MyStruc
		C::Float64
		a::Int64
	end
	# Multiple dispatch of struct
	MyStruc(C::Float64; aValue=a) = MyStruc(C,aValue)
	MyStruc(C::Int64, a::Float64) = MyStruc(C,a)
end

But when I try to use the second method like this

TestStruct = MyStruc(3, 101.1)

I get a StackOverflowError. Not sure why though - would appreciate any comments and insight on this.

mutable struct MyStruc
      C::Float64
      a::Int64
end

is enough.

julia> TestStruct = MyStruc(3.1, 101)
MyStruc(3.1, 101)

julia> TestStruct = MyStruc(3, 101)
MyStruc(3.0, 101)

but:

julia> TestStruct = MyStruc(3, 101.1)
ERROR: InexactError: Int64(101.1)
Stacktrace: ...

to avoid this you could define a method (multiple dispatch):

MyStruc(C::Int64, a::Float64) = MyStruc(C, Int(round(a)))

then you get:

julia> TestStruct = MyStruc(3, 101.1)
MyStruc(3.0, 101)

[quote=“TI36XPro, post:1, topic:53118”]
Am I correct there are two methods for this struct?

No, there are four. You can check with the methods function:

julia> methods(MyStruc)
# 4 methods for type constructor:
[1] MyStruc(C::Float64; aValue) in Main at REPL[2]:2
[2] MyStruc(C::Float64, a::Int64) in Main at REPL[1]:2
[3] MyStruc(C::Int64; bValue) in Main at REPL[3]:1
[4] MyStruc(C, a) in Main at REPL[1]:2

[1] and [3] are the methods you defined, and [2] and [4] are the default methods that are always defined when you create a new struct (unless you define inner constructors).

Thank you for the reply. Why can’t I do

MyStruc(C::Int64, a::Float64) = MyStruc(C, a)
MyStruc(-3, 5.4)

I’m confused because in my original declaration I have C::Float64 and it didn’t seem to mind defining a new method with that as an Int64. But if I try it the other way around, with a where it is initially an Int64 in my original declaration and then later on I try to define a new method with it as a Float64, it gives me a stackoverflow error and I have to use round as you show, to work correctly.

Because of what it means to go from Int to Float and vice versa?

It doesn’t have anything to do with the order of the definition. The problem is that in your second example, you define the method MyStruc(C::Int64, a::Float64) that always calls itself. Thus, you get a stack overflow (basically, the method ends up calling itself over and over again, so it can never return). In your first example, your methods have keyword arguments and call methods without keyword arguments, so none of the methods call themselves.

This does call only itself since 5.4 isa Float64 and it matches only your method definition above but not your struct, which has an Int64 as 2nd element. Therefore the stack overflow. It says you actually, which method it calls:

julia> TestStruct = MyStruc(3, 5.4)
ERROR: StackOverflowError:
Stacktrace:
 [1] MyStruc(::Int64, ::Float64) at ./REPL[64]:1 (repeats 79984 times)

you see it does not call your struct definition.

Thank you both - I think that makes a little more sense now.

One last question - you both said I was creating a method that called itself. Isn’t that what is happening here too? No keyword arguments are used to distinguish the method from itself like before. Yet, this does work. Because the presence of the Int function?

MyStruc(C::Float64, a::Float64) = MyStruc(C, Int(round(a)))

yes, that then matches your struct definition better with an Int64 at the second place. So that gets dispatched as you probably want.

julia> MyStruc(C::Float64, a::Float64) = MyStruc(C, Int(round(a)))
MyStruc

julia> TestStruct = MyStruc(3, 101.1)
MyStruc(3.0, 101)

This does not call itself because it has the signature MyStruc(::Float64, ::Float64) and converts the second float to Int, so it calls MyStruc(::Float64, ::Int) then. This is a different method of the same function. See:

Thank you all for explaining this to me and answering my questions.

One last thing. Am I technically creating multiple dispatches of a function called MyStruc by doing MyStruc(C::Float64; aValue=a) = MyStruc(C,aValue) and it just so happens I’ve also already defined a new type called MyStruc? I could just as easily have done the following?

begin
	mutable struct MyStruc
		C::Float64
		a::Int64
	end
	# Multiple dispatch of struct
	MyFunction(C::Float64; aValue=a) = MyStruc(C,aValue)
	MyFunction(C::Int64; bValue=b) = MyStruc(C,bValue)
end

If that’s the case - I’m not really creating multiple dispatches of a struct here am I? Sorry if I am overthinking things here.

I think it a bit misleading if you speak of “Multiple dispatch of struct”. There is multiple function dispatch. I’m starting with a fresh REPL here. I you define your struct MyStruc, you implicitly define a function MyStruc with two methods:

julia> mutable struct MyStruc
           C::Float64
           a::Int64
       end

julia> methods(MyStruc)
# 2 methods for type constructor:
[1] MyStruc(C::Float64, a::Int64) in Main at REPL[1]:2
[2] MyStruc(C, a) in Main at REPL[1]:2

but it does not do what you want, since

julia> MyStruc(123, 4.56)
ERROR: InexactError: Int64(4.56)
Stacktrace: ...

Therefore you define another method like

julia> MyStruc(C::Int64, a::Float64) = MyStruc(C, Int(round(a)))
MyStruc

julia> methods(MyStruc)
# 3 methods for type constructor:
[1] MyStruc(C::Float64, a::Int64) in Main at REPL[1]:2
[2] MyStruc(C::Int64, a::Float64) in Main at REPL[3]:1
[3] MyStruc(C, a) in Main at REPL[1]:2

julia> MyStruc(123, 4.56)
MyStruc(123.0, 5)

In your case then you create a 2nd function, MyFunction with two methods, dispatching on Float64 and Int64:

julia> MyFunction(C::Float64; aValue) = MyStruc(C,aValue)
MyFunction (generic function with 2 methods)

julia> MyFunction(C::Int64; bValue) = MyStruc(C,bValue)
MyFunction (generic function with 2 methods)

julia> methods(MyFunction)
# 2 methods for generic function "MyFunction":
[1] MyFunction(C::Int64; bValue) in Main at REPL[7]:1
[2] MyFunction(C::Float64; aValue) in Main at REPL[6]:1

The problem with the 2nd parameter is not resolved by that since without the 3rd method for MyStruc above you get the same InexactError if you do MyFunction(123, bValue=4.56). Therefore MyFunction does not help.

BTW: MyFunction(C::Float64; aValue=a) assumes a being a global variable or a constant present in the current scope.

1 Like