[Accepted with Modification] SE-0253 - Callable values of user-defined nominal types

I would have preferred to just drop ‘func’ and just say ‘call()’. It mimics init/deinit and makes it clear what’s going on.
Any name that we suggest still will have the issue of not being clear enough. Would this be the first feature that with JUST a special name gives new powers to a type?

9 Likes

AFAIK a new kind of decl (like the original proposal) is out of scope.

Otherwise, I'd prefer we introduced an operator func modifier, more like C++: it clearly indicates that those members are not usable like regular member functions, even without type-level attributes, it can be used to avoid collisions and provide better diagnostics and auto-complete, and it scales to future features.

struct Foo {
  operator func call(...) { /* ... */ }
  operator func memberLookup(...) { /*...*/ }
}

And it could be used for our other operators, like + and +=:

struct Foo {
  operator func + (other: Foo) -> Foo { /* ... */ }
  mutating operator func += (other: Foo) { /* ... */ }
}

By the way, how much nicer is it to write += as a mutating instance member like that? As opposed to what we have now:

struct Foo {
  static func += (lhs: inout Foo, rhs: Foo) { /* ... */ }
}

The only niggle is that you couldn't refer to the function for things like partial application. It's nice that the core team considers that important, but you can't even refer to more basic operators like + for those things today!

let f = Int.+= // Error
let f: (inout Int, Int) = += // Error

A general solution to that would be welcome. Perhaps something like #operator(Foo.call) or #operator(infix, Int.+). That would allow us to disambiguate between the operator "call" and a non-operator function which was coincidentally named "call".

This is what I meant in the original review when I said we should be looking at these features like operators. init/deinit are not just like normal functions; there are certain things you must do in an init, and the compiler checks that you do them, there are chaining rules, etc.

2 Likes

I agree, removing the name altogether makes most sense, since that almost exactly parallels how you would call it.

func myFunc() is called as myInstance.myFunc()
func () would be called as myInstance()

This is very close to the expected myInstance.() which would be ugly. This notation also has the advantage that it doesn’t introduce any surprising magic - it’s pretty clear than having a function with an empty name could give special behaviour, but nobody would expect special behaviour form a function named “call”.

1 Like

Actually, perhaps the least surprising option would be

func _(someArg: Int) -> Int

It parallels other use of _, it is googleable and the behaviour is not at all unexpected.

5 Likes

"_" is the first thing that came to my mind as well, but i wonder if using it isn't going to come back to bite us in the future.

PS : for what it's worth, i also don't like the idea of giving exceptional meaning for special user-defined values. I couldn't think of anything more confusing. At least "_" makes it look that something you would expect the language to give some kind of special meaning.

1 Like

The Core Team discussed this and has decided to further revise the proposal to name the operator function func callAsFunction(). We are comfortable with enabling this functionality purely based on nothing more than the name of a function, and we are not persuaded that init and subscript (which both have substantially different semantic and syntactic rules and are not simply functions) provide important precedents here requiring a new declaration introducer.

Thank you all for the discussion.

John McCall
Bikeshed Manager

39 Likes

heh, but at least we now have a verdict, thank you. :slight_smile:

8 Likes

Thank you all! Proposal updated: Final revision of SE-0253 callable. by rxwei · Pull Request #1043 · apple/swift-evolution · GitHub.

3 Likes

Thank you for wrapping this up for me John, I applaud your efforts as bikeshed manager!!

-Chris

14 Likes

A majority of the original feedback was against magic handling of methods spelled a particular way

This seems like a huge failure in the Swift evolution process. What is the purpose of the community's participation if the core committee is going to disregard it (and choose a really suboptimal solution nonetheless, spelling-based behavior, inconsistency across dynamic and static).

3 Likes

What is the purpose of the community's participation

Pitch a non-existent language feature, publicly present it, debate it, create an entire proposal, publicly present it, debate it, incorporate the feedback and implement the entire feature, break Swift, fix it, break it again, fix it, then witness Swift being shipped with your ideas and your code inside of it.

The great philosopher Sir Michael Jagger once said "You can't always get what you wa-a-nt; but if you try sometime, you'll find, you get what you need."

10 Likes

I think call made the most sense given the existence of init and subscript, these things as keywords are so aesthetic.

3 Likes

I was really looking forward to this feature, @dan-zheng can you give us an update on the issue(s) of implementation?

Is there is any chance we can see this feature in a release post swift 5.1?

Thanks for the ping! I'm sorry about the long wait for this feature.

The current status is (implementation PR):

  • func callAsFunction is implemented and fairly well-tested.
  • @xedin requested some implementation detail changes (regarding the constraint system) which I haven't investigated yet: this is the main reason why the PR hasn't landed.
  • Diagnostics regarding sugared func callAsFunction applications are not ideal, but can be improved as a follow-up to the initial feature.
  • There's at least one known type-checking issue regarding sugared func callAsFunction applications (TF-516). Perhaps this will be fixed after I address @xedin's comments.

I'll try to investigate @xedin's comments this weekend - thanks for your patience :slightly_smiling_face:

6 Likes

func callAsFunction has been merged and is available in latest development snapsnots from Swift.org - Download Swift.

I just tested swift-DEVELOPMENT-SNAPSHOT-2019-08-27-a:

struct Adder {
  var base: Int
  func callAsFunction(_ x: Int) -> Int {
    return x + base
  }
}
var adder = Adder(base: 3)
print(adder(10)) // 13

Thanks for your patience!

14 Likes

I assume it won't be cherry picked into 5.1?

Also, does it support throwing implementations? The discussed feature doesn't, just wondered if it was implemented along the way.

I'm not sure who can make that decision, sorry.

Yes, it does. Here's a changelog PR with a short callAsFunction feature list:

  • func callAsFunction argument labels are required at call sites.
  • Multiple func callAsFunction methods on a single type are supported.
  • mutating func callAsFunction is supported.
  • func callAsFunction works with throws and rethrows.
  • func callAsFunction works with trailing closures.

Check out this test file for general func callAsFunction functionality tests. There are also other tests for generics, protocols, cross-module support, and edge cases.

If you find bugs or unsupported behavior, please file a bug. Here are currently known issues:

  • SR-11378: improve callAsFunction diagnostics. Ideally, diagnostics should match those from direct callAsFunction calls.
  • SR-11386: crasher regarding conditionally available callAsFunction methods.
3 Likes

Correct.

But since this is solely a compiler feature/syntax sugar (i.e not requiring runtime changes), am I correct in thinking that once the next version of the compiler is released, we will be able to use this feature without backwards deployment concerns?

2 Likes