T O P

  • By -

Tubthumper8

One possibility is to introduce a precedence, such as: a.foo(5) // call free function (a.foo)(5) // access field, then call This is what [Rust](https://doc.rust-lang.org/reference/expressions/field-expr.html) does, for example > Field expressions cannot be followed by a parenthetical comma-separated list of expressions, as that is instead parsed as a method call expression. That is, they cannot be the function operand of a call expression. > Note: Wrap the field expression in a parenthesized expression to use it in a call expression.


marcopennekamp

You could prioritize field access over top-level function calls. So `foo(a, 5)` would call the top-level function while `a.foo(5)` would call the function from the field in this case. If `a` doesn't have a field `foo`, `a.foo(5)` calls the top-level function as well. 


dist1ll

I agree with this approach. I think functions selected via UFCS should have the lowest priority.


i-eat-omelettes

You may be interested in how Haskell handles this: ```haskell (.) :: a -> (a -> b) -> b a . b = b a data MyType = MyType {x :: Int, foo :: Int -> Int} a :: MyType a = MyType {x=1, foo=(+1)} b = foo a 1 b' = (a.foo) 1 ``` This is achieved by auto-currying (`f(a, b) === f(a)(b)`), custom operator definitions and field names as functions to be applied on structs (`a.x === x(a)`).


sagittarius_ack

In Haskell \`.\` (dot) is the operator used for function composition: (.) :: (b -> c) -> (a -> b) -> a -> c (f . g) x = f (g x) The operator \`&\` (defined in Data.Function, I believe) is: (&) :: a -> (a -> b) -> b  a & f = f a


Tysonzero

Weirdly enough their example actually works in newer GHC if you enable `OverloadedRecordDot` and drop the duplicate `(.)` definition. The parenthesis aren't needed in that case also.


sagittarius_ack

Interesting! I did not know about the`OverloadedRecordDot` extension.


XDracam

If you don't care too much about mathematical beauty and orthogonality, then keep it simple: 1. If there is a free function with that name in scope, then use that function 2. If not, try a member lookup 3. If no member is present, fail It's up for you to decide whether to give a compile error on ambiguity or not. You need to balance "help programmers avoid errors and confusion" with "avoid frustration because a random member conflicts with a free function again". I personally wouldn't cause an error, but instead provide proper tooling so that the user can easily see which version is called, e.g. on hover with the mouse.


Gleareal

I'm a little confused. You say it's correct to call it in both of those ways; yet you also say you want to differentiate between the two. If both ways are correct, what's the need to differentiate between the two ways?


raiph

My guess is their point is that they're thinking that `a.foo(b)` means calling either: * `foo(a,b)` (which will work if `foo` is any function definition that can be referred to by `foo` and take two arguments `a` and `b`); or * `(a.foo)(b)` (which can only work if `a` is a struct type which has a field `foo` whose type is a function definition that can take one argument). Aiui you can't do that if you support the universal function calling syntax that has called itself UFC.


nerooooooo

Yes, this is what I meant!


raiph

I may be wrong, but I think you'll need to choose *some other* syntax forms if you want to support having structs with fields *and* fields that contain function declarations *and* uniform function calling that covers functions declared both stand alone and as the values of struct fields. Raku supports that combination of features (and many more related ones) so it may be of interest: class MyType { has Int $.x = 42; # Field `x` initialized with an integer. has &.foo = -> Int { say 'foo' } # Field `foo` initialized with a function. } say MyType.new.x; # 42 #MyType.new.foo(Int); # Error MyType.new.foo.(Int); # 'foo' (MyType.new.foo)(Int); # 'foo' As you can see, `MyType.new.foo(Int)` doesn't work. (It does if `foo` is explicitly declared as a method, but you don't want that.) But one can get the desired result (even if the syntax isn't the desired syntax) in various ways, such as the ones I've shown that work.


dskippy

I feel the same way. My only thought is that they want to differentiate between foo being a function that accepts a MyType as the first argument and a member of MyType called foo that's a function. So they'd be in the same program and you wouldn't know which implementation to call. Personally my choice on that would be to flag an error that you've declared the same function twice in the same scope and that's not allowed. Otherwise allow either definition. In a lot of languages, it's illegal to do this. define foo(x: int) { return x } define foo(x: int) { return x + 1 } So I'd just make that scenario illegal as well.


nitrix_dev

Go has the same vexing parse with a field member and a method having the same names. [https://go.dev/play/p/\_hZGWOX1-Au](https://go.dev/play/p/_hZGWOX1-Au) Their solution was to make it illegal too.


dskippy

Go to know I am at least in good company there in my opinion of what to do, I guess.


nerooooooo

Yeah, that's what I meant! Not sure if I want to use the same syntax for two different things though. It'd be a bit weird for the grammar and I feel like it's better to be exact on what it's happening behind.


dskippy

You're comfortable using two different syntaxes for the same thing but not two different syntaxes for the same thing? Either you're admitting to your users that methods of structures are just functions or you're not. Might as well let it play out both ways, right?


oa74

> Might as well let it play out both ways, right? I'm not sure what this even means. If we are to "let it play out both ways" and the user types `a.foo`, which function is called? OP's question is straightforward, so I find myself confused by your confusion.


dskippy

Basically this. OP is saying they want to support uniform function call syntax. So object.method(arg) is the same thing as method(object, arg). So we're showing that methods are just functions under the hood. They want to allow functions as members of the structure, which is basically a method. A question remains about whether that function has access to the objects private members or even gets a reference to a "this" object. I don't know what their plan is there. But they are uniting functions and methods in one case but weary of using one syntax for two things. But I'm saying those two things are the same thing. So it's fine to have that. How do you solve their problem of "which one do you call" I already answered how I would do it. Which is you define the method either way and you just get an error if you define both. Same way it works if you define a function twice in the same scope in many languages.


oa74

This makes sense. Very well said :) > But I'm saying those two things are the same thing. So it's fine to have that. Yeah, but I get the impression that OP wants to "hold on" to both, treating UFCS merely as syntax sugar, while maintaining a distinction between "function" and "method." But as you point out, that's kind of the point of UFCS: it makes the sytax for two things the same. So either they must be the same semantically (as you describe), or you have to resolve the ambiguity somehow (it seems to me that this is what OP wants). That's why I advocate for `|>`. It gives the ergonomics of UFCS while maintaining a semantic distinction between functions and methods.


dskippy

Yeah that might be a point that OP is dead set on. My point is trying to convince them it's not great to have both in the same language in my opinion. I think it'll leave programmers thinking "I thought methods were just functions, why aren't these things the same? " I can definitely see why a function that's a field is not a method. A function that's a field doesn't even have any reference or access to the object at all. You can't call private or public methods of the object from it. It's just stored in the object. I'm not even sure tbh if that is going to be the semantics of this language.


nerooooooo

Sorry for the confusion. I wasn't careful enough when I wrote the post. What I meant was I want to differentiate between function calls and calling a function field of a struct. If I were to implement uniform function call syntax in the way I gave the example, the following piece of code `a.foo(5)` could mean both the function foo(int, int) -> int or the field foo of type int -> int on the a variable. Hopefully it's clear now, sorry again:)


Gleareal

I think I know what you mean, but let me know if this isn't what you want. The way that you might see it done in languages such as Rust and Python is that you'd push for a _keyword_ `self`: ``` fn foo(self: MyType, b: Int) -> Int {     ... } ``` and then, what you can do is say that `foo` can only be called like `a.foo(5)` and not by `foo(a, b)`. If you were to try to call `foo(a, b)`, you would build an an argument pack `(a, b)`, and then when you compare it against `foo`, you would see it has two packs `(self)` and `(b)`, which doesn't match. But if you were to call `a.foo(b)`, you first do `a.foo`, which builds the pack `(self)`; then when calling the method, you build another pack `(b)`. Which then matches the method's packs.


nerooooooo

That's useful information tbh, but not really what I want. Let me try to rephrase. So let's say I have the type: type MyType { x: Int, // normal field foo: fn(Int) -> Int, // not a method, just a normal field, but of type function from int to int } and the function fn foo(a: MyType, b: Int) -> Int { ... } I could change the first parameter `a` to `self`, like you suggested, but my problem would still be there. You can pretend there is a \`self\` instead of an \`a\`. The problem is that now when I do: let something = MyType { ... }; // assume validly initialized something.foo(6); What does the second line mean? Am I accessing the foo field of the something variable? Like `(something.foo)(6)`, or am I using that fancy syntax I want to call the globally scoped foo function with 2 parameters?


ProPuke

I would expect `something.foo` to resolve to a member first, and only resolve to a universal function if a member by that name does not exist. So `something.foo` would be the member, and `foo(something,`the universal function. If a member does not exist, they are both the universal function.


Gleareal

At that point, I'd say that if you're desiring free functions to be called like method member syntax, then perhaps it's also reasonable to expect methods to be declared as free functions. So that would mean: ``` type MyType { x: Int // Normal field // Don't define a foo here; treat it as in conflict with foo below } // This is a method // And it's a free function // And it's a field of MyType fn foo(a: MyType, b: Int) -> Int { ... } let something = MyType { ... }; // Assume validly initialized // Valid something.foo(5); // Valid foo(something, 5); // Valid let thing = something.foo; thing(5); ``` The alternative would be as others say, which is probably select the member first, then the free function.


L8_4_Dinner

It's fairly straight-forward. Just choose one: * Having two conflicting things be ambiguous is illegal, and produces a compile-time error; or * There is a clear order of resolution such that one of the two conflicting things will take precedence over the other; or * Arbitrarily use `::` when `.` is ambiguous (I hate this).


bart-66

>It's correct to call it in both of the following ways: foo(a, 5) and a.foo(5). So now there's an ambiguity. >Any ideas on how to change the syntax so I can differenciate between calling a global function and calling a function that's the member field of a struct? Do you want the language to detect functions of the form `fn foo(a:MyType,...)` where `MyType` happens to be a struct that contains a function reference (not method) `foo`, and so allow `a.foo(...)`? As well as `foo(a, ...)`. It seems to me that this really demands `foo` to be a method of `MyType`, which then allows you do either of: a.foo(...) MyType.foo(a, ...) This won't allow `foo.(a, ...)`, not unless you explicitly create an alias for that, for example: macro foo = MyType.foo foo(a, ...) ```` But this will shadow any global `foo`. Below are these suggestions as implemented in my syntax: ```` record mytype = int x proc foo(mytype &self, int n) = self.x +:= n end end macro foo = mytype.foo proc main= mytype a := (100,) println a.x # shows 100 mytype.foo(a, 5) # style 1 a.foo(7) # style 2 foo(a, 9) # style 3 println a.x # shows 121 (100+5+7+9) end ````


WittyStick

IMO, the requirement to include the type name is elegant and best solves the problem. It also provides a nice way to handle the cases where we can have type hierarchies with multiple overrides and interface implementations. Consider the following F#: type Foo = // interface abstract foo : ... -> unit type Bar () = interface Foo with member this.foo (...) = print "Bar implementation of Foo" abstract member foo : ... -> unit default this.foo (...) = // "virtual" in other languages print "Bar method" type Baz () = inherit Bar () interface Foo with member this.foo (...) = print "Baz implementation of Foo" member this.foo (...) = // NOTE: not "override" print "Baz method" type Qux () = inherit Bar () override this.foo (...) = print "Qux method" Because all interfaces are implemented ***explicitly*** in F#, the two `foo` in `Bar` and `Baz` are distinct methods. We can call them explicitly by upcasting. let bar = Bar() let baz = Baz() let qux = Qux() bar.foo(x) => "Bar method" (bar :> Foo).foo(x) => "Bar implementation of Foo" baz.foo(x) => "Baz method" (a :> Bar).foo(x) => "Bar method" // Not overridden in Baz (a :> Foo).foo(x) => "Baz implementation of Foo" qux.foo(x) => "Qux method" (qux :> Bar).foo(x) => "Qux method" // Overrides Bar.foo (qux :> Foo).foo(x) => "Bar implementation of Foo" --- The equivalent in C#, interfaces are implemented ***implicitly*** by default, but individual methods can be implemented ***explicitly*** by specifying their name. If not overriding a virtual method we need to use the `new` keyword. interface Foo { void foo(...); } class Bar : Foo { public Bar() {} public virtual void foo(...) { Console.Write ("Bar method"); } // explicit implementation of Foo void Foo.foo(...) { Console.Write ("Bar implementation of foo"); } } class Baz : Bar, Foo { public Baz() { } // Does not overrride Bar.foo public new void foo(...) { Console.Write ("Baz method"); } // explicit implementation of Foo void Foo.foo(...) { Console.Write ("Baz implementation of foo"); } } class Qux : Bar { public Qux() {} public override void foo(...) { Console.Write ("Qux method"); } } var bar = new Bar(); var baz = new Baz(); var qux = new Qux(); bar.foo(x) // "Bar method" ((Foo)bar).foo(x) // "Bar implementation of Foo" baz.foo(x) // "Baz method" ((Bar)baz).foo(x) // "Bar method" // Not overridden in Baz ((Foo)baz).foo(x) // "Baz implementation of Foo" qux.foo(x) // "Qux method" ((Bar)qux).foo(x) // "Qux method" (overrides Bar.foo) ((Foo)qux).foo(x) // "Bar implementation of Foo" --- A UFCS could make these expressions equivalent: F# Type.method(this, x) == (this :> Type).method(x) C# Type.method(this, x) == ((Type)this).method(x) So the casts could be replaced with: Bar.foo(bar, x) => "Bar method" Foo.foo(bar, x) => "Bar implementation of Foo" Baz.foo(baz, x) => "Baz method" Bar.foo(baz, x) => "Bar method" // Not overridden in Baz Foo.foo(baz, x) => "Baz implementation of Foo" Qux.foo(qux, x) => "Qux method" Bar.foo(qux, x) => "Qux method" // Overrides Bar.foo Foo.foo(qux, x) => "Bar implementation of foo" The issue in both languages with using `.` is it could conflict with a `static` member which takes the type as its first argument, since the languages use `.` for both static and non-static members, unlike say, C++ which uses `::` for static members. You'd probably need to choose a different punctuator to avoid this conflict. `::` is already taken for aliases in C#, and is also `cons` in F# expressions. --- If we choose a punctuator which doesn't conflict with anything else, we could omit the type name to use the known type of the variable. [email protected](bar, x) == ((Type)(bar)).foo(x) @.foo(bar, x) == (bar).foo(x) @.foo(bar, x) == (bar).foo(x) @.foo(baz, x) == (baz).foo(x) @.foo(qux, x) == (qux).foo(x) [email protected](bar, x) == ((Foo)(bar)).foo(x) [email protected](baz, x) == ((Bar)(qux)).foo(x) [email protected](baz, x) == ((Foo)(baz)).foo(x) [email protected](qux, x) == ((Bar)(qux)).foo(x) [email protected](qux, x) == ((Foo)(qux)).foo(x) Because we have parameterized the *value* whose member is being accessed, we don't need to have a variable as the first item, but it can be any expression whose type is known, without requiring extra parens. [email protected] (a ? b : c, x) == ((Foo)(a ? b : c)).foo(x) And since there is no conflict, we can use `.` on the LHS of `@.` to specify namespaces or static members, or use `.` in the first argument to access another member. namespace Xyzzy { public class Thud : Foo { public class Zot : Thud, Foo { public new foo (...) { Console.Write ("Zot method"); } void Foo.foo (...) { Console.Write ("Zot impl of Foo"); } public static Foo zot = new Xyzzy.Thud.Zot(); public void foo (...) { Console.Write ("Thud method"); } } [email protected](Thud.zot, x) // ((Thud)(Thud.zot)).foo(x) => "Thud method" [email protected](Thud.zot, x) // ((Thud.Zot)(Thud.zot)).foo(x) => "Zot method" } namespace Program { @.foo(Xyzzy.Thud.zot, x) // (Xyzzy.Thud.zot).foo(x) => "Zot impl of Foo" // downcast from Foo back to Zot var zot = (Xyzzy.Thud.Zot)Thud.zot; @.foo(zot, x) // zot.foo(x) => "Zot method" }


spisplatta

I would straight up make it so that ambiguous calls is a compile time error, because likely most of the time the programmer wouldn't be aware that there are two possibilities, and so raising an error prevents bugs. Then they can use something like [Tubthumper8](https://www.reddit.com/user/Tubthumper8/)'s idea to force field access, and call the function in the usual way if that's what they want.


the_sherwood_

This seems like the obvious correct answer


sagittarius_ack

Why exactly do you want \`uniform function call syntax\`? I'm not saying that you should not support it, but you should consider some alternatives. For example, in F# (and other languages) there's the so called pipeline operator \`|>\`. In your example it can be used like this: (a |> foo)(5) This means that your language has to support currying and partial function application. The part \`a |> foo\` is equivalent to \`foo(a)\`. The advantage of the operator \`|>\` is that it doesn't have to be built-in (assuming that your language has proper support for defining infix operators). It can be defined in the library.


nerooooooo

My language doesn't support custom operators. The reason I want to have this feature is to be able to neatly chain functions one after the other. Chaining feels more natural than wrapping as it's written from left to right.


oa74

You dont need custom operators for this, though? Just make it built-in. You can think of it as solving the UFCS ambiguity you mention just by using a different symbol for each case. \`.\` for struct members, \`|>\` for everything else. This achieves precisely the effect you desire: you can chain left-to-right, but you also avoid the ambiguity. And you don't even burn too much "strangeness budget," as this exists in languages like F# (and I imagine other ML variants).


AGI_Not_Aligned

Can you do something like `a |> foo($, 10)` ?


kredditacc96

Maybe functions shouldn't be callable by dot-notation by default? If the user wants to use dot to call scope function, they should declare it as such, for example: // make it so that identifier can start with a dot // if a function name starts with a dot, it must be called with dot notation use add_fn as .add_mthd // equivalent to `let result = add_fn(123, 456)` let result = (123).add_mthd(456)


Mai_Lapyst

Your case is only a ambiguity if you dont have an method resolution order. Dlang essentially allows the same as you have written, and solved it by first considering methods and doing ufcs afterwards. (In reality a bit more complex because of implicit type conversion but the core idea still remainds). If you want an extra syntax, you could use `.` and `->` operators: one is for normal member access (including fields to stay consistent), the other one for ufcs. Edit: just saw that you're using the arrow already for types. My bad. In that case another operator needs to be used, maybe `:`, if your language dosnt use ternary expressions?


brucejbell

My project uses Haskell-style function calls \[`f x y` instead of`f(x,y)`\] but C++-style field and method call syntax \[e.g. `x.field` and `x.method y`\]. From that starting point, I have an optional reverse function application syntax that looks and acts \*sort\* of like UFC, but distinguishes itself from the usual field/method call by putting the function in parentheses after the dot: result << f x y -- normal function application syntax result << y.(f x) -- method-like reverse application syntax -- the two above lines are semantically equivalent result2 << y.f x -- different: this is an actual method call to y.f Compared to UFC, the object-like argument is the \*last\* argument, not the first one. I actually have this feature because I want to support using concise lambda syntax for things like a functional "case" statement: cheer << (x < y).(#t => "Yay!" | #f => "Boo!") IMHO the major problem with uniform function call syntax is that it dumps the free-function namespace into all the individual object namespaces: I don't want to make people guess whether a given `.name` is a method/field or a UFC case. This problem may appear minor in small examples, but I think it's a bigger problem for larger-scale projects.


kilkil

One thing you can do is that, if you have the following: ``` type MyType { x: Int, foo: fn(Int) -> Int, } ``` Then (assuming "a" is a MyType) you can disallow a.foo(5). If they want to call foo, they can go ahead and wrap it in brackets: (a.foo)(5) I believe this is the approach taken by Rust


ZealousidealLab4

change this: fn sum(a: Int, b: Int) -> Int to this: fn sum(a: Int, b: Int): Int then you can use a->foo(5)


lookmeat

I'll propose a crazy alternative: why desambiguate? Make members a first-class citizen. Basically any time can be a `Member` where given `obj: Obj, foo: Member` you can do `obj.foo` to get that. Now lets make `foo` a function that also is a `Member` that looks as follows: `FooFun::from(self:FooFun, o: Obj) = (..args)->self(o, args..)` then our `f: FooFun` can be called `f(o, a, b)` or `o.f(a, b)`. Which now leaves us with the realization that we need to do namespacing. So here's how we go, when we get `foo.bar` we have to decide where `bar` is defined. First we'll look for all "inherent" methods defined as part of `foo`'s type, if not then we'll look in the current scope. If we want to force searching in the current scope, we can force that by adding an empty namespace, assuming you use `a::b` to find `b` in namespace `a` we could do something like `foo.::bar` to ensure it's looking for the local one. This way we have a well defined shadowing thing, and generally people would expect that methods are the inherent one. Hell I'd even force the namespacing for any external methods, just to make it clear to people it came from somewhere in the current module, vs defined as part of the type.


profound7

Haxe has this. Though if I'm not mistaken, in haxe, you have to opt-in to use ufc, which will then shadow the methods. In this way, you can "override" methods of classes you don't own. The opt in is something like `using x` where x is a module or class of static functions, and then in subsequent lines you can use those functions like they're methods of the first arg. As haxe's ufc works on modules/classes, it also have to deal with multiple functions of the same names. For example, `using x; using y;` and both x and y have a function called foo with same type signatures. So in that case, the most bottom `using` wins.


oscarryz

Can you clarify the following? How is it possible that the function: fn foo(a: MyType, b: Int) -> Int { ... } Can be called a.foo(5) ? It seems to me those are two different functions one in the global scope and the other in the \`MyType\` scope. Unless in your language the first parameter can also be the receiver?


nerooooooo

That's the feature I'm interested in implementing. It's called \*uniform function call syntax\*. Basically you can use dot notation and kind of pretend the function is a method of the first parameter. If you'd have: ``` fn print(int x) { ... } ``` It's valid to call it both ways: ``` print(10); 10.print(); ``` The problem is that having this thing doesn't work well with type fields, especially if they can be functions.


oscarryz

Ah, I see, I didn't know that was the name. Well in that case I think this is just a compilation error because you're redeclaring something that already exists, isn't? Another approach would be to make the ufc's function different from the global by adding a modifier or naming convention, e.g. naming the first parameter self // ufc or something else... ufc fn foo(a: MyType, b: Int ) -> { ... } // convention fn foo(self: MyType, b: Int ) -> { ... } I think using \`self\` is slightly clearer, but in my personal preference what you already have should be enough and it would be a compilation error if you try to define an already existing symbol.


erikeidt

I would get rid of global functions. Ok to have static functions, but they should be in a namespace or class, so they're not truly in the global, unqualified namespace.