5 ms·
Universal Function Call Syntax, the syntax feature where you can call a function in a procedular language you can call function in a multitude of ways. For exam
by FireInsight 3y ago
Universal Function Call Syntax, the syntax feature where you can call a function in a procedular language you can call function in a multitude of ways. For example (pseudocode):
```
func toUpperCase(s: string) <IMPLEMENTATION>
a = "hello world" echo toUpperCase(a) # HELLO WORLD echo a.toUpperCase # HELLO WORLD echo toUpperCase a # HELLO WORLD echo a.toUpperCase # HELLO WORLD
```
With the dot syntax, the first argument is the one indicated by the dot, and the rest of the arguments are in the parentheses. In nim, you can leave out the parentheses too, which makes `echo` statements cleaner.
It's a shame more mainstream languages don't have this feature.
- rjha 3y agoformatting the above function for better readability ``` func toUpperCase(s: string) <IMPLEMENTATION> a = "hello world" echo toUpperCase(a) # HELLO WORLD echo a.toUpperCase # HELLO WORLD echo toUpperCase a # HELLO WORLD echo a.toUpperCase # HELLO WORLD ```
- usrusr 3y agoWhy would you ever want the dotless version though, in cases where it's not terrible? (e.g when there are multiple arguments of equal importance and calls where the argument is appears more as an option for esoteric cases than the implementation's main subject) I really like the option to "semantically scope" a function to its main subject, like in kotlin extension methods. But I fail to see why I'd ever want to have both options. Is it just a compatibility thing, to be able to call code written for conventional languages? Or from developers who don't like that approach?
- dmit 3y agoHere's one case. Instead of let letters = word.chars().filter(|ch| ch.is_alphabetic()); you can just write let letters = word.chars().filter(is_alphabetic); since the difference between "methods" and "functions" is purely syntactic.
- usrusr 3y agoMost languages that have a way to define dot-syntax functions that aren't actually methods of the type also come with the ability to use any zero-arg method (both true methods and dot-syntax extension functions) as a one-arg function reference, or rather n-arg methods as n+1-arg function references. No need to crowd the unqualified namespace just to be able to use them without wrapping in a lambda.
- lifthrasiir 3y agoUFCS only works well when the language also supports function overloading, which is a questionable feature by itself because it greatly complicates resolution processes and constrains other type system features. For example, function overloading in Rust will definitely conflict with traits (and Rust trait system is already complex enough). UFCS increases the complexity on top of that, and the resulting convenience is more or less marginal in my opinion. Yes, it would be great to have a way to turn a method into a function. That doesn't necessarily mean that they have to be unified---an explicit conversion is enough.
- 3836293648 3y agoUFCS is not the feature to call anything like `a.f(b)`, it's to make all functions callable the same way. Calling a method like `A::f(a, b)` is also UFCS and Rust does have that.
- lifthrasiir 3y agoI'm specifically referring to UFCS as in D and Nim (possibly more). Rust's `A::f(a, b)` was also called UFCS in the past, but it is a distinct syntax and the term is no longer used [1]. The UFCS in question would look like `f(a, b)` instead, and conversely, you may be able to call an ordinary function `f` with `a.f(b)` as well. Rust allows neither of them. [1] https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls https://doc.rust-lang.org/reference/expressions/call-expr.ht...
- WalterBright 3y agoD supports both `f(a,b)` and `a.f(b)`.
- cassepipe 3y agoIt's in cpp2/cppfront I believe, for the record
- camgunz 3y ago(your formatting got a little lost but I get the gist) I've done a little langdev and I've found that UFCS interferes with stuff like field access (person.name) or things like interfaces. Let's say you have a person struct like { name: str } but then also a Label interface like Label { name: fn () -> str }. It's kind of a broader problem of being able to extend things in any place you might imagine, like I'll implement a trait on this struct here, I'll implement a function on this struct here, etc. etc. and before you know it your code is extremely nonlocal. There are ways to temper this, e.g. Rust combats this a little by having to have traits in scope for them to apply, but that's annoying in its own way. I'm just saying it's not a free lunch, I think anyway.
- FireInsight 3y agoSorry about the formatting, typing this on my phone. The preview looked alright, but ig that didn't translate. For the function-field interference, I'd probably just work it based on situation. And usually naming functions clearly as actions with verbs and fields/variables with nouns.
- camgunz 3y agoSure yeah but like, you gotta deal with clashes. The problem itself isn't hard, but trying to get the ergonomics right is pretty tough, especially when you're talking about multiple dependencies that didn't coordinate at all with each other (this trait adds a "name", this one adds a "getName", this one adds a "display_name", this one adds a "get_name").
- FireInsight 3y agoThe mixing of snake_case and camelCase is solved by Nim by ignoring capitalization and underscores. This feature is often controversial when talked about on HN, but I love it. I like camelCase, but I understand why a python user would be more comfortable with snake_case. Of course one codebase should set its internal style to prevent faults in control-f.
- WalterBright 3y agoD has had UFCS for maybe a decade. It's a very popular feature.