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

I don't quite understand why we'd need a name that avoids any awkwardness, or that would need to be immediately understood by anyone not aware of the feature.

On the contrary, names that stand out make it easy to trigger strong mental tricks such as convention and googleability. Especially for "magic" features. They make any confusion disappear.

1 Like

I’m not a fan of the ‘magic’ either, but if we’re going with it, here are some more ideas:

func namelessCall() { ... }
func anonymousCall() { ... }
func directCall() { ... }
func rootCall() { ... }

There’s also func operatorCall() or func callOperator() (or even operator func call()) if we want to make the magic slightly more apparent.

3 Likes

I've read both the proposal and the return for discussion thread, but I cannot fully grasp why we didn't go for call() like init().

17 Likes

This probably came up in other threads but to be honest I‘d prefer anonymous functions just like subscripts, as it would perfectly define the whole metal model for this feature. func (), func (param: Int) -> Int.

If we did that we could also opt in into named subscripts for cases were view types are not efficient enough.

/* static */ func #name#(#param_list#) #trailing_stuff#

// we could allow this:
/* static */ subscript #name#(#param_list#) #trailing_stuff#

// this proposal
/* static */ func (#param_list#) #trailing_stuff#

// existing symmetric part
/* static */ subscript (#param_list#) #trailing_stuff#

Since subscripts can be nameless, and do not require a leading dot during the call, the current proposal should just drop the function name entirely and also not require the dot (which it already does as proposed). All known rules from get only subscripts could be applied here as well.

9 Likes

It is not a noun. It is a verb phrase formed from a verb plus a direct object — just like dropFirst, updateValue, reserveCapacity, formUnion, removeAll, and addSubview.

It is worth noting that in all those examples, the direct object (first, value, capacity, union, all, subview) refers to the first argument of the function. I agree with what Garth said: callFunction implies that the arguments are a function, not the receiver.

callAsFunction is a better name, and one I like.

This section of the API guidelines seems to apply here (note especially the red box):


The examples the guide gives there also help allay concerns about the name being too verbose, I think.

We do have the precedent of these other names which take things that are abbreviated when they are keywords and spell them out in full:

  • Spelling out func:
    • NSExpression.function
    • NSPointerFunctions
    • sortSubviewsUsingFunction(_:)
    • JSObjectCallAsFunction(…) ← note “call as function” here!
  • Spelling out int:
    • BinaryInteger
    • ExpressibleByIntegerLiteral
    • FixedWidthInteger
    • IndexSet(integer:)
    • IndexSet.integerLessThan(_:)
    • Decimal(integerLiteral:)

There are plenty more like this; I just stopped searching at a certain point.

Conversely, in my quick search, I wasn’t able to find any example of any type or function using the abbreviations “int” or “func.”

I agree that this is concerning, and there is also a strong argument for staticallyCall as the obvious parallel to dynamicallyCall.

There is also a reasonable argument for “dynamic” being a bonus qualifier whose absence signals “non-dynamic.”

12 Likes

All your examples for function are from (Objective-C) APIs, and not from Swift keywords or standard library APIs. I don't know how much of a difference that should make, if any. Your examples for int are obviously more relevant.

+1. I’m not a fan of the direction recommended by the core team. The approach of using unnamed methods feels a lot more elegant, a lot less magical, and doesn’t introduce a method we don’t really want to be directly accessible. Further, func () or func _ () is every bit as searchable as anything else we might come up with.

The only downside I can see to the unnamed method approach is that there isn’t a way to refer to the unapplied method as a function. This seems fine. There are future directions to address that. In the meantime writing { callableValue($0) } is not a big deal.

I don’t see why we should have to use a rather arbitrary and ungainly name callable methods. Setting aside the fact that subscripts can’t be methods for a moment, if they could would we really want to spell them func accessSubscript? I sure hope note.

11 Likes

Could we please complete the set by allowing the corresponding equivalent for variables? For example:

blahBlah var Foo: String

subscript(blahBlahMember: String) -> ...

I pitched it a while back but I think I led with some very wrong nomenclature: Allow dynamic keyword on non-objc properties

"functionCall" would be consistent with "subscript", linguistically. If we really want "call" to act as a verb, there's "callAsFunction", but I don't find that to be an improvement

9 Likes

TIL!

I stand corrected on multiple fronts. Thank you for taking time to reply!

1 Like

Thank you for a gracious response!

Since subscript is not to name anything while func is to name a function, I'm not a big fan of name less func unless it'll allow us to name a subscript as well.

func something() // what we have today.
subscript something() // NOT what we have today.

But I think that many folks agreed that subscript is the perfect mental model for this functionality. Therefore, introducing new keyword instead could work best, I thought.

call() // not sure why this was rejected.
subscript() // what we have today.

Now, while I can't tell if someone is willing to propose new subscript magic in the future, I feel it can make more sense to new users for symmetricity.

func callFunction() // what we'll have.
func subscriptFunction() // NOT what we have today.
1 Like

Implementation updates:

  • I've updated the SE-0253 implementation to use callFunction instead of call as the call-syntax delegate method name. By the way, the implementation is currently ~150 LOC - not too big.

  • I also added some trailing closure tests.

    struct TakesTrailingClosure {
      func callFunction(_ fn: () -> Void) {
        fn()
      }
      func callFunction(_ x: Int, label y: Float, _ fn: (Int, Float) -> Void) {
        fn(x, y)
      }
    }
    var takesTrailingClosure = TakesTrailingClosure()
    takesTrailingClosure { print("Hi") }
    takesTrailingClosure() { print("Hi") }
    takesTrailingClosure(1, label: 2) { _ = Float($0) + $1 }
    
  • There's one remaining known bug where call-syntax sugar fails for mutating func callFunction and IUO. Advice on fixing this (handling mutating func callFunction more robustly) would be appreciated.

    struct Mutating {
      var x: Int
      mutating func callFunction() {
        x += 1
      }
    }
    func testIUO(f: inout Mutating!) {
      f() // not okay
      f.callFunction() // okay
    }
    
    mutating_iuo.swift:8:3: error: cannot use mutating member on immutable value: 'f' is immutable
      f() // not okay
      ^
    
2 Likes

+1. I think this is analogous to C++'s famously verbose template <typename T>. When we introduced these features, lots of people (including me) had this strange idea that people might "abuse" them; so we invented extra syntax to warn you that something strange and scary is coming.

But on reflection - these features are part of the language. We should just get over it, accept that they are here to stay and try to make them great. It's important to make things clear so you don't accidentally add support for these features when you didn't mean to; but beyond that, burdening them with extra syntax is so 1980s :tipping_hand_man:.


But now we have the problem of functions with magic names and special calling syntax. As I mentioned in the review, the language already has this problem because of operators, but in practice they tend to get away with it because they have a restricted character set which only allows "operatory-looking" things:

// Okay
infix operator |/
// Error: 'abc' is considered to be an identifier, not an operator
infix operator abc

I don't exactly love the name func callFunction(...); it feels kind of verbose and awkward. It almost reads like a command, as if it's saying "call a function", which doesn't really make much sense.

My preferred spellings are, in order:

  1. func () (...args...)
  2. func __call(...args...)
  3. func callFunction(...args...)

Although I appreciate that #1 might be difficult for beginners to Google when they first encounter it. I'm not sure if that should be a massive concern. #2 doesn't have any precedent in the language, but it's shorter and clearer than #3, and possibly has even better Googlability.

I very much like @Joe_Groff's suggestion to borrow Python's **kwargs and to use it to absorb dynamicCallable in to this. It's a better conceptual model and enables lots of nice features. It's a much more Swifty way of doing variadic parameters.

If we go in that direction, it means we don't need to care so much about being consistent with func dynamicallyCall(args/kwArgs) because it will eventually be deprecated anyway.

1 Like

Named subscripts are not purely about symmetricity, they would allow us to avoid creating view types and potentially provide some enhancements such as avoiding unnecessary copies (in a few cases), while keeping the name for convenient API usage. The gain is minimal but it would align well if this proposal would use unnamed methods.

That said I‘m for this:

/* static */ 
func 
/* optional name */
/* <generic type parameters> */ 
(/* parameters */) 
/* (re)throws */
/* -> ReturnType */

The Core Team has no interest in using special syntax to declare these functions. Please confine your suggestions to ordinary identifiers. If there's no consensus on a better alternative to callFunction, we'll just stick with that.

1 Like

That's true, but also all functions are functions :slight_smile:

Before func callFunction() I would prefer:

  • func call()
  • func staticallyCall() (bonus points for consistency with Dynamic Callable, also it sounds clearly "magical")
  • func operatorCall()
  • func callable()

Can you specify 'special syntax'? I think using callFunction is special syntax, but unnamed methods would have precedent in the language as they would align to read only subscripts for both the member definition and usage (except for the parameter label rules).

4 Likes

Right: specifically the core team wants an already-valid identifier as the well-known name that triggers this feature. The identifier should be a compound word to reduce the odds of accidental conflict and to improve googlability. This is the model that dynamicCallable and dynamicMemberLookup use as well (once the attribute is gone).

The rationale for this is that we want to keep the language simple and uniform, and don't want to introduce special syntactic forms. The entrypoint is func-like in every way, so if someone wants to explicitly use it (e.g. for partial application) then being able to name it is important.

-Chris

3 Likes