DynamicMemberLookup proposal: status update

Hi everyone,

With the holidays and many other things behind us, the core team had a chance to talk about python interop + the dynamic member lookup proposal recently.

Here’s where things stand: we specifically discussed whether a counter-proposal of using “automatically generated wrappers” or “foreign classes” to solve the problem would be better. After discussion, the conclusion is no: the best approach appears to be DynamicMemberLookup/DynamicCallable or something similar in spirit to them. As such, I’ll be dusting off the proposal and we’ll eventually run it.

For transparency, I’m attaching the analysis below of what a wrapper facility could look like, and why it doesn’t work very well for Python interop. I appologize in advance that this is sort of train-of-thought and not a well written doc.

That said, it would be really great to get tighter integration between Swift and SwiftPM for other purposes! I don’t have time to push this forward in the short term though, but if someone was interested in pushing it forward, many people would love to see it discussed seriously.

-Chris

A Swift automatic wrapper facility:

Requirements:
- We want the be able to run a user defined script to generate wrappers.
- This script can have arbitrary dependencies and should get updated when one of them change.
- These dependencies won’t be visible to the Xcode build system, so the compiler will have to manage them.
- In principle, one set of wrappers should be able to depend on another set, and wants “overlays”, so we need a pretty general model.

I don’t think the clang modules based approach is a good way to go.

Proposed Approach: Tighter integration between SwiftPM and Swift

The model is that you should be able to say (strawman syntax):

   import Foo from http://github.com/whatever/mypackage
   import Bar from file:///some/path/on/my/machine <file:///some/path/on/my/machine>

and have the compiler ask SwiftPM to build and cache the specified module onto your local disk, then have the compiler load it like any other module. This means that “generated wrappers” is now a SwiftPM/llbuild feature, and we can use the SwiftPM “language” to describe things like:

1. Information about what command line invocation is required to generate the wrappers.
2. Dependency information so that the compiler can regenerate the wrappers when they are out of date.
3. Platform abstraction tools since things are in different locations on linux vs mac, Python 2 vs Python 3 is also something that would have to be handled somehow.
4. The directory could contain manually written .swift code, serving the function similar to “overlays” to augment the automatic wrappers generated.

We care about Playgrounds and the REPL, and they should be able to work with this model.

I think that this would be a very nice and useful feature.

Using Wrappers to implement Python Interop:

While such a thing would be generally useful, it is important to explore how well this will work to solve the actual problem at hand, since this is being pitched as an alternative to DynamicMemberLookup. Here is the example from the list:

class BankAccount:
    def __init__(self, initial_balance: int = 0) -> None:
        self.balance = initial_balance
    def deposit(self, amount: int) -> None:
        self.balance += amount
    def withdraw(self, amount: int) -> None:
        self.balance -= amount
    def overdrawn(self) -> bool:
        return self.balance < 0

my_account = BankAccount(15)
my_account.withdraw(5)
print(my_account.balance)

The idea is to generate a wrapper like this (potentially including the type annotations as a refinement):

typealias BankAccount = PyVal
extension PyVal { // methods on BankAccount
  init(initial_balance: PyVal) { … }
  func deposit(amount: PyVal) -> PyVal { … }
  func withdraw(amount: PyVal) -> PyVal { … }
  func overdrawn() -> PyVal { … }
}

my_account = BankAccount(initial_balance: 15)
my_account.withdraw(amount: 5)
print(my_account.balance)

It is worth pointing out that this approach is very analogous to the “type providers” feature that Joe pushed hard for months ago, it is just a different implementation approach. The proposal specifically explains why this isn’t a great solution here:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#introduce-f-style-type-providers-into-swift

That said, while there are similarities, there are also differences with type providers. Here are the problems that I foresee:

1) This design still requires DynamicMemberLookup

This is because Python doesn’t have property declarations for the wrapper generator to process. The code above shows this on the last line: since there is no definition of the “balance" property, there will be no “balance member” declared in the PyVal extension. A wrapper generator can generate a decl for something it can’t “see”. You can see this in a simpler example:

class Car(object):
    def __init__(self):
        self.speed = 100

We really do want code like this to work:

let mini = Car()
print(mini.speed)

How will the wrapper generator produce a decl for ‘speed’ when no decl exists? Doug agreed through offline email that "we’d need to fall back to foo[dynamicMember: “speed”] or something like DynamicMemberLookup.” to handle properties.

2) Dumping properties and methods into the same scope won’t work

I didn’t expect this, and I’m not sure how this works, but apparently we accept this:

struct S {
  var x: Int
  func x(_ a: Int) -> Int { return a }
}

let x = S(x: 1)
x.x // returns 1, not a curried method. Bug or feature?
x.x(42) // returns 42

That said, we reject this:

struct S {
  var x: Int
  func x() -> Int { ... } // invalid redeclaration of x
}
which means that we’re going to have problems if we find a way to generate property decls (e.g. in response to @property in Python).

Even if we didn’t, we’d still have a problem if we wanted to incorporate types because we reject this:

struct S {
  var x: Int
  var x: Float // invalid redeclaration of X.
}

This means that even if we have property declarations (e.g. due to use of the Python @property marker for computed properties) we cannot actually incorporate type information into the synthesized header, because multiple classes have all their members munged together and will conflict.

Further, types in methods can lead to ambiguities too in some cases, e.g. if you have:

extension PyVal { // Class Dog
  func f(a: Float) -> Float { ... }
}
extension PyVal { // Class Cat
  func f(a: Int) {}
}

print(myDog.f(a: 1))

This compiles just fine, but prints out “()" instead of the result of your Dog method, because we select the second overload. In other cases, I suspect you’d fail to compile due to ambiguities. This is a case where types are really harmful for Python, and one of the reasons that the Python types do not affect runtime behavior at all.

3) It’s unclear how to incorporate initializers into this model.

The example above included this as suggested. It looks nice on the surface, but presents some problems:

typealias BankAccount = PyVal
extension PyVal { // methods on BankAccount
  init(initial_balance: Int) { … }
}

This has a couple of problems. First of all, in Python classes are themselves callable values, and this break that. Second this mooshes all of the initializers for all of the Python classes onto PyVal.

While it might seem that this would make code completion for initializers ugly, this isn’t actually a problem. After all, we’re enhancing code completion to know about PyVal already, so we can clearly use this trivial local information to filter the list down.

The actual problem is that multiple Python classes can have the same initializer, and we have no way to set the ‘self’ correctly in this case. Consider:

class BankAccount:
    def __init__(self, initial_balance: int = 0) -> None: …
class Gyroscope:
    def __init__(self, initial_balance: int = 0) -> None: …

These will require generating one extension:

extension PyVal { // methods on BankAccount
  init(initial_balance: Int) {
    self.init( /* what Python class do we allocate and pass here? BankAccount or Gyroscope? */ )
  }
}

I can think of a couple of solutions to this problem, I’d appreciate suggestions for other ones:

3A) Classes turn into typealiases, initializers get name mangled:

something like:

typealias BankAccount = PyVal
extension PyVal {
  init(BankAccount_initial_balance: PyVal) { … }
}
...
let my_account = BankAccount(BankAccount_initial_balance: 15)

I don’t like this: this is ugly for clients, and BankAccount is still itself not a value.

3B) Classes turn into global functions:

something like:

func BankAccount(initial_balance: PyVal) -> PyVal {… }
...
let my_account = BankAccount(initial_balance: 15)

This makes the common cases work, but breaks currying and just seems weird to me. Maybe this is ok? This also opens the door for:

// type annotation for clarity only.
let BankAccount: PyVal = BankingModule.BankAccount

which makes “BankAccount” itself be a value (looked up somehow on the module it is defined in).

3C) Classes turn into types that are callable with subscripts?

We don’t actually support static subscripts right now (seems like a silly limitation…):

struct BankAccount {
  // error, subscripts can’t be static.
  static subscript(initial_balance initial_balance: PyVal) -> PyVal { … }
}

let my_account = BankAccount[initial_balance: 15]

But we could emulate them with:

struct BankAccountType {
  subscript(initial_balance initial_balance: PyVal) -> PyVal { … }
}
var BankAccount: BankAccountType { return BankAccountType() }

let my_account = BankAccount[initial_balance: 15]

this could work, but the square brackets are “gross”, and BankAccount is not a useful Python metatype. We could improve the call-side syntax by introducing a “call decl” or a new “operator()” language feature like:

extension BankAccountType {
  func () (initial_balance: PyVal) -> PyVal { … }
}
var BankAccount: BankAccountType { return BankAccountType() }

let my_account = BankAccount(initial_balance: 15)

but this still doesn’t solve the “BankAccount as a python value” problem.

4) Throwing / Failability of APIs

Python, like many languages, doesn’t require you to specify whether APIs can throw or not, and because it is dynamically typed, member lookup itself is failable. That said, just like in C++, it is pretty uncommon for exceptions to be thrown from APIs, and our Error Handling design wants Swift programmers to think about error handling, not just slap try on everything.

The Python interop prototype handles this by doing this:

extension PyVal {
  /// Return a version of this value that may be called. It throws a Swift
  /// error if the underlying Python function throws a Python exception.
  public var throwing : ThrowingPyVal {
    return ThrowingPyVal(self)
  }
}

.. and both PyVal and ThrowingPyVal are callable. The implementation of DynamicCallable on ThrowingPyVal throws a Swift error but the implementation on PyVal does not (similar mechanic exists for DynamicMemberLookup).

This leads to a nice model: the PyVal currency type never throws a Swift error (it aborts on exception) so users don’t have to use “try” on everything. However, if they *want* to process an error, they can. Here is an example from the tutorial:

do {
  let x = try Python.open.throwing("/file/that/doesnt/exist")
  print(x)
} catch let err {
  print("file not found, just like we expected!")

  // Here is the error we got:
  print(err)
}

In practice, this works really nicely with the way that “try” is required for throwing values, but produces a warning when you use it unnecessarily.

Coming back to wrappers, it isn’t clear how to handle this: a direct port of this would require synthesizing all members onto both PyVal and ThrowingPyVal. This causes tons of bloat and undermines the goal of making the generated header nice.

-Chris

There’s a lot of information here and it’ll take some time to process it
all. My initial reaction is that a “strong type-alias” feature might help.
If one could write (strawman syntax):

strong typealias Dog = PyVal // A semantically independent new type

extension Dog {
    // Declarations here are only available on “Dog”, not on “PyVal”
}

then most of the overload issues would evaporate.

Nevin

···

On Thu, Jan 4, 2018 at 3:52 PM, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

Hi everyone,

With the holidays and many other things behind us, the core team had a
chance to talk about python interop + the dynamic member lookup proposal
recently.

Here’s where things stand: we specifically discussed whether a
counter-proposal of using “automatically generated wrappers” or “foreign
classes” to solve the problem would be better. After discussion, the
conclusion is no: the best approach appears to be DynamicMemberLookup/DynamicCallable
or something similar in spirit to them. As such, I’ll be dusting off the
proposal and we’ll eventually run it.

For transparency, I’m attaching the analysis below of what a wrapper
facility could look like, and why it doesn’t work very well for Python
interop. I appologize in advance that this is sort of train-of-thought and
not a well written doc.

That said, it would be really great to get tighter integration between
Swift and SwiftPM for other purposes! I don’t have time to push this
forward in the short term though, but if someone was interested in pushing
it forward, many people would love to see it discussed seriously.

-Chris

*A Swift automatic wrapper facility:*

Requirements:
- We want the be able to run a user defined script to generate wrappers.
- This script can have arbitrary dependencies and should get updated when
one of them change.
- These dependencies won’t be visible to the Xcode build system, so the
compiler will have to manage them.
- In principle, one set of wrappers should be able to depend on another
set, and wants “overlays”, so we need a pretty general model.

I don’t think the clang modules based approach is a good way to go.

*Proposed Approach: Tighter integration between SwiftPM and Swift*

The model is that you should be able to say (strawman syntax):

   import Foo from http://github.com/whatever/mypackage
   import Bar from file:///some/path/on/my/machine

and have the compiler ask SwiftPM to build and cache the specified module
onto your local disk, then have the compiler load it like any other
module. This means that “generated wrappers” is now a SwiftPM/llbuild
feature, and we can use the SwiftPM “language” to describe things like:

1. Information about what command line invocation is required to generate
the wrappers.
2. Dependency information so that the compiler can regenerate the wrappers
when they are out of date.
3. Platform abstraction tools since things are in different locations on
linux vs mac, Python 2 vs Python 3 is also something that would have to be
handled somehow.
4. The directory could contain manually written .swift code, serving the
function similar to “overlays” to augment the automatic wrappers generated.

We care about Playgrounds and the REPL, and they should be able to work
with this model.

I think that this would be a very nice and useful feature.

*Using Wrappers to implement Python Interop:*

While such a thing would be generally useful, it is important to explore
how well this will work to solve the actual problem at hand, since this is
being pitched as an alternative to DynamicMemberLookup. Here is the
example from the list:

class BankAccount:
    def __init__(self, initial_balance: int = 0) -> None:
        self.balance = initial_balance
    def deposit(self, amount: int) -> None:
        self.balance += amount
    def withdraw(self, amount: int) -> None:
        self.balance -= amount
    def overdrawn(self) -> bool:
        return self.balance < 0

my_account = BankAccount(15)
my_account.withdraw(5)print(my_account.balance)

The idea is to generate a wrapper like this (potentially including the
type annotations as a refinement):

typealias BankAccount = PyVal
extension PyVal { // methods on BankAccount
  init(initial_balance: PyVal) { … }
  func deposit(amount: PyVal) -> PyVal { … }
  func withdraw(amount: PyVal) -> PyVal { … }
  func overdrawn() -> PyVal { … }
}

my_account = BankAccount(initial_balance: 15)
my_account.withdraw(amount: 5)
print(my_account.balance)

It is worth pointing out that this approach is very analogous to the “type
providers” feature that Joe pushed hard for months ago, it is just a
different implementation approach. The proposal specifically explains why
this isn’t a great solution here:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae54
38#introduce-f-style-type-providers-into-swift

That said, while there are similarities, there are also differences with
type providers. Here are the problems that I foresee:

*1) This design still requires DynamicMemberLookup*

This is because Python doesn’t have property declarations for the wrapper
generator to process. The code above shows this on the last line: since
there is no definition of the “balance" property, there will be no “balance
member” declared in the PyVal extension. A wrapper generator can generate
a decl for something it can’t “see”. You can see this in a simpler example:

class Car(object):
    def __init__(self):
        self.speed = 100

We really do want code like this to work:

let mini = Car()
print(mini.speed)

How will the wrapper generator produce a decl for ‘speed’ when no decl
exists? Doug agreed through offline email that "we’d need to fall back to
foo[dynamicMember: “speed”] or something like DynamicMemberLookup.” to
handle properties.

*2) **Dumping properties and methods into the same scope won’t work*

I didn’t expect this, and I’m not sure how this works, but apparently we
accept this:

struct S {
  var x: Int
  func x(_ a: Int) -> Int { return a }
}

let x = S(x: 1)
x.x // returns 1, not a curried method. Bug or feature?
x.x(42) // returns 42

That said, we reject this:

struct S {
  var x: Int
  func x() -> Int { ... } // invalid redeclaration of x
}

which means that we’re going to have problems if we find a way to generate
property decls (e.g. in response to @property in Python).

Even if we didn’t, we’d still have a problem if we wanted to incorporate
types because we reject this:

struct S {
  var x: Int
  var x: Float // invalid redeclaration of X.
}

This means that even if we have property declarations (e.g. due to use of
the Python @property marker for computed properties) we cannot actually
incorporate type information into the synthesized header, because multiple
classes have all their members munged together and will conflict.

Further, types in methods can lead to ambiguities too in some cases, e.g.
if you have:

extension PyVal { // Class Dog
  func f(a: Float) -> Float { ... }
}
extension PyVal { // Class Cat
  func f(a: Int) {}
}

print(myDog.f(a: 1))

This compiles just fine, but prints out “()" instead of the result of your
Dog method, because we select the second overload. In other cases, I
suspect you’d fail to compile due to ambiguities. This is a case where
types are really harmful for Python, and one of the reasons that the Python
types do not affect runtime behavior at all.

*3) It’s unclear how to incorporate initializers into this model.*

The example above included this as suggested. It looks nice on the
surface, but presents some problems:

typealias BankAccount = PyVal
extension PyVal { // methods on BankAccount
  init(initial_balance: Int) { … }
}

This has a couple of problems. First of all, in Python classes are
themselves callable values, and this break that. Second this mooshes all
of the initializers for all of the Python classes onto PyVal.

While it might seem that this would make code completion for initializers
ugly, this isn’t actually a problem. After all, we’re enhancing code
completion to know about PyVal already, so we can clearly use this trivial
local information to filter the list down.

The actual problem is that multiple Python classes can have the same
initializer, and we have no way to set the ‘self’ correctly in this case.
Consider:

class BankAccount:
    def __init__(self, initial_balance: int = 0) -> None: …

class Gyroscope:
    def __init__(self, initial_balance: int = 0) -> None: …

These will require generating one extension:

extension PyVal { // methods on BankAccount
  init(initial_balance: Int) {
    self.init( /* what Python class do we allocate and pass here?
BankAccount or Gyroscope? */ )
  }
}

I can think of a couple of solutions to this problem, I’d appreciate
suggestions for other ones:

*3A) Classes turn into typealiases, initializers get name mangled:*

something like:

typealias BankAccount = PyVal
extension PyVal {
  init(BankAccount_initial_balance: PyVal) { … }
}
...
let my_account = BankAccount(BankAccount_initial_balance: 15)

I don’t like this: this is ugly for clients, and BankAccount is still
itself not a value.

*3B) Classes turn into global functions:*

something like:

func BankAccount(initial_balance: PyVal) -> PyVal {… }
...
let my_account = BankAccount(initial_balance: 15)

This makes the common cases work, but breaks currying and just seems weird
to me. Maybe this is ok? This also opens the door for:

// type annotation for clarity only.

let BankAccount: PyVal = BankingModule.BankAccount

which makes “BankAccount” itself be a value (looked up somehow on the
module it is defined in).

*3C) Classes turn into types that are callable with subscripts?*

We don’t actually support static subscripts right now (seems like a silly
limitation…):

struct BankAccount {
  // error, subscripts can’t be static.
  static subscript(initial_balance initial_balance: PyVal) -> PyVal { … }
}

let my_account = BankAccount[initial_balance: 15]

But we could emulate them with:

struct BankAccountType {
  subscript(initial_balance initial_balance: PyVal) -> PyVal { … }
}
var BankAccount: BankAccountType { return BankAccountType() }

let my_account = BankAccount[initial_balance: 15]

this could work, but the square brackets are “gross”, and BankAccount is
not a useful Python metatype. We could improve the call-side syntax by
introducing a “call decl” or a new “operator()” language feature like:

extension BankAccountType {
  func () (initial_balance: PyVal) -> PyVal { … }
}
var BankAccount: BankAccountType { return BankAccountType() }

let my_account = BankAccount(initial_balance: 15)

but this still doesn’t solve the “BankAccount as a python value” problem.

*4) Throwing / Failability of APIs*

Python, like many languages, doesn’t require you to specify whether APIs
can throw or not, and because it is dynamically typed, member lookup itself
is failable. That said, just like in C++, it is pretty uncommon for
exceptions to be thrown from APIs, and our Error Handling design wants
Swift programmers to think about error handling, not just slap try on
everything.

The Python interop prototype handles this by doing this:

extension PyVal {
  /// Return a version of this value that may be called. It throws a
Swift
  /// error if the underlying Python function throws a Python exception.
  public var throwing : ThrowingPyVal {
    return ThrowingPyVal(self)
  }
}

.. and both PyVal and ThrowingPyVal are callable. The implementation of
DynamicCallable on ThrowingPyVal throws a Swift error but the
implementation on PyVal does not (similar mechanic exists for
DynamicMemberLookup).

This leads to a nice model: the PyVal currency type never throws a Swift
error (it aborts on exception) so users don’t have to use “try” on
everything. However, if they *want* to process an error, they can. Here is
an example from the tutorial:

do {
  let x = *try* Python.open.throwing("/file/that/doesnt/exist")
  print(x)
} catch let err {
  print("file not found, just like we expected!")

  // Here is the error we got:
  print(err)
}

In practice, this works really nicely with the way that “try” is required
for throwing values, but produces a warning when you use it unnecessarily.

Coming back to wrappers, it isn’t clear how to handle this: a direct port
of this would require synthesizing all members onto both PyVal
and ThrowingPyVal. This causes tons of bloat and undermines the goal of
making the generated header nice.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Have you seen John Holdsworth's experiments?
<https://github.com/johnno1962/SwiftPython&gt;

-- Ben

···

On 4 Jan 2018, at 20:52, Chris Lattner wrote:

Hi everyone,

With the holidays and many other things behind us, the core team had a chance to talk about python interop + the dynamic member lookup proposal recently.

Here’s where things stand: we specifically discussed whether a counter-proposal of using “automatically generated wrappers” or “foreign classes” to solve the problem would be better. After discussion, the conclusion is no: the best approach appears to be DynamicMemberLookup/DynamicCallable or something similar in spirit to them. As such, I’ll be dusting off the proposal and we’ll eventually run it.

For transparency, I’m attaching the analysis below of what a wrapper facility could look like, and why it doesn’t work very well for Python interop. I appologize in advance that this is sort of train-of-thought and not a well written doc.

That said, it would be really great to get tighter integration between Swift and SwiftPM for other purposes! I don’t have time to push this forward in the short term though, but if someone was interested in pushing it forward, many people would love to see it discussed seriously.

-Chris

Until and if there were an acceptable design for such a feature, it is impossible to say whether it would help. I am personally skeptical that "strong type aliases" will ever make it into Swift.

-Chris

···

On Jan 4, 2018, at 3:43 PM, Nevin Brackett-Rozinsky <nevin.brackettrozinsky@gmail.com> wrote:

There’s a lot of information here and it’ll take some time to process it all. My initial reaction is that a “strong type-alias” feature might help. If one could write (strawman syntax):

strong typealias Dog = PyVal // A semantically independent new type

extension Dog {
    // Declarations here are only available on “Dog”, not on “PyVal”
}

then most of the overload issues would evaporate.

Yes indeed! He and I have been exchanging emails :-)

-Chris

···

On Jan 5, 2018, at 5:20 AM, Ben Rimmington <me@benrimmington.com> wrote:

Have you seen John Holdsworth's experiments?
<https://github.com/johnno1962/SwiftPython&gt;

-- Ben

On 4 Jan 2018, at 20:52, Chris Lattner wrote:

Hi everyone,

With the holidays and many other things behind us, the core team had a chance to talk about python interop + the dynamic member lookup proposal recently.

Here’s where things stand: we specifically discussed whether a counter-proposal of using “automatically generated wrappers” or “foreign classes” to solve the problem would be better. After discussion, the conclusion is no: the best approach appears to be DynamicMemberLookup/DynamicCallable or something similar in spirit to them. As such, I’ll be dusting off the proposal and we’ll eventually run it.

For transparency, I’m attaching the analysis below of what a wrapper facility could look like, and why it doesn’t work very well for Python interop. I appologize in advance that this is sort of train-of-thought and not a well written doc.

That said, it would be really great to get tighter integration between Swift and SwiftPM for other purposes! I don’t have time to push this forward in the short term though, but if someone was interested in pushing it forward, many people would love to see it discussed seriously.

-Chris

What abouting saying that DynamicMemberLookup/DynamicCallable are specific protocol for using @dynamic?


struct BankAccountType : PyVal {
  @dynamic var balance: PyVal // calls DynamicMemberLookup method with right arguments

  @dynamic init(initial_balance: PyVal) // calls DynamicCall method with right arguments
}

let my_account = BankAccount.init(initial_balance: 15)

Some advantages IMO:

  • a "similar" approach than with Obj-C when method are not known at compile-time
  • You can change setters/methods visibilities

I'm not sure exactly what the question is. @dynamic and DynamicMemberLookup are similar in that they relate to dynamic behavior, but they are otherwise completely different. @dynamic marks a property that exists but which is generated at runtime. DynamicMemberLookup has nothing to do with that. I don't see how the two features could usefully intersect, but maybe I'm missing your point.

Can you elaborate more on your idea?

Idea was to say "OK I have a property/method with no implementation but I do know it exists, so I here's its declaration". And then having DynamicMemberLookup used at runtime to actually handle the implementation.

Here's an example:

With DynamicMemberLookup (from my understanding of the proposal) one might be able to write something like this:

let dog = Dog(5) // wrong type but no compile error as parameter type is "only" PyVal

print(dog.nname) // wrong typing, but no compile error

One solution would be to provide Dog with all its methods/properties

struct Dog {
  var name: String { 
    get { return self[dynamicMember: "name"] }
    set { self[dynamicMember: "name"] = newValue }
  }

  init(name: String) {
     // self.dynamicCall(arguments: name)
  }
}

This is verbose so annotating (@dynamic, but might be something else) would simplify the code:

struct Dog {
  @dynamic var name: String
  @dynamic init(name: String) 
}

And as Dog is itself a python value and for annotation to work we would need to mark itself as a PyVal:

struct Dog: PyVal {
...
}

(Now that wouldn't work because PyVal is a struct in the proposal, but this is theory code anyway).

Is it clearer? :D

I don't see how this fits into the idea behind these protocols in the first place. They are specifically being introduced to ease the interoperability of dynamic languages where the type information is difficult or impossible for Swift to gather. As such, these protocols all revolve around some currency types (like PyVal).

Furthermore, wouldn't this already be possible given these new protocols?

I see what you mean. That could be an independently cool feature, but it is orthogonal to the proposal. It has the advantage of filling out our @dynamic model (which is presently pretty half baked), but is quite different because it takes a specific declaration as input.

Ok point noted and I agree it can be independent.