Unlike in Python, method references in JavaScript are unbound by default. Either @dynamicMemberCallable would be needed, or it might be possible to use bind() in the @dynamicMemberLookup implementation.
Yargh, JavaScript. Using @dynamicMemberCallable to bind methods seems like a fine solution:
extension JSObject {
func dynamicallyCallMethod(
named name: String, withArguments args: [JSObject]
) -> JSObject {
// Pseudocode: look up the method, bind it to `self`, then
// call the bound method.
return self[dynamicMember: name].bind(self).call(with: args)
}
}
Regarding throwing dynamic calls: some ideas were posted on the Swift for TensorFlow mailing list: Redirecting to Google Groups
@pvieito proposed the following:
That’s great! For the throwing case I would propose using:
let a = np.throwing.arange(15)
Which is clearer than:
let a = np.arange.throwing(15)
And better than:
let a = np.arange.throwing.dynamicallyCall(withArguments: 15)
I believe this is technically possible.
x.throwing would not be callable, but any dynamic members of x.throwing would be.
Sample implementation:
extension PythonObject {
var throwing: ThrowingPythonObject { ... }
}
/// This struct only has dynamic member lookup but is not callable.
/// This makes it so that `x.throwing` cannot be directly called.
struct ThrowingPythonObject {
public subscript(dynamicMember name: String)
-> ThrowingCallablePythonObject? { ... }
}
/// This struct has dynamic member lookup but is also callable.
struct ThrowingCallablePythonObject {
public subscript(dynamicMember name: String)
-> ThrowingCallablePythonObject? { ... }
// This method is throwing.
// Dynamic calls return a regular `PythonObject`.
public func dynamicallyCall(
withKeywordArguments args:
DictionaryLiteral<String, PythonConvertible> = [:]
) throws -> PythonObject { ... }
}
This enables syntax like the below:
import Python
let np = Python.import("numpy")
let a = try? np.throwing.arange(15)
// ^~ PythonObject
// ^~~~~~~~ ThrowingPythonObject
// ^~~~~~ ThrowingCallablePythonObject
// ^~~~ PythonObject (result of dynamic call)
One question is how to enable throwing behavior for top-level Python builtins, like Python.type since Python.type.throwing would no longer be callable.
An straightforward solution is to support Python.throwing.type(x). This would be done by defining a throwing property on PythonInterface (like PythonObject):
public struct PythonInterface {
var throwing: ThrowingPythonInterface { ... }
public subscript(dynamicMember name: String) -> PythonObject { ... }
}
public struct ThrowingPythonInterface {
public subscript(dynamicMember name: String) -> ThrowingCallablePythonObject {
return ThrowingCallablePythonObject(self[dynamicMember: name])
}
}
import Python
let list: PythonObject = [1, 2, 3]
let type: PythonObject? = try? Python.throwing.type(list)
// ^~~~~~ PythonInterface
// ^~~~~~~~ ThrowingPythonInterface
// ^~~~ ThrowingCallablePythonObject
// ^~~~~~ PythonObject (result of dynamic call)
I'm not sure which is more better: pickle.loads[.throwing](blob) or pickle.throwing.loads(blob).
The former is more "magical" but also more straightforward and doesn't collide with dynamic member lookup behavior (accessing a member called x.throwing).
The latter could be more intuitive, but the throwing property may confuse people who aren't familiar with the ThrowingPythonObject behavior and assume it's a dynamic member lookup.