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.
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.
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.
" 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
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.
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?