From 8d7d386f7e864e5904cd84fe4302d9c18e7d4f5d Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Thu, 25 Apr 2019 17:13:38 -0700 Subject: [PATCH 01/11] Callable take two. --- proposals/0253-callable.md | 481 +++++++++++++++---------------------- 1 file changed, 198 insertions(+), 283 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index b92b05a200..bb0f6c558f 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -3,8 +3,9 @@ * Proposal: [SE-0253](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md) * Authors: [Richard Wei](https://github.com/rxwei), [Dan Zheng](https://github.com/dan-zheng) * Review Manager: [Chris Lattner](https://github.com/lattner) -* Status: **Returned for revision** -* Implementation: [apple/swift#23517](https://github.com/apple/swift/pull/23517) +* Status: **Awaiting review** +* Implementation: [apple/swift#24299](https://github.com/apple/swift/pull/24299) +* Previous Revisions: [[1](https://github.com/apple/swift-evolution/blob/36ea8be09508db9380949954d0c7a101fdb15226/proposals/0253-callable.md)] * Decision Notes: [Rationale](https://forums.swift.org/t/returned-for-revision-se-0253-static-callables/23290) ## Introduction @@ -16,13 +17,13 @@ In a nutshell, we propose to introduce a new declaration syntax with the keyword ```swift struct Adder { var base: Int - call(_ x: Int) -> Int { + func call(_ x: Int) -> Int { return base + x } } ``` -Values that have a `call` member can be applied like functions, forwarding arguments to the `call` member. +Values that have a `call` method can be applied like functions, forwarding arguments to the `call` method. ```swift let add3 = Adder(base: 3) @@ -89,7 +90,7 @@ print(polynomial[2]) // => 24 The proposed feature enables the same call syntax as the mathematical notation: ```swift extension Polynomial { - call(_ input: Float) -> Float { + func call(_ input: Float) -> Float { ... } } @@ -108,7 +109,7 @@ struct BoundClosure { var function: (T) -> Void var value: T - call() { return function(value) } + func call() { return function(value) } } let x = "Hello world!" @@ -177,7 +178,7 @@ struct Model { var flatten = Flatten() var dense = Dense(inputSize: 36 * 6, outputSize: 10) - call(_ input: Tensor) -> Tensor { + func call(_ input: Tensor) -> Tensor { // Call syntax. return dense(flatten(maxPool(conv(input)))) } @@ -209,7 +210,7 @@ struct Parser { When using a parser, one would need to explicitly call `applied(to:)`, but this is a bit cumbersome—the naming this API often repeats the type. Since parsers are like functions, it would be cleaner if the parser itself were callable. ```swift -call(_ input: String) throws -> Output { +func call(_ input: String) throws -> Output { // Using the stored state... } ``` @@ -234,7 +235,7 @@ Many languages offer the call syntax sugar: ### Unifying compound types and nominal types -A long term goal with the type system is to unify compound types (e.g. function types and tuple types) and nominal types, to allow compound types to conform to protocols and have members. When function types can have members, it will be most natural for them to have a `call` member, which can help unify the compiler's type checking rules for call expressions. +A long term goal with the type system is to unify compound types (e.g. function types and tuple types) and nominal types, to allow compound types to conform to protocols and have members. When function types can have members, it will be most natural for them to have a `call` method, which can help unify the compiler's type checking rules for call expressions. ## Proposed design @@ -243,13 +244,13 @@ We propose to introduce a new keyword `call` and a new declaration syntax–the ```swift struct Adder { var base: Int - call(_ x: Int) -> Int { + func call(_ x: Int) -> Int { return base + x } } ``` -Values that have a `call` member can be called like a function, forwarding arguments to the `call` member. +Values that have a `call` method can be called like a function, forwarding arguments to the `call` method. ```swift let add3 = Adder(base: 3) @@ -262,189 +263,81 @@ considered"](#alternative-ways-to-denote-call-syntax-delegate-methods) section. ## Detailed design -### `call` member declarations - -`call` members can be declared in structure types, enumeration types, class types, protocols, and extensions thereof. - -A `call` member declaration is similar to `subscript` in the following ways: - -* It does not take a name. -* It must be an instance member of a type. (Though there is [a pitch to add - static and class subscripts][static-and-class-subscripts].) - -But it is more similar to a `func` declaration in that: - -* It does not allow `get` and `set` declarations inside the body. -* When a parameter has a name, the name is treated as the argument label. - -The rest of the `call` declaration grammar and semantics is identical to that of -function declarations – it supports the same syntax for: -- Access levels. -- Generic parameter clauses. -- Argument labels. -- Return types. -- `throws` and `rethrows`. -- `mutating`. -- `where` clauses. - -`call` declarations can be overloaded based on argument and result types. -`call` declarations are inherited from superclasses, just like other class members. -Most modifiers/attributes that can be applied to function declarations can also be applied to -`call` declarations. - -
-Click here for a comprehensive list of modifiers/attributes supported by call declarations. - -
- -Preface: `call` declarations are implemented as a `CallDecl` class, inheriting -from `FuncDecl`, which in tern inherits from `AbstractFunctionDecl`. - -### Supported modifiers/attributes on `call` declarations - -The following attributes are supported on `AbstractFunctionDecl` or all -declarations, and thus by default are supported on `call` declarations. -(Disabling these attributes on `call` declarations is possible, but may require -ad-hoc implementation changes.) - -* `fileprivate`, `internal`, `public`, `open` -* `@available` -* `@objc` -* `@inlinable` -* `@inline` -* `@usableFromInline` -* `@_alwaysEmitIntoClient` -* `@_dynamicReplacement` -* `@_effects` -* `@_forbidSerializingReference`` -* `@_optimize` -* `@_silgen_name` -* `@_semantics` -* `@__raw_doc_comment` - -The following attributes are supported on `FuncDecl`, and are also are supported on `call` declarations. - -* `final` -* `optional` -* `dynamic` -* `__consuming` -* `mutating` -* `nonmutating` -* `override` -* `private` -* `rethrows` -* `@discardableResult` -* `@nonobjc` -* `@_cdecl` -* `@_implements` -* `@_implicitly_unwrapped_optional` -* `@_nonoverride` -* `@_specialize_` -* `@_transparent` -* `@_weakLinked` - -### Notable unsupported modifiers/attributes on `call` declarations - -* `@warn_unqualified_access` - * Qualified access is not possible because direct references to `call` declarations is not supported. - -
-
- -To support source compatibility, `call` is treated as a keyword only when -parsing members of a nominal type. Otherwise, it is treated as a normal -identifier. See the source compatibility section below. +### `call` methods -``` -call-declaration → call-head generic-parameter-clause? function-signature generic-where-clause? function-body? -call-head → attributes? declaration-modifiers? 'call' -``` +Instance methods whose base name is `call` will be recognized as an +implementation that makes a value of the enclosing type "callable" like a +function. -#### Examples +When type-checking a call expression, the type checker will try to resolve the +callee. Currently, the callee can be a value with a function type, a type name, +or a value of a `@dynamicCallable` type. This proposal adds a fourth kind of a +callee: a value with a matching `call` method. ```swift struct Adder { var base: Int - call(_ x: Int) -> Int { + func call(_ x: Int) -> Int { return base + x } - call(_ x: Float) -> Float { + func call(_ x: Float) -> Float { return Float(base) + x } - call(_ x: T, bang: Bool) throws -> T where T: BinaryInteger { + func call(_ x: T, bang: Bool) throws -> T where T: BinaryInteger { if bang { return T(Int(exactly: x)! + base) } else { return T(Int(truncatingIfNeeded: x) + base) } } - - // This is a normal function, not a `call` member. - func call(x: Int) {} } ``` -### Call expressions - -When type-checking a call expression, the type checker will try to resolve the callee. Currently, the callee can be a value with a function type, a type name, or a value of a `@dynamicCallable` type. This proposal adds a fourth kind of a callee: a value with a matching `call` member. - ```swift let add1 = Adder(base: 1) add1(2) // => 3 try add1(4, bang: true) // => 5 ``` -When type-checking fails, error messages look like those for function calls. When there is ambiguity, the compiler will show relevant `call` member candidates. +When type-checking fails, error messages look like those for function calls. +When there is ambiguity, the compiler will show relevant `call` method +candidates. ```swift add1("foo") // error: cannot invoke 'add1' with an argument list of type '(String)' -// note: overloads for 'call' exist with these partially matching parameter lists: (Float), (Int) +// note: overloads for functions named 'call' exist with these partially matching parameter lists: (Float), (Int) add1(1, 2, 3) // error: cannot invoke 'add1' with an argument list of type '(Int, Int, Int)' ``` ### When the type is also `@dynamicCallable` -A type can both have `call` members and be declared with `@dynamicCallable`. When type-checking a call expression, the type checker will first try to resolve the call to a function or initializer call, then a `call` member call, and finally a dynamic call. +A type can both have `call` functions and be declared with `@dynamicCallable`. +When type-checking a call expression, the type checker will first try to resolve +the call to a function or initializer call, then a `call` method call, and +finally a dynamic call. -### Using `call` members +## Source compatibility -Currently, `call` members cannot be directly referenced; create a closure to -call them instead. +This is a strictly additive proposal with no source-breaking changes. -```swift -let add1 = Adder(base: 1) -let f: (Int) -> Int = { x in add1(x) } -f(2) // => 3 -[1, 2, 3].map { x in add1(x) } // => [2, 3, 4] -``` +## Effect on ABI stability -`call` members are represented as instance methods with a special-case name and -not as instance methods with the actual name "call". +This is a strictly additive proposal with no ABI-breaking changes. -Thus, no redeclaration error is produced for types that define an instance method named "call" and a `call` member with the exact same type signature. +## Effect on API resilience -```swift -struct S { - func call() {} // ok - call() {} // ok -} -``` +This has no impact on API resilience which is not already captured by other language features. -When a type does not have `call` members, but has instance methods or an instance properties named "call", direct references to `call` are resolved via existing overload resolution logic. There's nothing new here. +## Future directions -```swift -struct S { - var call: Int = 0 -} -S().call // resolves to the property -``` +### Implicit conversions to function -A value cannot be implicitly converted to a function when the destination function type matches the type of the `call` member. +A value cannot be implicitly converted to a function when the destination function type matches the type of the `call` method. ```swift let h: (Int) -> Int = add1 // error: cannot convert value of type `Adder` to expected type `(Int) -> Int` @@ -461,60 +354,76 @@ A less controversial future direction is to support explicit conversion via `as` let h = add1 as (Int) -> Int ``` -## Source compatibility - -The proposed feature adds a `call` keyword. Normally, this would require existing identifiers named "call" to be escaped as `` `call` ``. However, this would break existing code using `call` identifiers, e.g. `func call`. +### Function type as a constraint -To maintain source compatibility, we propose making `call` a contextual keyword: that is, it is a keyword only in declaration contexts and a normal identifier elsewhere (e.g. in expression contexts). This means that `func call` and `call(...)` (apply expressions) continue to parse correctly. - -Here's a comprehensive example of parsing `call` in different contexts: +On the [pitch +thread](https://forums.swift.org/t/pitch-introduce-static-callables/21732/2), +@jckarter brought up the possibility of allowing function types to be used as +conformance constraints. Performance-minded programmers can define custom +closure types where the closure context is not fully type-erased. ```swift -struct Callable { - // declaration - call(_ body: () -> Void) { - // expression - call() {} - // expression - call {} - - struct U { - // declaration - call(x: Int) {} - - // declaration - call(function: (Int) -> Void) {} - - // error: expression in declaration context - // expected '(' for 'call' member parameters - call {} - } +struct BoundClosure ()>: () -> () { + var function: F + var value: T - let u = U() - // expression - u { x in } - } + func call() { return function(value) } } -// expression -call() {} -// expression -call {} +let f = BoundClosure({ print($0) }, x) // instantiates BoundClosure<(underlying type of closure), Int> +f() // invokes call on BoundClosure ``` -## Effect on ABI stability +In this design, the function type constraint behaves like a protocol that +requires a `call` method whose parameter types are the same as the function type's +parameter types. -Adding a new `call` declaration is an additive change to the ABI. +### `static`/`class` `call` methods -`call` declarations will not be supported when deploying to a Swift 5.0 runtime. +Static `call` methods could in theory look like initializers at the call site. -## Effect on API resilience +```swift +extension Adder { + static func call(base: Int) -> Int { + ... + } + static func call(_ x: Int) -> Int { + ... + } +} +Adder(base: 3) // error: ambiguous static member; do you mean `init(base:)` or `call(base:)`? +Adder(3) // okay, returns an `Int`, but it looks really like an initializer that returns an `Adder`. +``` -`call` declarations will not be supported when deploying to a Swift 5.0 runtime. +This is an interesting direction, but since parentheses followed by a type +identifier often connote initialization and it is not source-compatible, further +discussions in a separate pitch are necessary. ## Alternatives considered -### Alternative ways to denote call-syntax delegate methods +### Alternative ways to declare call-syntax delegate methods + +#### Create a new declaration kind like `subscript` and `init` + +Declarations that are associated with special invocation syntax often have their +own declaration kind. For example, subscripts are implemented with a `subscript` +declaration, and initialization calls are implemented with an `init` +declaration. Since the function call syntax is first-class, one direction is to +make the declaration be as first-class as possible. + +```swift +struct Adder { + var base: Int + call(_ x: Int) -> Int { + return base + x + } +} +``` + +This alternative is in fact [the proposed solution in first round of +review](https://github.com/apple/swift-evolution/blob/36ea8be09508db9380949954d0c7a101fdb15226/proposals/0253-callable.md), +which got [returned for +revision](https://forums.swift.org/t/returned-for-revision-se-0253-static-callables/23290). #### Use unnamed `func` declarations to mark call-syntax delegate methods @@ -525,17 +434,29 @@ struct Adder { func(_ x: Int) -> Int { return base + x } - // Option: `call` declaration modifier on unnamed `func` declarations. + // Option: `call` method modifier on unnamed `func` declarations. // Makes unnamed `func` less weird and clearly states "call". call func(_ x: Int) -> Int { ... } } ``` -This approach represents call-syntax delegate methods as unnamed `func` declarations instead of creating a new `call` declaration kind. +This approach represents call-syntax delegate methods as unnamed `func` +declarations instead of creating a new `call` method kind. -One option is to use `func(...)` without an identifier name. Since the word "call" does not appear, it is less clear that this denotes a call-syntax delegate method. Additionally, it's not clear how direct references would work: the proposed design of referencing `call` declarations via `foo.call` is clear and consistent with the behavior of `init` declarations. +One option is to use `func(...)` without an identifier name. Since the word +"call" does not appear, it is less clear that this denotes a call-syntax +delegate method. Additionally, it's not clear how direct references would work: +the proposed design of referencing `call` methods via `foo.call` is clear and +consistent with the behavior of `init` declarations. -To make unnamed `func(...)` less weird, one option is to add a `call` declaration modifier: `call func(...)`. The word `call` appears in both this option and the proposed design, clearly conveying "call-syntax delegate method". However, declaration modifiers are currently also treated as keywords, so with both approaches, parser changes to ensure source compatibility are necessary. `call func(...)` requires additional parser changes to allow `func` to sometimes not be followed by a name. The authors lean towards `call` declarations for terseness. +To make unnamed `func(...)` less weird, one option is to add a `call` +declaration modifier: `call func(...)`. The word `call` appears in both this +option and the proposed design, clearly conveying "call-syntax delegate method". +However, declaration modifiers are currently also treated as keywords, so with +both approaches, parser changes to ensure source compatibility are necessary. +`call func(...)` requires additional parser changes to allow `func` to sometimes +not be followed by a name. The authors lean towards `call` methods for +simplicity and uniformity. #### Use an attribute to mark call-syntax delegate methods @@ -549,11 +470,40 @@ struct Adder { } ``` -This approach achieves a similar effect as `call` declarations, except that methods can have a custom name and be directly referenced by that name. This is useful for types that want to make use of the call syntax sugar, but for which the name "call" does not accurately describe the callable functionality. - -However, we feel that using a `@callableMethod` method attribute is more noisy. Introducing a `call` declaration kind makes the concept of "callables" feel more first-class in the language, just like subscripts. `call` is to `()` as `subscript` is to `[]`. - -For reference: other languages with callable functionality typically require call-syntax delegate methods to have a particular name (e.g. `def __call__` in Python, `def apply` in Scala). +This approach achieves a similar effect as `call` methods, except that it allows +call-syntax delegate methods to have a custom name and be directly referenced by +that name. This is useful for types that want to make use of the call syntax +sugar, but for which the name "call" does not accurately describe the callable +functionality. + +However, there are two concerns. + +* First, we feel that using a `@callableMethod` method attribute is more noisy, + as many callable values do not need a special name for its call-syntax + delegate methods. + +* Second, custom names often involve argument labels that form a phrase with the + base name in order to be idiomatic. The grammaticality will be lost in the + call syntax when the base name disappears. + + ```swift + struct Layer { + ... + @callDelegate + func applied(to x: Int) -> Int { ... } + } + + let layer: Layer = ... + layer.applied(to: x) // Grammatical. + layer(to: x) // Broken. + ``` + +In contrast, standardizing on a specific name defines these problems away and +makes this feature easier to use. + +For reference: other languages with callable functionality typically require +call-syntax delegate methods to have a particular name (e.g. `def __call__` in +Python, `def apply` in Scala). #### Use `func` with a special name to mark call-syntax delegate methods @@ -566,7 +516,7 @@ struct Adder { } ``` -This approach represents call-syntax delegate methods as `func` declarations with a special name instead of creating a new `call` declaration kind. However, such `func` declarations do not convey "call-syntax delegate method" as clearly as the `call` keyword. +This approach represents call-syntax delegate methods as `func` declarations with a special name instead of creating a new `call` method kind. However, such `func` declarations do not convey "call-syntax delegate method" as clearly as the `call` keyword. #### Use a type attribute to mark types with call-syntax delegate methods @@ -585,15 +535,20 @@ struct Adder { } ``` -We feel this approach is not ideal because: +We feel this approach is not ideal because a marker type attribute is not +particularly meaningful. The call-syntax delegate methods of a type are what +make values of that type callable - a type attribute means nothing by itself. +There's also an unforunate edge case that must be explicitly handled: if a +`@staticCallable` type defines no call-syntax delegate methods, an error must be +produced. + +After the first round of review, the core team also did not think a type-level +attribute is necessary. -* A marker type attribute is not particularly meaningful. The call-syntax - delegate methods of a type are what make values of that type callable - a type - attribute means nothing by itself. There's an unforunate edge case that must - be explicitly handled: if a `@staticCallable` type defines no call-syntax - delegate methods, an error must be produced. -* The name for call-syntax delegate methods (e.g. `func call` ) is not - first-class in the language, while their call site syntax is. +> After discussion, the core team doesn't think that a type level attribute is +> necessary, and there is no reason to limit this to primal type declarations - +> it is fine to add callable members (or overloads) in extensions, just as you +> can add subscripts to a type in extensions today. #### Use a `Callable` protocol to represent callable types @@ -610,94 +565,54 @@ struct Adder: Callable { We feel this approach is not ideal for the same reasons as the marker type attribute. A marker protocol by itself is not meaningful and the name for call-syntax delegate methods is informal. Additionally, protocols should represent particular semantics, but call-*syntax* behavior has no inherent semantics. -In comparison, `call` declarations have a formal representation in the language and exactly indicate callable behavior (unlike a marker attribute or protocol). - -### Property-like `call` with getter and setter - -In C++, `operator()` can return a reference, which can be used on the left hand side of an assignment expression. This is used by some DSLs such as [Halide](http://halide-lang.org/docs/class_halide_1_1_func.html): - -```swift -Halide::Func foo; -Halide::Var x, y; -foo(x, y) = x + y; -``` - -This can be achieved via Swift's subscripts, which can have a getter and a setter. - -```swift -foo[x, y] = x + y -``` - -Since the proposed `call` declaration syntax is like `subscript` in many ways, it's in theory possible to allow `get` and `set` in a `call` declaration's body. - -```swift -call(x: T) -> U { - get { - ... - } - set { - ... - } -} -``` - -However, we do not believe `call` should behave like a storage accessor like `subscript`. Instead, `call`'s appearance should be as close to function calls as possible. Function call expressions today are not assignable because they can't return an l-value reference, so a call to a `call` member should not be assignable either. - -### Static `call` members - -Static `call` members could in theory look like initializers at the call site. - -```swift -extension Adder { - static call(base: Int) -> Int { - ... - } - static call(_ x: Int) -> Int { - ... - } -} -Adder(base: 3) // error: ambiguous static member; do you mean `init(base:)` or `call(base:)`? -Adder(3) // okay, returns an `Int`, but it looks really like an initializer that returns an `Adder`. -``` - -We believe that the initializer call syntax in Swift is baked tightly into programmers' mental model, and thus do not think overloading that is a good idea. - -We could also make it so that static `call` members can only be called via call expressions on metatypes. - -```swift -Adder.self(base: 3) // okay -``` - -But since this would be an additive feature on top of this proposal and that `subscript` cannot be `static` yet, we'd like to defer this feature to future discussions. - -### Direct references to `call` members - -Direct references to `call` members are intentionally not supported. An earlier -version of the proposal included support for direct references to `call` members -via `foo.call` (like referring to an instance method with the name "call"). - -However, the direction for "callable values" is not to support direct `call` -member references, but to support conversions of callable values to -function-typed values. Supporting explicit conversion via `as` seems relatively -non-controversial in forum discussions. Implicit conversion was a highly -requested feature, but it likely has type-system-wide impact and requires more -exploration. - -`call` members should be thought of and represented as "instance methods with a -special-case name", not "instance methods with the name 'call'". For now, -without support for conversion to function-typed values, create closures like -`{ foo(...) }` instead. +In comparison, `call` methods have a formal representation in the language and exactly indicate callable behavior (unlike a marker attribute or protocol). + +### Alternative names for call-syntax delegate methods + +In addition to `call`, there are other names that can be used to denote the +function call syntax. The most common ones are `apply` and `invoke` as they are +used to declare call-syntax delegate methods in other programming languages. + +Both `apply` and `invoke` are good one-syllable English words that are +technically correct, but we feel there are two concerns with these names: + +* They are new official terminology that need to be introduced. In [the +_Functions_ chapter of _The Swift Programming Language_ +book](https://docs.swift.org/swift-book/LanguageGuide/Functions.html), there is +no mentions of "apply" or "invoke" anywhere. Function calls are officially +called "function calls". + +* They do not work very well with Swift's API naming conventions. According to + [Swift API Design Guidelines - Strive for Fluent + Usage](https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage), + functions should be named according to their side-effects. + + > Those with side-effects should read as imperative verb phrases, e.g., + > `print(x)`, `x.sort()`, `x.append(y)`. + + Both `apply` and `invoke` are clearly imperative verb phrases. If call-syntax + delegate methods must be named `apply` or `invoke`, their declarations and + direct references will look almost certainly read like a mutating function + while they may not be. + + In contrast, `call` is both a noun and a verb. It is perfectly suited for + describing the precise functionality while not having a strong implication + about the function's side-effects. + + > **call**: + > _v._ Cause (a subroutine) to be executed. + > _n._ A command to execute a subroutine. ### Unify callable functionality with `@dynamicCallable` -Both `@dynamicCallable` and the proposed `call` members involve syntactic sugar related to function applications. However, the rules of the sugar are different, making unification difficult. In particular, `@dynamicCallable` provides a special sugar for argument labels that is crucial for usability. +Both `@dynamicCallable` and the proposed `call` methods involve syntactic sugar related to function applications. However, the rules of the sugar are different, making unification difficult. In particular, `@dynamicCallable` provides a special sugar for argument labels that is crucial for usability. ```swift // Let `PythonObject` be a `@dynamicMemberLookup` type with callable functionality. let np: PythonObject = ... // `PythonObject` with `@dynamicCallable. np.random.randint(-10, 10, dtype: np.float) -// `PythonObject` with `call` members. The empty strings are killer. +// `PythonObject` with `call` methods. The empty strings are killer. np.random.randint(["": -10, "": 10, "dtype": np.float]) ``` From ae3a6dc94cd8d7a956dd1f67f26cc04fd7bf0bcc Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Thu, 25 Apr 2019 17:17:30 -0700 Subject: [PATCH 02/11] Minor improvements. --- proposals/0253-callable.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index bb0f6c558f..2e968e923a 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -565,8 +565,6 @@ struct Adder: Callable { We feel this approach is not ideal for the same reasons as the marker type attribute. A marker protocol by itself is not meaningful and the name for call-syntax delegate methods is informal. Additionally, protocols should represent particular semantics, but call-*syntax* behavior has no inherent semantics. -In comparison, `call` methods have a formal representation in the language and exactly indicate callable behavior (unlike a marker attribute or protocol). - ### Alternative names for call-syntax delegate methods In addition to `call`, there are other names that can be used to denote the @@ -599,8 +597,10 @@ called "function calls". describing the precise functionality while not having a strong implication about the function's side-effects. - > **call**: + > **call** + > > _v._ Cause (a subroutine) to be executed. + > > _n._ A command to execute a subroutine. ### Unify callable functionality with `@dynamicCallable` From b4ebd1469a1ac272bc025f065e5bd3d3bcd29d51 Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Thu, 25 Apr 2019 17:18:31 -0700 Subject: [PATCH 03/11] Typo fix. --- proposals/0253-callable.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index 2e968e923a..e4ac04d5f1 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -590,7 +590,7 @@ called "function calls". Both `apply` and `invoke` are clearly imperative verb phrases. If call-syntax delegate methods must be named `apply` or `invoke`, their declarations and - direct references will look almost certainly read like a mutating function + direct references will almost certainly read like a mutating function while they may not be. In contrast, `call` is both a noun and a verb. It is perfectly suited for From bcf3a82c78ef1ba9056aca40495bf0ffe8a81e89 Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 01:15:29 -0700 Subject: [PATCH 04/11] Address comments. --- proposals/0253-callable.md | 43 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index e4ac04d5f1..1cf5b64df4 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -1,4 +1,4 @@ -# Introduce callables +# User-defined callable values * Proposal: [SE-0253](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md) * Authors: [Richard Wei](https://github.com/rxwei), [Dan Zheng](https://github.com/dan-zheng) @@ -10,9 +10,19 @@ ## Introduction -This proposal introduces [callables](https://en.wikipedia.org/wiki/Callable_object) to Swift. Callables are values that define function-like behavior and can be applied using function application syntax. +This proposal introduces "statically" +[callable](https://en.wikipedia.org/wiki/Callable_object) values to Swift. +Callable values are values that define function-like behavior and can be called +using function call syntax. In contrast to dynamically callable values +introduced in +[SE-0216](https://github.com/apple/swift-evolution/blob/master/proposals/0216-dynamic-callable.md), +this feature supports statically declared arities, argument labels, and +parameter types, and is not constrained to primary type declarations. -In a nutshell, we propose to introduce a new declaration syntax with the keyword `call`: +In a nutshell, values that have a method whose base name is `call` (referred to +as a "`call` method" for the rest of this proposal) can be called like a +function. The function call syntax forwards arguments to the corresponding +`call` method. ```swift struct Adder { @@ -21,11 +31,7 @@ struct Adder { return base + x } } -``` - -Values that have a `call` method can be applied like functions, forwarding arguments to the `call` method. -```swift let add3 = Adder(base: 3) add3(10) // => 13 ``` @@ -314,9 +320,22 @@ add1(1, 2, 3) // error: cannot invoke 'add1' with an argument list of type '(Int, Int, Int)' ``` +### Direct reference to `call` + +Since a `call` method is a normal method, one can refer to a `call` method using +its declaration name and get a closure where `self` is captured. This is exactly +how method references work today. + +```swift +let add1 = Adder(base: 1) +let f1: (Int) -> Int = add1.call +let f2: (Float) -> Float = add1.call(_:) +let f3: (Int, Bool) throws -> Int = add1.call(_:bang:) +``` + ### When the type is also `@dynamicCallable` -A type can both have `call` functions and be declared with `@dynamicCallable`. +A type can both have `call` methods and be declared with `@dynamicCallable`. When type-checking a call expression, the type checker will first try to resolve the call to a function or initializer call, then a `call` method call, and finally a dynamic call. @@ -597,11 +616,9 @@ called "function calls". describing the precise functionality while not having a strong implication about the function's side-effects. - > **call** - > - > _v._ Cause (a subroutine) to be executed. - > - > _n._ A command to execute a subroutine. + **call** + - _v._ Cause (a subroutine) to be executed. + - _n._ A command to execute a subroutine. ### Unify callable functionality with `@dynamicCallable` From 65b9f0bb53aed95edf6daeb9cc8bdf0360114e36 Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 01:34:27 -0700 Subject: [PATCH 05/11] More fixes. --- proposals/0253-callable.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index 1cf5b64df4..cc378bd887 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -245,7 +245,8 @@ A long term goal with the type system is to unify compound types (e.g. function ## Proposed design -We propose to introduce a new keyword `call` and a new declaration syntax–the call declaration syntax. +We propose to introduce a syntactic sugar for values that have an instance +method whose base name is `call` (a `call` method). ```swift struct Adder { @@ -256,16 +257,17 @@ struct Adder { } ``` -Values that have a `call` method can be called like a function, forwarding arguments to the `call` method. +Values that have a `call` method can be called like a function, forwarding +arguments to the `call` method. ```swift let add3 = Adder(base: 3) add3(10) // => 13 ``` -Note: there are many alternative syntaxes for marking "call-syntax delegate +Note: There are many alternative syntaxes for marking "call-syntax delegate methods". These are listed and explored in the ["Alternatives -considered"](#alternative-ways-to-denote-call-syntax-delegate-methods) section. +considered"](#alternative-ways-to-declare-call-syntax-delegate-methods) section. ## Detailed design @@ -377,9 +379,10 @@ let h = add1 as (Int) -> Int On the [pitch thread](https://forums.swift.org/t/pitch-introduce-static-callables/21732/2), -@jckarter brought up the possibility of allowing function types to be used as -conformance constraints. Performance-minded programmers can define custom -closure types where the closure context is not fully type-erased. +[Joe Groff](https://github.com/jckarter) brought up the possibility of allowing +function types to be used as conformance constraints. Performance-minded +programmers can define custom closure types where the closure context is not +fully type-erased. ```swift struct BoundClosure ()>: () -> () { @@ -535,7 +538,10 @@ struct Adder { } ``` -This approach represents call-syntax delegate methods as `func` declarations with a special name instead of creating a new `call` method kind. However, such `func` declarations do not convey "call-syntax delegate method" as clearly as the `call` keyword. +This approach represents call-syntax delegate methods as `func` declarations +with a special name instead of creating a new `call` method kind. However, such +`func` declarations do not convey "call-syntax delegate method" as clearly as +method declarations named `call`. #### Use a type attribute to mark types with call-syntax delegate methods From 8548d517bf1434c7863bd0621237ea1203984667 Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 01:36:06 -0700 Subject: [PATCH 06/11] Fix wording. --- proposals/0253-callable.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index cc378bd887..a7ecfbb845 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -442,8 +442,8 @@ struct Adder { } ``` -This alternative is in fact [the proposed solution in first round of -review](https://github.com/apple/swift-evolution/blob/36ea8be09508db9380949954d0c7a101fdb15226/proposals/0253-callable.md), +This alternative is in fact what's proposed in [the first revision of this +proposal](https://github.com/apple/swift-evolution/blob/36ea8be09508db9380949954d0c7a101fdb15226/proposals/0253-callable.md), which got [returned for revision](https://forums.swift.org/t/returned-for-revision-se-0253-static-callables/23290). From 5b53b3edb95d4d5ca5249fc978739a473ec63b19 Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 01:46:00 -0700 Subject: [PATCH 07/11] Simplify alternatives. --- proposals/0253-callable.md | 47 +++++++++++++++----------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index a7ecfbb845..b890bd3954 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -452,10 +452,15 @@ revision](https://forums.swift.org/t/returned-for-revision-se-0253-static-callab ```swift struct Adder { var base: Int - // Option: unnamed `func`. - func(_ x: Int) -> Int { - return base + x - } + // Option: `func` with literally no name. + func(_ x: Int) -> Int { ... } + + // Option: `func` with an underscore at the base name position. + func _(_ x: Int) -> Int + + // Option: `func` with a `self` keyword at the base name position. + func self(_ x: Int) -> Int + // Option: `call` method modifier on unnamed `func` declarations. // Makes unnamed `func` less weird and clearly states "call". call func(_ x: Int) -> Int { ... } @@ -463,7 +468,7 @@ struct Adder { ``` This approach represents call-syntax delegate methods as unnamed `func` -declarations instead of creating a new `call` method kind. +declarations instead of `func call`. One option is to use `func(...)` without an identifier name. Since the word "call" does not appear, it is less clear that this denotes a call-syntax @@ -523,26 +528,10 @@ However, there are two concerns. In contrast, standardizing on a specific name defines these problems away and makes this feature easier to use. -For reference: other languages with callable functionality typically require +For reference: Other languages with callable functionality typically require call-syntax delegate methods to have a particular name (e.g. `def __call__` in Python, `def apply` in Scala). -#### Use `func` with a special name to mark call-syntax delegate methods - -```swift -struct Adder { - var base: Int - // Option: specially-named `func` declarations. - func _(_ x: Int) -> Int - func self(_ x: Int) -> Int -} -``` - -This approach represents call-syntax delegate methods as `func` declarations -with a special name instead of creating a new `call` method kind. However, such -`func` declarations do not convey "call-syntax delegate method" as clearly as -method declarations named `call`. - #### Use a type attribute to mark types with call-syntax delegate methods ```swift @@ -592,17 +581,17 @@ We feel this approach is not ideal for the same reasons as the marker type attri ### Alternative names for call-syntax delegate methods -In addition to `call`, there are other names that can be used to denote the +In addition to `call`, there are other words that can be used to denote the function call syntax. The most common ones are `apply` and `invoke` as they are used to declare call-syntax delegate methods in other programming languages. Both `apply` and `invoke` are good one-syllable English words that are technically correct, but we feel there are two concerns with these names: -* They are new official terminology that need to be introduced. In [the -_Functions_ chapter of _The Swift Programming Language_ +* They are officially completely new terminology to Swift. In [the _Functions_ +chapter of _The Swift Programming Language_ book](https://docs.swift.org/swift-book/LanguageGuide/Functions.html), there is -no mentions of "apply" or "invoke" anywhere. Function calls are officially +no mention of "apply" or "invoke" anywhere. Function calls are officially called "function calls". * They do not work very well with Swift's API naming conventions. According to @@ -613,10 +602,10 @@ called "function calls". > Those with side-effects should read as imperative verb phrases, e.g., > `print(x)`, `x.sort()`, `x.append(y)`. - Both `apply` and `invoke` are clearly imperative verb phrases. If call-syntax + Both `apply` and `invoke` are clearly imperative verbs. If call-syntax delegate methods must be named `apply` or `invoke`, their declarations and - direct references will almost certainly read like a mutating function - while they may not be. + direct references will almost certainly read like a mutating function while + they may not be. In contrast, `call` is both a noun and a verb. It is perfectly suited for describing the precise functionality while not having a strong implication From 005c4c1154a3c0a013a7ee6e6813059777b65275 Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 18:01:43 -0700 Subject: [PATCH 08/11] Address comments. --- proposals/0253-callable.md | 108 +++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index b890bd3954..ce17fd3134 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -1,4 +1,4 @@ -# User-defined callable values +# User-defined non-function callable values * Proposal: [SE-0253](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md) * Authors: [Richard Wei](https://github.com/rxwei), [Dan Zheng](https://github.com/dan-zheng) @@ -342,6 +342,12 @@ When type-checking a call expression, the type checker will first try to resolve the call to a function or initializer call, then a `call` method call, and finally a dynamic call. +### Implementation + +The implementation is very simple: [less than 200 lines of +code](https://github.com/apple/swift/pull/24299) in the type checker that +performs lookup and expression rewrite. + ## Source compatibility This is a strictly additive proposal with no source-breaking changes. @@ -400,28 +406,43 @@ In this design, the function type constraint behaves like a protocol that requires a `call` method whose parameter types are the same as the function type's parameter types. -### `static`/`class` `call` methods +## Alternatives considered -Static `call` methods could in theory look like initializers at the call site. +### Alternative names for call-syntax delegate methods -```swift -extension Adder { - static func call(base: Int) -> Int { - ... - } - static func call(_ x: Int) -> Int { - ... - } -} -Adder(base: 3) // error: ambiguous static member; do you mean `init(base:)` or `call(base:)`? -Adder(3) // okay, returns an `Int`, but it looks really like an initializer that returns an `Adder`. -``` +In addition to `call`, there are other words that can be used to denote the +function call syntax. The most common ones are `apply` and `invoke` as they are +used to declare call-syntax delegate methods in other programming languages. -This is an interesting direction, but since parentheses followed by a type -identifier often connote initialization and it is not source-compatible, further -discussions in a separate pitch are necessary. +Both `apply` and `invoke` are good one-syllable English words that are +technically correct, but we feel there are two concerns with these names: -## Alternatives considered +* They are officially completely new terminology to Swift. In [the _Functions_ +chapter of _The Swift Programming Language_ +book](https://docs.swift.org/swift-book/LanguageGuide/Functions.html), there is +no mention of "apply" or "invoke" anywhere. Function calls are officially +called "function calls". + +* They do not work very well with Swift's API naming conventions. According to + [Swift API Design Guidelines - Strive for Fluent + Usage](https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage), + functions should be named according to their side-effects. + + > Those with side-effects should read as imperative verb phrases, e.g., + > `print(x)`, `x.sort()`, `x.append(y)`. + + Both `apply` and `invoke` are clearly imperative verbs. If call-syntax + delegate methods must be named `apply` or `invoke`, their declarations and + direct references will almost certainly read like a mutating function while + they may not be. + + In contrast, `call` is both a noun and a verb. It is perfectly suited for + describing the precise functionality while not having a strong implication + about the function's side-effects. + + **call** + - _v._ Cause (a subroutine) to be executed. + - _n._ A command to execute a subroutine. ### Alternative ways to declare call-syntax delegate methods @@ -579,41 +600,26 @@ struct Adder: Callable { We feel this approach is not ideal for the same reasons as the marker type attribute. A marker protocol by itself is not meaningful and the name for call-syntax delegate methods is informal. Additionally, protocols should represent particular semantics, but call-*syntax* behavior has no inherent semantics. -### Alternative names for call-syntax delegate methods - -In addition to `call`, there are other words that can be used to denote the -function call syntax. The most common ones are `apply` and `invoke` as they are -used to declare call-syntax delegate methods in other programming languages. +### Also allow `static`/`class` `call` methods -Both `apply` and `invoke` are good one-syllable English words that are -technically correct, but we feel there are two concerns with these names: +Static `call` methods could in theory look like initializers at the call site. -* They are officially completely new terminology to Swift. In [the _Functions_ -chapter of _The Swift Programming Language_ -book](https://docs.swift.org/swift-book/LanguageGuide/Functions.html), there is -no mention of "apply" or "invoke" anywhere. Function calls are officially -called "function calls". +```swift +extension Adder { + static func call(base: Int) -> Int { + ... + } + static func call(_ x: Int) -> Int { + ... + } +} +Adder(base: 3) // error: ambiguous static member; do you mean `init(base:)` or `call(base:)`? +Adder(3) // okay, returns an `Int`, but it looks really like an initializer that returns an `Adder`. +``` -* They do not work very well with Swift's API naming conventions. According to - [Swift API Design Guidelines - Strive for Fluent - Usage](https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage), - functions should be named according to their side-effects. - - > Those with side-effects should read as imperative verb phrases, e.g., - > `print(x)`, `x.sort()`, `x.append(y)`. - - Both `apply` and `invoke` are clearly imperative verbs. If call-syntax - delegate methods must be named `apply` or `invoke`, their declarations and - direct references will almost certainly read like a mutating function while - they may not be. - - In contrast, `call` is both a noun and a verb. It is perfectly suited for - describing the precise functionality while not having a strong implication - about the function's side-effects. - - **call** - - _v._ Cause (a subroutine) to be executed. - - _n._ A command to execute a subroutine. +This is an interesting direction, but parentheses followed by a type identifier +often connote initialization and it is not source-compatible. We believe this +would make call sites look very confusing. ### Unify callable functionality with `@dynamicCallable` From 72ede1bf7ce973cab8ef58c6ca45c2d627fbd83e Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 18:12:48 -0700 Subject: [PATCH 09/11] Rename title. --- proposals/0253-callable.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index ce17fd3134..295817e244 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -1,4 +1,4 @@ -# User-defined non-function callable values +# Callable values of user-defined nominal types * Proposal: [SE-0253](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md) * Authors: [Richard Wei](https://github.com/rxwei), [Dan Zheng](https://github.com/dan-zheng) From bf0949349f39eb034c16da0570dda7b9c376dc4d Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 18:14:38 -0700 Subject: [PATCH 10/11] Small fix. --- proposals/0253-callable.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index 295817e244..c1438cd626 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -344,10 +344,15 @@ finally a dynamic call. ### Implementation -The implementation is very simple: [less than 200 lines of +The implementation is very simple and non-invasive: [less than 200 lines of code](https://github.com/apple/swift/pull/24299) in the type checker that performs lookup and expression rewrite. +```swift +let add1 = Adder(base: 1) +add1(0) // Rewritten to `add1.call(0)` after type checking. +``` + ## Source compatibility This is a strictly additive proposal with no source-breaking changes. From 7db03fe67823bb4af60653d950cc19d64cd50069 Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Fri, 26 Apr 2019 18:33:44 -0700 Subject: [PATCH 11/11] Amend implicit conversions section. --- proposals/0253-callable.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/proposals/0253-callable.md b/proposals/0253-callable.md index c1438cd626..a0c43afde0 100644 --- a/proposals/0253-callable.md +++ b/proposals/0253-callable.md @@ -369,17 +369,20 @@ This has no impact on API resilience which is not already captured by other lang ### Implicit conversions to function -A value cannot be implicitly converted to a function when the destination function type matches the type of the `call` method. - -```swift -let h: (Int) -> Int = add1 // error: cannot convert value of type `Adder` to expected type `(Int) -> Int` -``` +A value cannot be implicitly converted to a function when the destination +function type matches the type of the `call` method. Since `call` methods are +normal methods, you can [refer to them directly](#direct-reference-to-call) via +`.call` and get a function. Implicit conversions impact the entire type system and require runtime support to work with dynamic casts; thus, further exploration is necessary for a formal proposal. This base proposal is self-contained; incremental proposals involving conversion can come later. +```swift +let h: (Int) -> Int = add1 +``` + A less controversial future direction is to support explicit conversion via `as`: ```swift