3
3
* Proposal: [ SE-0216] ( 0216-dynamic-callable.md )
4
4
* Authors: [ Chris Lattner] ( https://github.com/lattner ) , [ Dan Zheng] ( https://github.com/dan-zheng )
5
5
* Review Manager: [ John McCall] ( https://github.com/rjmccall )
6
- * Implementation: [ apple/swift #16980 ] ( https://github.com/apple/swift/pull/16980 )
6
+ * Implementation: [ apple/swift #20305 ] ( https://github.com/apple/swift/pull/20305 )
7
7
* Decision Notes: [ Rationale] ( https://forums.swift.org/t/accepted-se-216-user-defined-dynamically-callable-types/14110 )
8
8
* Status: ** Accepted**
9
9
10
10
## Introduction
11
11
12
- This proposal is a follow-on to [ SE-0195 - Introduce User-defined "Dynamic Member
13
- Lookup" Types] ( https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md )
12
+ This proposal is a follow-up to [ SE-0195 - Introduce User-defined "Dynamic Member
13
+ Lookup" Types] ( https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md ) ,
14
14
which shipped in Swift 4.2. It introduces a new ` @dynamicCallable ` attribute, which marks
15
15
a type as being "callable" with normal syntax. It is simple syntactic sugar
16
16
which allows the user to write:
@@ -19,7 +19,7 @@ which allows the user to write:
19
19
a = someValue (keyword1 : 42 , " foo" , keyword2 : 19 )
20
20
````
21
21
22
- and have it be interpreted by the compiler as :
22
+ and have it be rewritten by the compiler as :
23
23
24
24
```swift
25
25
a = someValue.dynamicallyCall (withKeywordArguments : [
@@ -113,7 +113,7 @@ This capability works well, but the syntactic burden of having to use
113
113
the syntactic weight, it directly harms code clarity by making code hard to
114
114
read and understand, cutting against a core value of Swift.
115
115
116
- The ` @dynamicCallable ` attribute in this proposal directly solves this problem.
116
+ The proposed ` @dynamicCallable ` attribute directly solves this problem.
117
117
With it, these examples become more natural and clear, effectively matching the
118
118
original Python code in expressiveness:
119
119
@@ -147,13 +147,14 @@ let blob = file.read()
147
147
let result = pickle.loads (blob)
148
148
```
149
149
150
- This is a proposal is purely syntactic sugar - it introduces no new semantic
151
- model to Swift at all . We believe that interoperability with scripting
150
+ This proposal merely introduces a syntactic sugar - it does not add any new
151
+ semantic model to Swift. We believe that interoperability with scripting
152
152
languages is an important and rising need in the Swift community, particularly
153
153
as Swift makes inroads into the server development and machine learning
154
- communities. This sort of capability is also highly precedented in other
155
- languages, and is a generally useful language feature that could be used for
156
- other purposes as well (e.g. for implementing dynamic proxy objects).
154
+ communities. This feature is also precedented in other languages (e.g. Scala's
155
+ [ ` Dynamic ` ] ( https://www.scala-lang.org/api/current/scala/Dynamic.html ) trait), and
156
+ can be used for other purposes besides language interoperability (e.g.
157
+ implementing dynamic proxy objects).
157
158
158
159
## Proposed solution
159
160
@@ -162,45 +163,53 @@ which may be applied to structs, classes, enums, and protocols. This follows
162
163
the precedent of
163
164
[ SE-0195] ( https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md ) .
164
165
165
- Before this proposal, values of these types are not valid in a function call
166
- position: the only callable values that Swift has are those with a function
167
- type (functions, methods, closures, etc) and metatypes (which are initializer
166
+ Before this proposal, values of these types are not valid in a call expression:
167
+ the only existing callable values in Swift are those with function types
168
+ (functions, methods, closures, etc) and metatypes (which are initializer
168
169
expressions like ` String(42) ` ). Thus, it is always an error to "call" an
169
170
instance of a nominal type (like a struct, for instance).
170
171
171
- With this proposal, types that adopt the new attribute on their primary type
172
- declaration become "callable" and are required to implement one or more methods
173
- for handling the call behavior.
174
-
175
- To support these cases, a type with this attribute is required to implement at
176
- least one of the following two methods. In the examples below, ` T* ` are
177
- arbitrary types, ` S* ` must conform to ` ExpressibleByStringLiteral ` (e.g.
178
- ` String ` and ` StaticString ` ).
172
+ With this proposal, types with the ` @dynamicCallable ` attribute on their
173
+ primary type declaration become "callable". They are required to implement at
174
+ least one of the following two methods for handling the call behavior:
179
175
180
176
``` swift
181
- func dynamicallyCall (withArguments : [T1]) -> T2
182
- func dynamicallyCall (withKeywordArguments : [S : T3]) -> T4
177
+ func dynamicallyCall (withArguments : <#Arguments#>) -> <#R1#>
178
+ // `<#Arguments#>` can be any type that conforms to `ExpressibleByArrayLiteral`.
179
+ // `<#Arguments#>.ArrayLiteralElement` and the result type `<#R1#>` can be arbitrary.
180
+
181
+ func dynamicallyCall (withKeywordArguments : <#KeywordArguments#>) -> <#R2#>
182
+ // `<#KeywordArguments#>` can be any type that conforms to `ExpressibleByDictionaryLiteral`.
183
+ // `<#KeywordArguments#>.Key` must be a type that conforms to `ExpressibleByStringLiteral`.
184
+ // `<#KeywordArguments#>.Value` and the result type `<#R2#>` can be arbitrary.
185
+
186
+ // Note: in these type signatures, bracketed types like <#Arguments#> and <#KeywordArguments#>
187
+ // are not actual types, but rather any actual type that meets the specified conditions.
183
188
```
184
189
185
- We write ` Arguments ` as an array type and ` KeywordArguments ` as a dictionary
186
- type, but these can actually be any type that conforms to the
187
- ` ExpressibleByArrayLiteral ` and ` ExpressibleByDictionaryLiteral ` protocols,
188
- respectively. The later is inclusive of
189
- [ ` KeyValuePairs ` ] ( https://developer.apple.com/documentation/swift/keyvaluepairs )
190
- which can represent multiple instances of a 'key' in the collection. This is
191
- important to support duplicated and positional arguments (because positional
192
- arguments have the empty string ` "" ` as their key).
190
+ As stated above, ` <#Arguments#> ` and ` <#KeywordArguments#> ` can be any types
191
+ that conform to the
192
+ [ ` ExpressibleByArrayLiteral ` ] ( https://developer.apple.com/documentation/swift/expressiblebyarrayliteral )
193
+ and
194
+ [ ` ExpressibleByDictionaryLiteral ` ] ( https://developer.apple.com/documentation/swift/expressiblebydictionaryliteral )
195
+ protocols, respectively. The latter is inclusive of
196
+ [ ` KeyValuePairs ` ] ( https://developer.apple.com/documentation/swift/keyvaluepairs ) ,
197
+ which supports duplicate keys, unlike [ ` Dictionary ` ] ( https://developer.apple.com/documentation/swift/dictionary ) .
198
+ Thus, using ` KeyValuePairs ` is recommended to support duplicate keywords and
199
+ positional arguments (because positional arguments are desugared as keyword
200
+ arguments with the empty string ` "" ` as the key).
193
201
194
202
If a type implements the ` withKeywordArguments: ` method, it may be dynamically
195
- called with both positional and keyword arguments ( positional arguments have
196
- the empty string ` "" ` as their key) . If a type only implements the
203
+ called with both positional and keyword arguments: positional arguments have
204
+ the empty string ` "" ` as the key. If a type only implements the
197
205
` withArguments: ` method but is called with keyword arguments, a compile-time
198
206
error is emitted.
199
207
200
- Because this is a syntactic sugar proposal, additional behavior of the
201
- implementation methods is directly expressed: for example, if these types are
202
- defined to be ` throws ` or ` @discardableResult ` then the corresponding sugared
203
- call is as well.
208
+ Since dynamic calls are syntactic sugar for direct calls to ` dynamicallyCall `
209
+ methods, additional behavior of the ` dynamicallyCall ` methods is directly
210
+ forwarded. For example, if a ` dynamicallyCall ` method is marked with ` throws `
211
+ or ` @discardableResult ` , then the corresponding sugared dynamic call will
212
+ forward that behavior.
204
213
205
214
### Ambiguity resolution: most specific match
206
215
@@ -209,17 +218,60 @@ handle some dynamic calls. What happens if a type specifies both the
209
218
` withArguments: ` and ` withKeywordArguments: ` methods?
210
219
211
220
We propose that the type checker resolve this ambiguity towards the tightest
212
- match based on syntactic form of the expression. If a type implements both the
213
- ` withArguments: ` and ` withKeywordArguments: ` methods, the compiler will use the
214
- ` withArguments: ` method for call sites that have no keyword arguments and the
215
- ` withKeywordArguments: ` method for call sites that have at least one keyword
216
- argument.
217
-
218
- This ambiguity resolution rule works out very naturally given the behavior of
219
- the Swift type checker, because it only resolves call expressions when the type
220
- of the base expression is known. At that point, it knows the capabilities of
221
- the type (whether the base is a function type, metatype, or a valid
222
- ` @dynamicCallable ` type where one of these two methods exist) and it knows the
221
+ match based on syntactic form of the expression. The exact rules are:
222
+
223
+ - If a ` @dynamicCallable ` type implements the ` withArguments: ` method and it is
224
+ called with no keyword arguments, use the ` withArguments: ` method.
225
+ - In all other cases, attempt to use the ` withKeywordArguments: ` method.
226
+ - This includes the case where a ` @dynamicCallable ` type implements the
227
+ ` withKeywordArguments: ` method and it is called with at least one keyword
228
+ argument.
229
+ - This also includes the case where a ` @dynamicCallable ` type implements only
230
+ the ` withKeywordArguments: ` method (not the ` withArguments: ` method) and
231
+ it is called with no keyword arguments.
232
+ - If ` @dynamicCallable ` type does not implement the ` withKeywordArguments: `
233
+ method but the call site has keyword arguments, an error is emitted.
234
+
235
+ Here are some toy illustrative examples:
236
+
237
+ ``` swift
238
+ @dynamicCallable
239
+ struct Callable {
240
+ func dynamicallyCall (withArguments args : [Int ]) -> Int { return args.count }
241
+ }
242
+ let c1 = Callable ()
243
+ c1 () // desugars to `c1.dynamicallyCall(withArguments: [])`
244
+ c1 (1 , 2 ) // desugars to `c1.dynamicallyCall(withArguments: [1, 2])`
245
+ c1 (a : 1 , 2 ) // error: `Callable` does not define the 'withKeywordArguments:' method
246
+
247
+ @dynamicCallable
248
+ struct KeywordCallable {
249
+ func dynamicallyCall (withKeywordArguments args : KeyValuePairs<String , Int >) -> Int {
250
+ return args.count
251
+ }
252
+ }
253
+ let c2 = KeywordCallable ()
254
+ c2 () // desugars to `c2.dynamicallyCall(withKeywordArguments: [:])`
255
+ c2 (1 , 2 ) // desugars to `c2.dynamicallyCall(withKeywordArguments: ["": 1, "": 2])`
256
+ c2 (a : 1 , 2 ) // desugars to `c2.dynamicallyCall(withKeywordArguments: ["a": 1, "": 2])`
257
+
258
+ @dynamicCallable
259
+ struct BothCallable {
260
+ func dynamicallyCall (withArguments args : [Int ]) -> Int { return args.count }
261
+ func dynamicallyCall (withKeywordArguments args : KeyValuePairs<String , Int >) -> Int {
262
+ return args.count
263
+ }
264
+ }
265
+ let c3 = BothCallable ()
266
+ c3 () // desugars to `c3.dynamicallyCall(withArguments: [])`
267
+ c3 (1 , 2 ) // desugars to `c3.dynamicallyCall(withArguments: [1, 2])`
268
+ c3 (a : 1 , 2 ) // desugars to `c3.dynamicallyCall(withKeywordArguments: ["a": 1, "": 2])`
269
+ ```
270
+
271
+ This ambiguity resolution rule works out naturally given the behavior of the
272
+ Swift type checker, because it only resolves call expressions when the type
273
+ of the base expression is known. At that point, it knows whether the base is a
274
+ function type, metatype, or a valid ` @dynamicCallable ` type, and it knows the
223
275
syntactic form of the call.
224
276
225
277
This proposal does not require massive or invasive changes to the constraint
0 commit comments