Stream: helpdesk (published)

Topic: Code Design - Fields in Struct or Arguments in Function


view this post on Zulip Davi Sales Barreira (May 17 2022 at 23:34):

Code design question here. I'm writing a drawing package, and in the package, there are several possible structs referring to geometric objects. For example, circle, triangle, rectangle. My idea is to create a drawobject(o::MyStruct) function that dispatches in each struct. The question is, should the parameters of the geometric objects go inside the structs, or appear only as arguments in the function?
For example:

struct Circle
 radius::Real
 label::String
end
c = Circle(1,"C")
drawobject(c)

Or should I go with:

abstract type Circle end
drawobject(c ; label="C", r=1.0)

Is there any heuristic to know when to use one or the other in Julia?

view this post on Zulip Mason Protter (May 17 2022 at 23:39):

I think they should be in the struct

view this post on Zulip Davi Sales Barreira (May 17 2022 at 23:40):

If I may. Why would you go with that? I'm trying to develop an intuition for this sort of thing.

view this post on Zulip Mason Protter (May 17 2022 at 23:42):

Well, there's a lot of different considerations, but I think that generally arguments that are a 'bundle of connected things' usually should be bundled together into a struct when possible

view this post on Zulip Mason Protter (May 17 2022 at 23:45):

For instance, to take a mathematical example, we could say write a function for finding the norm of a complex number like this:

norm(re, im) = re^2 + im^2

but the real and imaginary parts of a complex number are tightly connected things and we usually want to think of it as just one thing. Why juggle around a bunch of different parts? Rather, we prefer to write

norm(z) = real(z)^2 + imag(z)^2

view this post on Zulip Mason Protter (May 17 2022 at 23:45):

That said, it's possible your label isn't really an 'instrinsic part' of the circle and you might prefer to split that out of the struct

view this post on Zulip Davi Sales Barreira (May 17 2022 at 23:48):

Makes total sense. Would you say that the heuristic here would be to place inside the struct the fields that are specific ("unique") for that struct, and leave as an argument those that appear in all of them?

view this post on Zulip Davi Sales Barreira (May 17 2022 at 23:48):

I mean, this would even make sense with the fact that structs in Julia do not inherit stuff.

view this post on Zulip Davi Sales Barreira (May 17 2022 at 23:50):

Your explanation actually made me realized I had the whole thing reversed. I was placing the commonly shared parameters in the struct, and leaving everything else out.

view this post on Zulip Mason Protter (May 17 2022 at 23:51):

Davi Sales Barreira said:

Makes total sense. Would you say that the heuristic here would be to place inside the struct the fields that are specific ("unique") for that struct, and leave as an argument those that appear in all of them?

No, I don't think I'd say that. I'd just say that the stuff that makes a circle a circle should be inside the circle struct.

view this post on Zulip Davi Sales Barreira (May 18 2022 at 00:04):

Ok. I see your point. But then I still have a conundrum. My package is for drawing, so I have properties such as linethickness and linecolor and fillcolor... Would you say that all of these drawing attributes should be inside the struct or outside?

view this post on Zulip Davi Sales Barreira (May 18 2022 at 00:05):

At some point, for example, I could end up with a struct TRex, which would have stuff like head and headcolor...

view this post on Zulip Davi Sales Barreira (May 18 2022 at 00:07):

I know this sound silly, but the idea here is that the struct are like the "atomic" drawings. And they are going to be things beyond simple geometric objects... Thus, I'm trying to understand how should one deal with all this complexity.

view this post on Zulip Mason Protter (May 18 2022 at 00:07):

Yeah, I think this just comes down to taste and ergonomics at that point. I'd try out some different designs and see what you like

view this post on Zulip Mason Protter (May 18 2022 at 00:07):

Sometimes it makes sense for stuff like the line thickness and whatnot to be in the struct and sometimes it doesn't

view this post on Zulip Davi Sales Barreira (May 18 2022 at 00:07):

I see. I was wondering if there was a sort of guideline for this stuff. So I guess not.

view this post on Zulip Davi Sales Barreira (May 18 2022 at 00:08):

Yet, thanks a lot @Mason Protter !

view this post on Zulip Davi Sales Barreira (May 18 2022 at 00:08):

You've already helped me a lot, not only in this post, but in many other posts here in Zulip. I'm truly grateful!

view this post on Zulip Mason Protter (May 18 2022 at 00:09):

E.g. in Plots.jl when you do something like

xs = 1:10
plot(xs, xs .^ 2, title="a title", linewidth=2)

this actually makes a struct that contains all that data (as well as a bunch of other default data) because it's easier to move around and combine with other plots and whatnot.

view this post on Zulip c42f (May 19 2022 at 00:19):

Yeah. Though that's an implementation detail of Plots. I think Davi's question is more about what the user-facing API should be?

For this I think you'd also be well served by looking at what other drawing packages do. There's so many to take inspiration from, including ones from other languages. Usually you can mimic the API from another language's packages into Julia with very little effort (it's the other direction which is often not so easy).

There's a lot of options! For example, Luxor.jl doesn't have structs at all, it's a very stateful imperative drawing API. That might not be what you want but personally I do find that style very easy for quick drawings. As drawings get larger and you want to compose parts, having more explicit state and functional style is less confusing, I think.

view this post on Zulip c42f (May 19 2022 at 00:22):

So I guess I'd ask: What are the structs for?

Having something like

drawobject(Circle(center, radius), stroke_width=1.0)

is strictly more typing than

circle(center, radius, stroke_width=1.0)

But it might be worth it if you think the user typically wants to pass Circle objects around and manipulate them. It depends on your expected use case.

view this post on Zulip arbitrandomuser (Jun 01 2022 at 17:24):

also check out https://jkrumbiegel.com/Layered.jl/dev/ for a different take on 2d graphics api


Last updated: Dec 28 2024 at 04:38 UTC