What would be the harm of allowing dot syntax for procs?

add :: proc(a: int, b: int) -> (c: int) {
    return a + b
}

half :: proc(c: int) -> (d: int) {
    return c / 2
}

example :: proc() {
    a, b := 2, 3
    fmt.println(a.add(b).half())
//  same as     half(add(a, b))
}

I remember reading about it in the FAQ I think where it was mentioned that some testing was done but it didn’t provide much benefit. Can there be a more specific explanation? In my opinion, it is just syntax sugar that can be ignored if not to one personal preference. Selector Call Expressions already exist in the language so I doubt it would add much complexity or inconsistency.

EDIT: Lmao this is me being dumb. I read the FAQ again and indeed there is a proper explanation. I just remember the explanation not resonating with me and forgot. The point about it being ambiguous is true but also there can be workarounds. Fields can be used with the selector call expression but if the first argument of the proc doesn’t match then it doesn’t get passed in. Same deal with procs marked as “contextless”. Reading the code, you can’t tell whether the context is passed in or not, you’d have to look at the definition.

EDIT2: I should also clarify that the issue is not that important to me. I was just writing some libraries for myself and felt like the api would be nicer if this was allowed but ultimately I am not seriously suggesting it would be worth the effort of adding to the language.

1 Like

Loads of harm and the FAQ explains thoroughly:

Odin is an unabashedly procedural language, without method-like syntax in the way you are describing. This daisy-chaining approach will also fail quickly as it would not work well with multiple return values.

5 Likes

Yeah, it wouldn’t work with multiple return values. So much for that.

1 Like

But this would work I think:

package_name.entity.to_string(&my_entity)

entity.to_string :: proc(ent: ^Entity) -> string { /*...*/ }

Where entity.to_string is the procedure name (the dot is part of the proc name). There are no namespaces here. There is no OOP here (maybe only conceptual OOP which is used everywhere in procedural languages).

Sometimes it requires more mental effort to identify the “object” name.
For example, in the procedure imagine_setting_integer_set(/*...*/), the “object” could be imagine, imagine_setting, or even imagine_setting_integer. However, by using the dot, it’s easy to identify the “object” and saves mental energy. In the case of imagine_setting.integer_set, the “object” is imagine_setting. In the case of imagine_setting_integer.set, the “object” is imagine_setting_integer.

(allow only one dot in proc name)

Also, I proposed this syntax sugar before (could be a stupid idea, but sharing it for fun):

Instead of this:


g_physical_device: vk.PhysicalDevice

physical_device__get_extensions(&g_physical_device)
physical_device__find_swapchain_support(&g_physical_device, &surface)
etc.

Imagine we could declare the g_physical_device variable like this:

g_physical_device: vk.PhysicalDevice #proc_sugar: "physical_device__"

then we would be able to call the above procs like this:

&g_physical_device.get_extensions()
&g_physical_device.find_swapchain_support(&surface)

Never going to happen.

“Never going to happen” to both ideas? OK

This argument makes sense:

" One of Odin’s goals is simplicity and striving to only offer one way to do things, to improve clarity. x.f(y) meaning f(x, y) is ambiguous as x may have a field called f . It is not at all clear what this means."

I wish we had another character as good as a dot to separate “noun” from “verb”.
And “::” and “#” are already taken too :slightly_smiling_face:

package_name.entity@to_string(&my_entity)

entity@to_string :: proc(ent: ^Entity) -> string { /*...*/ }

I guess I will stick with two underscores “__”.

You’re forgetting packages exist still and thus there is still some ambiguity. So again, no. Please stop trying to make Odin OOPy.

1 Like

I think I figured it out with your help. The package name becomes a noun, and then everything seems to fit in place. Similar to the slice package. I needed to shift my mentality. Thanks.

not sure if it works in user code, but dxgi bindings utilise custom vtable implementations to achieve this

might also work idk

although idt it’s not a good fit for the specific example you gave

it seems better off for interop usage where the library is written in c++

After reading the FAQ, I think this question could NOW be splitted into two different questions:
Q1) What IDE allows us to know which procedures can be applied to each struct.
Q2) What would be the harm of vtables OR dynamic dispatch/proc pointers.

I actually dont know the answer of any of those two questions and VSCode is a big pain to work with.

This comes down to the LSP (language server protocol) server to know how to parse and inform the user interface about what’s available. With Odin, we only have GitHub - DanielGavin/ols: Language server for Odin, and any IDE that supports communication with LSP should work with it.

I gave writing my own LSP for Odin a solid try at one point and found the complexity to be far more costly than what it was worth. The difficulty made me realize that there’s nothing wrong with just using a combination of grep and fzf: basic text and file searching.

It’s far easier to reason about and requires no knowledge of how the AST is assembled or any other language-level details, for any language for that matter.

On the matter of finding which procedures can accept some struct, you could grep for :.*\<proc\>.*\(.*\<T\>.*\), for example. That’s a very specific pattern, and I tend to just grep for \<T\>.

Being forced to dive into source code more often gives me a better understanding of the greater space than if I were to be given precisely some list of options after typing a . after a variable name.

Generally speaking?

  • The proc pointer is entirely opaque to the compiler. It cannot know what it is because it can change at runtime, thus it cannot reason about it with regards to optimization and inlining at the call site. This costs time.
  • One layer of lookup indirection if you’re storing it on a struct field, and two layers if you’re using a key-value map structure. This costs time.
  • These pointers to procedures need to be stored somewhere. Normally, procedures called are referenced directly in the assembly itself. Now, they have to be stored in memory somewhere else, and it scales linearly with each duplicate reference you have to the same procedure. You can, for example, have 100 objects all with a pointer to the same procedure and waste 100 times your pointer size in bytes. This costs space.

That’s not to say that a proc pointer is universally bad, but when it comes to anything in programming, you must know the costs. The question is then: what do you get in return?

1 Like