Proposal: Universal dynamic dispatch for method calls

That is nice, but if someone writes a method that's generic over <T: P> then your "override" won't get called. Seems like it's better to structure it like

protocol P { func f() } extension P { /// Default implementation
for `f`. Calls through to `_f()`. func f() { _f() } /// Helper
that provides the base functionality for `f`. func _f() { ... } }
struct S { func f() { ... _f() } }

This way you can write code that's generic over <T: P> (or that takes a
P object directly) and it will still call the overrides.

-Kevin Ballard

···

On Wed, Dec 9, 2015, at 11:01 AM, Gwendal Roué wrote:

Le 9 déc. 2015 à 19:52, Kevin Ballard via swift-evolution <swift- >> evolution@swift.org> a écrit :

b) methods defined in protocol extensions by definition can't be
   overridden already,

Methods defined in protocol extension actually can, sort of, be
overridden, and this is very useful:

protocol P { } extension P { func f() { … } } struct S
{ func f() { ... (self as P).f()
… } }

I know only one use case for this technique, in the groue/GRDB.swift
SQLite wrapper:

In this library, a DatabasePersistable protocol provides basic CRUD
operations, and a Record class adopts this protocol and "overrides"
with the technique above the protocol methods with extra features
provided by the class (especially change tracking).

The benefits of this architecture are:

- You can subclass use the full-featured Record base class, and get
  CRUD + change tracking for free.
- The Record subclasses can override the CRUD methods, and add custom
  code (validation, for example).
- You can have a custom struct adopt DatabasePersistable, and get CRUD
  for free.
- The custom structs that can also "override" the CRUD methods, and
  add custom code (validation, for example).

This is, in my opinion, a very valid use case for this "overriding".
Gwendal Roué

I quite knew about the caveat you’re talking about. This is what had prevented me from shipping this technique in the public API yet. I still need thinking.

And my current thinking is that this case is unlikely to happen (in my particular case, at least). Further, I’d rather document against this usage or even cutting off this feature, rather than adding this extra _f function. Talk about a simple API, when the library users needs a PhD in Swift dispatch subtleties to properly validate a poor struct before storing it in the database. That’s not my ambition when I write a library.

I hope, nevertheless, that I had shown:

1. that protocol default implementation can actually be "overriden", even if it is dangerous. If this danger can not be alleviated, then this is a hole in the language, and this hole may well need to be fixed because the inadequate usage I’ve show, should it reveal actually improper, will be discovered by others developers.

2. that there is a quite valid use case for letting adopting types "override" the default implementation of their protocol. The _f extra method is a lacking workaround.

Gwendal

···

Le 9 déc. 2015 à 22:13, Kevin Ballard <kevin@sb.org> a écrit :

That is nice, but if someone writes a method that's generic over <T: P> then your "override" won't get called. Seems like it's better to structure it like

protocol P {
    func f()
}
extension P {
    /// Default implementation for `f`. Calls through to `_f()`.
    func f() { _f() }
    /// Helper that provides the base functionality for `f`.
    func _f() { ... }
}
struct S {
    func f() {
        ...
        _f()
    }
}

This way you can write code that's generic over <T: P> (or that takes a P object directly) and it will still call the overrides.

-Kevin Ballard

On Wed, Dec 9, 2015, at 11:01 AM, Gwendal Roué wrote:

Le 9 déc. 2015 à 19:52, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

b) methods defined in protocol extensions by definition can't be overridden already,

Methods defined in protocol extension actually can, sort of, be overridden, and this is very useful:

    protocol P { }
    extension P {
        func f() { … }
    }
    struct S {
        func f() {
            ...
            (self as P).f()
            …
        }
    }

I know only one use case for this technique, in the groue/GRDB.swift SQLite wrapper:

In this library, a DatabasePersistable protocol provides basic CRUD operations, and a Record class adopts this protocol and "overrides" with the technique above the protocol methods with extra features provided by the class (especially change tracking).

The benefits of this architecture are:

- You can subclass use the full-featured Record base class, and get CRUD + change tracking for free.
- The Record subclasses can override the CRUD methods, and add custom code (validation, for example).
- You can have a custom struct adopt DatabasePersistable, and get CRUD for free.
- The custom structs that can also "override" the CRUD methods, and add custom code (validation, for example).

This is, in my opinion, a very valid use case for this "overriding".
Gwendal Roué

I quite knew about the caveat you’re talking about. This is what had prevented me from shipping this technique in the public API yet. I still need thinking.

And my current thinking is that this case is unlikely to happen (in my particular case, at least). Further, I’d rather document against this usage or even cutting off this feature, rather than adding this extra _f function. Talk about a simple API, when the library users needs a PhD in Swift dispatch subtleties to properly validate a poor struct before storing it in the database. That’s not my ambition when I write a library.

I don't understand. Why does the _f() approach impact library users at all? The only impact it should have on them is showing extra functions in the code completion list, but I seem to recall seeing some reference to doc comment changes being added to Swift to let you influence code completion by indicating that some other function should always be preferred to this one (e.g. so you can mark _f() as saying that users should always call f() instead).

The only code that needs to care about the distinction between f() and _f() is in implementations of the protocol P, and I don't see `_f()` as being any worse than `(self as P).f()`. If anything, it's better because it doesn't require the code reader to know whether f() was declared in the protocol (and is therefore subject to overriding).

I follow all of your points. That _f function is the only reasonable solution in the current state of affairs, I agree.

Yet, to me, `(self as P).f()`, targeted to the struct writer who has read the doc because he has a feature to write, looks odd, but less dangerous than a public _f function that is available to the struct user who *has not read the manual*. People don’t read the doc unless they have a strong need for it.

Like you, when I looked at this solution, I wanted to prefix with an underscore those extra methods: _insert, _update, etc. And exposing public methods with an underscore is so weird, don’t you agree? Again, we’re talking about code that lives in a library that aims at being useful, not an closed-source application with odd but private idiosyncrasies.

So. My goal is not to spoil this thread. I just want to say that as soon as default implementations were introduced in protocol extensions, for better and for worse, the need for some kind of overriding and `super` was introduced. The current state of the language requires, in all sanity, extra _f functions that are lacking, API-wise.

The rest of your message is about the fact that I don’t override anything. I know, that’s why I was using "overriding" with quotes. I hope I’ve been more clear in the previous paragraph.

···

Le 9 déc. 2015 à 22:41, Kevin Ballard <kevin@sb.org> a écrit :
On Wed, Dec 9, 2015, at 01:28 PM, Gwendal Roué wrote:

I hope, nevertheless, that I had shown:

1. that protocol default implementation can actually be "overriden", even if it is dangerous. If this danger can not be alleviated, then this is a hole in the language, and this hole may well need to be fixed because the inadequate usage I’ve show, should it reveal actually improper, will be discovered by others developers.

I disagree that this is a hole in the language. You aren't actually overriding anything in your version, you're just providing a method of the same name on the type itself, and according to the Swift language rules if someone calls the method on your type it will prefer your type's version instead of the protocol (this is just due to normal overloading behavior, where your type's method is considered more specific than the protocol and is therefore preferred). And I also think that the current behavior is the correct model; it would be genuinely surprising to me to have methods defined only in extensions actually be overridden by a concrete type when I'm calling the method on the protocol (as opposed to on the concrete type).

2. that there is a quite valid use case for letting adopting types "override" the default implementation of their protocol. The _f extra method is a lacking workaround.

Well yes, that's kind of the whole point of protocols, isn't it? The problem is you didn't define the method in the protocol. If you want a type to be able to override it, then you have to define it in the protocol. If you don't want it to be able to override it, then you define it in an extension.

I'm also a little confused here, because fixing this "hole" would break your code, as you'd no longer be able to get at the default implementation. The only way to recover that behavior, short of inventing brand new syntax, is to use the exact same _f() workaround I just suggested.

-Kevin Ballard

Le 9 déc. 2015 à 22:13, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> a écrit :

That is nice, but if someone writes a method that's generic over <T: P> then your "override" won't get called. Seems like it's better to structure it like

protocol P {
    func f()
}
extension P {
    /// Default implementation for `f`. Calls through to `_f()`.
    func f() { _f() }
    /// Helper that provides the base functionality for `f`.
    func _f() { ... }
}
struct S {
    func f() {
        ...
        _f()
    }
}

This way you can write code that's generic over <T: P> (or that takes a P object directly) and it will still call the overrides.

-Kevin Ballard

On Wed, Dec 9, 2015, at 11:01 AM, Gwendal Roué wrote:

Le 9 déc. 2015 à 19:52, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

b) methods defined in protocol extensions by definition can't be overridden already,

Methods defined in protocol extension actually can, sort of, be overridden, and this is very useful:

    protocol P { }
    extension P {
        func f() { … }
    }
    struct S {
        func f() {
            ...
            (self as P).f()
            …
        }
    }

I know only one use case for this technique, in the groue/GRDB.swift SQLite wrapper:

In this library, a DatabasePersistable protocol provides basic CRUD operations, and a Record class adopts this protocol and "overrides" with the technique above the protocol methods with extra features provided by the class (especially change tracking).

The benefits of this architecture are:

- You can subclass use the full-featured Record base class, and get CRUD + change tracking for free.
- The Record subclasses can override the CRUD methods, and add custom code (validation, for example).
- You can have a custom struct adopt DatabasePersistable, and get CRUD for free.
- The custom structs that can also "override" the CRUD methods, and add custom code (validation, for example).

This is, in my opinion, a very valid use case for this "overriding".
Gwendal Roué

Yes, and this is really the reason why a `_` prefix is bad for a use case that should be ranked Easy in the Obvious / Easy / Possible scale (The Obvious, the Easy, and the Possible – Signal v. Noise)

Gwendal

···

Le 9 déc. 2015 à 23:07, Brent Royal-Gordon <brent@architechies.com> a écrit :

Yet, to me, `(self as P).f()`, targeted to the struct writer who has read the doc because he has a feature to write, looks odd, but less dangerous than a public _f function that is available to the struct user who *has not read the manual*. People don’t read the doc unless they have a strong need for it.

You don’t have to read the manual to know that you keep your mitts off something with an `_` prefix unless you know what you’re doing.

The details of the solution are tricky, but I like this general approach with “final” (or whatever the right keyword is). It passes the smell test for me.

I’ve written up a draft of about half of a formal proposal at <https://github.com/brentdax/swift-evolution/blob/master/proposals/0000-require-final-on-protocol-extension-methods.md&gt;\. It provides a bit more detail and hopefully a more complete rationale for why I favor the design I’ve proposed.

···

--
Brent Royal-Gordon
Architechies

2) Swift’s general slant is towards using virtual dispatch unless it’s forbidden. (Leaving aside cases where the optimizer can prove at compile time what a dynamic dispatch would do.) That’s why we have a `final` keyword instead of a `virtual` keyword. Because of this, it’s not surprising to Swift users that you *can* override some extension methods; rather, the surprise is that you *can’t* override others. (And the *real* surprise is that you can kind-of-but-not-really override them, and Swift doesn’t complain.) I want developers to mark the surprise.

I don't agree that Swift's general slant is towards using virtual dispatch. Only classes and protocol objects use virtual dispatch[1]. And Swift even provides a mechanism (final / static) to remove dynamic dispatch from classes. It seems to me that Swift's general slant is towards encouraging static dispatch whenever possible.

I quite disagree here.

Swift loves to dispatch things statically and does so wherever possible. In some cases—such as value types not having inheritance—language features are clearly designed the way they are specifically to allow them to be statically dispatched. But Swift never uses static dispatch where dynamic dispatch would have given a different result. This contrasts with, for instance, a non-virtual C++ method’s behavior of “ha ha ha, I’m just going to ignore your override for speed." You could write a Swift compiler which dispatched everything dynamically, and you would never see any difference in semantics.

The sole exception to this rule is protocol extensions. There, we see static behavior with no explicit request for it. That’s why I think there *should* be a keyword requesting static behavior in this context—because it turns the static behavior into a feature rather than a bug.

···

--
Brent Royal-Gordon
Architechies

I wanted to thank you, Kevin Ballard, for your help, despite my stubbornness, and apologize to all readers for having diverged from the initial topic of the thread.

If you are interested, the result of this protocol clean up lies at GitHub - groue/GRDB.swift: A toolkit for SQLite databases, with a focus on application development

Gwendal Roué

···

Le 9 déc. 2015 à 22:13, Kevin Ballard <kevin@sb.org> a écrit :

That is nice, but if someone writes a method that's generic over <T: P> then your "override" won't get called. Seems like it's better to structure it like

protocol P {
    func f()
}
extension P {
    /// Default implementation for `f`. Calls through to `_f()`.
    func f() { _f() }
    /// Helper that provides the base functionality for `f`.
    func _f() { ... }
}
struct S {
    func f() {
        ...
        _f()
    }
}

This way you can write code that's generic over <T: P> (or that takes a P object directly) and it will still call the overrides.

-Kevin Ballard

On Wed, Dec 9, 2015, at 11:01 AM, Gwendal Roué wrote:

Le 9 déc. 2015 à 19:52, Kevin Ballard via swift-evolution <swift-evolution@swift.org> a écrit :

b) methods defined in protocol extensions by definition can't be overridden already,

Methods defined in protocol extension actually can, sort of, be overridden, and this is very useful:

    protocol P { }
    extension P {
        func f() { … }
    }
    struct S {
        func f() {
            ...
            (self as P).f()
            …
        }
    }

I know only one use case for this technique, in the groue/GRDB.swift SQLite wrapper:

In this library, a DatabasePersistable protocol provides basic CRUD operations, and a Record class adopts this protocol and "overrides" with the technique above the protocol methods with extra features provided by the class (especially change tracking).

The benefits of this architecture are:

- You can subclass use the full-featured Record base class, and get CRUD + change tracking for free.
- The Record subclasses can override the CRUD methods, and add custom code (validation, for example).
- You can have a custom struct adopt DatabasePersistable, and get CRUD for free.
- The custom structs that can also "override" the CRUD methods, and add custom code (validation, for example).

This is, in my opinion, a very valid use case for this "overriding".
Gwendal Roué

I quite knew about the caveat you’re talking about. This is what had
prevented me from shipping this technique in the public API yet. I
still need thinking.

And my current thinking is that this case is unlikely to happen (in my
particular case, at least). Further, I’d rather document against this
usage or even cutting off this feature, rather than adding this extra
_f function. Talk about a simple API, when the library users needs a
PhD in Swift dispatch subtleties to properly validate a poor struct
before storing it in the database. That’s not my ambition when I write
a library.

I don't understand. Why does the _f() approach impact library users at
all? The only impact it should have on them is showing extra functions
in the code completion list, but I seem to recall seeing some reference
to doc comment changes being added to Swift to let you influence code
completion by indicating that some other function should always be
preferred to this one (e.g. so you can mark _f() as saying that users
should always call f() instead).

The only code that needs to care about the distinction between f() and
_f() is in implementations of the protocol P, and I don't see `_f()` as
being any worse than `(self as P).f()`. If anything, it's better because
it doesn't require the code reader to know whether f() was declared in
the protocol (and is therefore subject to overriding).

I hope, nevertheless, that I had shown:

1. that protocol default implementation can actually be "overriden",
   even if it is dangerous. If this danger can not be alleviated, then
   this is a hole in the language, and this hole may well need to be
   fixed because the inadequate usage I’ve show, should it reveal
   actually improper, will be discovered by others developers.

I disagree that this is a hole in the language. You aren't actually
overriding anything in your version, you're just providing a method of
the same name on the type itself, and according to the Swift language
rules if someone calls the method on your type it will prefer your
type's version instead of the protocol (this is just due to normal
overloading behavior, where your type's method is considered more
specific than the protocol and is therefore preferred). And I also think
that the current behavior is the correct model; it would be genuinely
surprising to me to have methods defined only in extensions actually be
overridden by a concrete type when I'm calling the method on the
protocol (as opposed to on the concrete type).

2. that there is a quite valid use case for letting adopting types
   "override" the default implementation of their protocol. The _f
   extra method is a lacking workaround.

Well yes, that's kind of the whole point of protocols, isn't it? The
problem is you didn't define the method in the protocol. If you want a
type to be able to override it, then you have to define it in the
protocol. If you don't want it to be able to override it, then you
define it in an extension.

I'm also a little confused here, because fixing this "hole" would break
your code, as you'd no longer be able to get at the default
implementation. The only way to recover that behavior, short of
inventing brand new syntax, is to use the exact same _f() workaround I
just suggested.

-Kevin Ballard

···

On Wed, Dec 9, 2015, at 01:28 PM, Gwendal Roué wrote:

Le 9 déc. 2015 à 22:13, Kevin Ballard <kevin@sb.org> a écrit :

That is nice, but if someone writes a method that's generic over <T:
> then your "override" won't get called. Seems like it's better to
structure it like

protocol P { func f() } extension P { /// Default
implementation for `f`. Calls through to `_f()`. func f() { _f() }
/// Helper that provides the base functionality for `f`. func _f()
{ ... } } struct S { func f() { ... _f() } }

This way you can write code that's generic over <T: P> (or that takes
a P object directly) and it will still call the overrides.

-Kevin Ballard

On Wed, Dec 9, 2015, at 11:01 AM, Gwendal Roué wrote:

Le 9 déc. 2015 à 19:52, Kevin Ballard via swift-evolution <swift- >>>> evolution@swift.org> a écrit :

b) methods defined in protocol extensions by definition can't be
   overridden already,

Methods defined in protocol extension actually can, sort of, be
overridden, and this is very useful:

protocol P { } extension P { func f() { … } } struct
S { func f() { ... (self as P).f()
… } }

I know only one use case for this technique, in the groue/GRDB.swift
SQLite wrapper:

In this library, a DatabasePersistable protocol provides basic CRUD
operations, and a Record class adopts this protocol and "overrides"
with the technique above the protocol methods with extra features
provided by the class (especially change tracking).

The benefits of this architecture are:

- You can subclass use the full-featured Record base class, and get
  CRUD + change tracking for free.
- The Record subclasses can override the CRUD methods, and add
  custom code (validation, for example).
- You can have a custom struct adopt DatabasePersistable, and get
  CRUD for free.
- The custom structs that can also "override" the CRUD methods, and
  add custom code (validation, for example).

This is, in my opinion, a very valid use case for this "overriding".
Gwendal Roué

Yet, to me, `(self as P).f()`, targeted to the struct writer who has read the doc because he has a feature to write, looks odd, but less dangerous than a public _f function that is available to the struct user who *has not read the manual*. People don’t read the doc unless they have a strong need for it.

You don’t have to read the manual to know that you keep your mitts off something with an `_` prefix unless you know what you’re doing.

···

--
Brent Royal-Gordon
Architechies

I don't agree that Swift's general slant is towards using virtual dispatch. Only classes and protocol objects use virtual dispatch[1]. And Swift even provides a mechanism (final / static) to remove dynamic dispatch from classes. It seems to me that Swift's general slant is towards encouraging static dispatch whenever possible.

I quite disagree here.

Swift loves to dispatch things statically and does so wherever possible. In some cases—such as value types not having inheritance—language features are clearly designed the way they are specifically to allow them to be statically dispatched. But Swift never uses static dispatch where dynamic dispatch would have given a different result.

Exactly so.

Also note that, as I laid out in my original post, this is _especially_ true of the `foo.bar()` syntax — which in most programming languages is the syntax specifically set aside for dynamic dispatch. I’d prefer that the semantics of a particular syntax be as uniform as possible.

I’m a lot less surprised about a function overload, for example, being resolved by the static type of its arguments than I am about `foo.bar()` depending on the static type of foo.

If it’s not just Kevin who disagrees with the assertion that static dispatch of `foo.bar()` is unexpected and a likely source of programmer error, then we should stop arguing about it and gather some data.

Cheers, P

Yes it does. When message dispatch is done using the obj-c runtime, the compiler is free to omit the message send and use static dispatch if it believes it knows the concrete type of the receiver. This normally behaves the same, because dynamic dispatch with a known receiver type and static dispatch are the same, except if the method is dynamically replaced using the obj-c runtime methods, the static dispatch will not invoke the dynamically overridden method. The most common way you see this is with KVO. If you try and KVO an @objc property on a Swift object, it may work sometimes and may not work at other times, depending on when the compiler believes it can safely use static dispatch. This is why Swift has a whole keyword called `dynamic` whose job is to say "no really, use dynamic dispatch for every single access to this method/property, I don't care that you know for a fact it's a Foo and not a subclass, just trust me on this".

More generally, I don't see there being any real difference between

extension Proto {
    func someMethodNotDefinedInTheProto()
}

and saying

func someFuncThatUsesTheProto<T: Proto>(x: T)

except that one is invoked using method syntax and one is invoked using function syntax. Method invocation syntax does not inherently mean "dynamic dispatch" any more than function syntax does.

-Kevin Ballard

···

On Thu, Dec 10, 2015, at 02:24 AM, Brent Royal-Gordon wrote:

Swift loves to dispatch things statically and does so wherever possible. In some cases—such as value types not having inheritance—language features are clearly designed the way they are specifically to allow them to be statically dispatched. But Swift never uses static dispatch where dynamic dispatch would have given a different result. This contrasts with, for instance, a non-virtual C++ method’s behavior of “ha ha ha, I’m just going to ignore your override for speed." You could write a Swift compiler which dispatched everything dynamically, and you would never see any difference in semantics.

Thank you for writing this up. I have a few concerns:

1. The usage of `final` seems to be conflating class behavior and protocol behavior. Your stated reason for preferring `final` can be summed up as you view Swift from a virtual dispatch worldview, but the keyword doesn't make any sense here when viewed from a static dispatch worldview. I say it doesn't make sense based on the existing meaning of `final`, where it applies to classes only, and restricts subclasses.
2. I also worry that `final` will be confusing when you consider protocol inheritance; if a protocol P has an extension method foo(), and a protocol Q inherits P and also defines foo(), the usage of `final` suggests that should be disallowed when in fact it's perfectly fine.
3. I still don't understand the point of @incoherent. I feel like it's just going to be extra boilerplate that very few people are actually going to understand the reason for, and almost everyone will just add it when the compiler yells at them. Or since you propose not even offering it as a Fix-It, then people will think Swift simply doesn't support having the same method name on a type and on a protocol, and will view that as an annoying limitation.
4. I also think @incoherent only makes any sense at all if you view Swift from a dynamic dispatch worldview (not even virtual dispatch; protocol extensions don't have a virtual function table, so a virtual dispatch view should be fine with current behavior). Which is to say, there's simply no conflict when viewed from a static dispatch worldview. So this attribute is going to be required for everyone, even when we don't see it as being in conflict.

I do think it's valid to say that you can't tell from looking at a protocol extension which methods are default implementations and which ones are new methods that aren't part of the protocol, and that's why I proposed the `default` keyword. But using that keyword also means no @incoherent (because there's no implicit acknowledgement of a "conflict" like with the `final` keyword, it simply seeks to disambiguate which methods are default implementations).

-Kevin

···

On Thu, Dec 10, 2015, at 02:09 AM, Brent Royal-Gordon wrote:

> The details of the solution are tricky, but I like this general approach with “final” (or whatever the right keyword is). It passes the smell test for me.

I’ve written up a draft of about half of a formal proposal at <https://github.com/brentdax/swift-evolution/blob/master/proposals/0000-require-final-on-protocol-extension-methods.md&gt;\. It provides a bit more detail and hopefully a more complete rationale for why I favor the design I’ve proposed.

--
Brent Royal-Gordon
Architechies

Totally unrelated to your point, but FYI “dynamic” is a declaration modifier, not a keyword. "var dynamic = 42” is perfectly legal in Swift.

This is one of the advantages of having a keyword at the start of every decl and statement: we get decl modifiers without taking keywords. You can see the list of them here (search for DeclModifier):

-Chris

···

On Dec 10, 2015, at 4:31 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

This is why Swift has a whole keyword called `dynamic` whose job is…

Swift loves to dispatch things statically and does so wherever possible. In some cases—such as value types not having inheritance—language features are clearly designed the way they are specifically to allow them to be statically dispatched. But Swift never uses static dispatch where dynamic dispatch would have given a different result. This contrasts with, for instance, a non-virtual C++ method’s behavior of “ha ha ha, I’m just going to ignore your override for speed." You could write a Swift compiler which dispatched everything dynamically, and you would never see any difference in semantics.

Yes it does. When message dispatch is done using the obj-c runtime, the compiler is free to omit the message send and use static dispatch if it believes it knows the concrete type of the receiver. This normally behaves the same, because dynamic dispatch with a known receiver type and static dispatch are the same, except if the method is dynamically replaced using the obj-c runtime methods, the static dispatch will not invoke the dynamically overridden method. The most common way you see this is with KVO. If you try and KVO an @objc property on a Swift object, it may work sometimes and may not work at other times, depending on when the compiler believes it can safely use static dispatch. This is why Swift has a whole keyword called `dynamic` whose job is to say "no really, use dynamic dispatch for every single access to this method/property, I don't care that you know for a fact it's a Foo and not a subclass, just trust me on this”.

Okay, time to introduce some precise definitions so we don’t talk past each other.

There are three types of dispatch:

* Static. The compiler determines the exact function to execute.
* Virtual (I was previously calling this “dynamic”). The compiler determines the function to execute’s position in the instance’s vtable. (I don’t know if Swift actually calls this data structure a “vtable”, but you get my meaning.)
* Dynamic. The compiler determines the selector to send to the instance, thereby causing a function to execute.

Swift prefers virtual dispatch. Unless you request dynamic dispatch with `dynamic`, or you’re using members written in Objective-C (which aren’t included in the vtables Swift uses for virtual dispatch), Swift always behaves as if you’re going to get at least virtual dispatch. In some cases it uses static dispatch, but only where the language’s semantics guarantee that virtual dispatch would give the same result. Examples of this include `final` and `static` members (which forbid overriding the members in question, and thus prevent a mismatch between the results of virtual dispatch and static) and value types (which don’t support inheritance, so there’s no way to introduce a mismatch).

Again, the sole exception to this is protocol extensions. Unlike any other construct in the language, protocol extension methods are dispatched statically in a situation where a virtual dispatch would cause different results. No compiler error prevents this mismatch.

More generally, I don't see there being any real difference between

extension Proto {
   func someMethodNotDefinedInTheProto()
}

and saying

func someFuncThatUsesTheProto<T: Proto>(x: T)

except that one is invoked using method syntax and one is invoked using function syntax. Method invocation syntax does not inherently mean "dynamic dispatch" any more than function syntax does.

I’m sorry, I just don’t agree with you on this. In Swift, generics and overloads are statically resolved at compile time. Other than explicit branching constructs like `if` and `switch`, the *only* place in Swift where a dynamic, *runtime* type—as opposed to the static, *compile-time* type—chooses which code to run is in the self position of a member access. Swift may choose to use static dispatch in certain cases, but the language is designed to prevent any difference in semantics based on that decision.

Protocol extensions are the weird outlier here—the only case in which the static and virtual dispatch behaviors are different, and Swift chooses the static dispatch behavior.

···

--
Brent Royal-Gordon
Architechies

Method invocation syntax does not inherently mean "dynamic dispatch" any more than function syntax does.

In languages now in widespread use — Java, Ruby, Javascript, C#, what I know of Python — that is _precisely_ what it means. In all those languages, as far as I know:

all uses of the dot invocation syntax either use dynamic dispatch, or have something on the left whose compile-time and runtime types are identical; and
all instances of dynamic dispatch in the language either use the dot invocation syntax, or have and implicit “self.” / “this.” that uses it.

In short, programmers are likely to bring to Swift the assumption that dot invocation = dynamic dispatch.

Our difference of perspective on this single point [rimshot] is probably the source of our larger disagreement on this entire thread.

Cheers, P

···

On Dec 10, 2015, at 6:31 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

On Thu, Dec 10, 2015, at 02:24 AM, Brent Royal-Gordon wrote:

Swift loves to dispatch things statically and does so wherever possible. In some cases—such as value types not having inheritance—language features are clearly designed the way they are specifically to allow them to be statically dispatched. But Swift never uses static dispatch where dynamic dispatch would have given a different result. This contrasts with, for instance, a non-virtual C++ method’s behavior of “ha ha ha, I’m just going to ignore your override for speed." You could write a Swift compiler which dispatched everything dynamically, and you would never see any difference in semantics.

Yes it does. When message dispatch is done using the obj-c runtime, the compiler is free to omit the message send and use static dispatch if it believes it knows the concrete type of the receiver. This normally behaves the same, because dynamic dispatch with a known receiver type and static dispatch are the same, except if the method is dynamically replaced using the obj-c runtime methods, the static dispatch will not invoke the dynamically overridden method. The most common way you see this is with KVO. If you try and KVO an @objc property on a Swift object, it may work sometimes and may not work at other times, depending on when the compiler believes it can safely use static dispatch. This is why Swift has a whole keyword called `dynamic` whose job is to say "no really, use dynamic dispatch for every single access to this method/property, I don't care that you know for a fact it's a Foo and not a subclass, just trust me on this".

More generally, I don't see there being any real difference between

extension Proto {
   func someMethodNotDefinedInTheProto()
}

and saying

func someFuncThatUsesTheProto<T: Proto>(x: T)

except that one is invoked using method syntax and one is invoked using function syntax. Method invocation syntax does not inherently mean "dynamic dispatch" any more than function syntax does.

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

I know almost nothing about C#, so let's ignore that for a moment.

The other languages you listed don't have some notion that dot-syntax
means dynamic dispatch, instead they're just dynamically-dispatched
languages in general. Java, Ruby, and Python are object-oriented
languages, so all interactions with objects are always dynamic.
JavaScript is a prototype-based language, but the same basic idea
applies. Python has a bunch of global functions that are actually
dynamically dispatched based on the argument runtime type (e.g.
`len(foo)` is actually `foo.__len__()`). JavaScript, Python, and Ruby
are all scripting languages so they don't even know at compile-time what
a function invocation resolves to, all function calls are resolved using
the same scoping rules that affect variables, and in Python and
JavaScript (not sure about Ruby) these functions are actually defined on
an object that's automatically in the scope, e.g. in Python the `len()`
function is actually `__builtins__.len()`, and you can say
`__builtins__.len = None` if you want to break any code that tries to
call the function.

So really there's just two classes of language that you've called out
that are predominately dynamic dispatch: Pure-OOP languages (like Java)
and scripting languages. And that really shouldn't be a surprise that
they're dynamic dispatch. But that's not inherent in the syntax.

Meanwhile, there's languages that use dot-notation for static dispatch.
The first language on my list is Swift, because that's absolutely true
for structs and enums. It's even somewhat true for generics; I say
"somewhat" because the behavior for generics is identical whether it's
virtual dispatch or static dispatch, and in fact it uses virtual
dispatch for the basic version of the function and uses static dispatch
for all specializations, so it's just as valid to say that dispatch on
generics is static as it is to say that it's virtual.

Beyond that, Rust uses static dispatch (with optional virtual dispatch
using trait objects, but most uses of traits—i.e. in generics—is still
static dispatch). C++ uses static dispatch by default and requires the
`virtual` keyword to enable virtual dispatch. I'm sure there's other
examples too, but I don't have a comprehensive knowledge of all
programming languages that use dot-syntax.

We're also only considering single-dispatch at the moment, where the
method is dispatched based on the receiver type. There's languages that
are multiple-dispatch, and even Swift supports this using
function/method overloading. I don't know enough about multiple-dispatch
languages like Dylan and Julia to know if they use static or dynamic
dispatch though.

Overall, my point is that when you say that "programmers are likely to
bring to Swift the assumption that dot invocation = dynamic dispatch",
you're speaking from a very limited point of view. People who only have
experience with Java, Ruby, Python, and JavaScript might think this,
although they also might think that all functions are resolved
dynamically at runtime, or that everything is an object, and those are
certainly not true. People who come from a different background, such as
C++, or Rust, may think that dot invocation == static by default. Or
people might come from a background that doesn't have dot invocation at
all, or maybe they'll come from a mixed background and recognize that
there's a wide variety of dispatching and method resolution strategies
and that each language does things its own way.

-Kevin Ballard

···

On Thu, Dec 10, 2015, at 09:27 PM, Paul Cantrell wrote:

Method invocation syntax does not inherently mean "dynamic dispatch"
any more than function syntax does.

In languages now in widespread use — Java, Ruby, Javascript, C#, what
I know of Python — that is _precisely_ what it means. In all those
languages, as far as I know:

* *all* uses of the dot invocation syntax either use dynamic
   dispatch, or have something on the left whose compile-time and
   runtime types are identical; and
* *all* instances of dynamic dispatch in the language either use the
   dot invocation syntax, or have and implicit “self.” / “this.” that
   uses it.

In short, programmers are likely to bring to Swift the assumption that
dot invocation = dynamic dispatch.

You think that Swift prefers virtual dispatch. I think it prefers static.

I think what's really going on here is that _in most cases_ there's no observable difference between static dispatch and virtual dispatch. If you think of Swift as an OOP language with a powerful value-typed system added on, then you'll probably think Swift prefers virtual dispatch. If you think of Swift as a value-typed language with an OOP layer added, then you'll probably think Swift prefers static dispatch. In reality, Swift is a hybrid language and it uses different types of dispatch in different situations as appropriate. In the OOP subset of the language, Swift is mostly virtual-dispatch, with a bit of dynamic-dispatch thrown in, and some opt-in static dispatch. In the value-typed subset of the language, Swift is static-dispatch.

Protocols are where it gets a little weird, as it's actually a combination of static and virtual dispatch, and runtime type information. When using generics, semantically speaking, the receiver type is statically resolved, and then it will use whatever dispatch the actual method in question uses (e.g. static for structs and values, virtual or dynamic for classes). Technically it does actually use virtual dispatch in the un-specialized case, but then specializes under optimization to match the described semantic behavior, but this is not an observable distinction. When using a protocol as a "protocol object" (i.e. a value typed as the protocol itself) it is of course always virtual dispatch (which may turn into dynamic dispatch for dynamic methods on classes). And the runtime type information part of protocols is the fact that you can always query the runtime type of the contained value, both checking what concrete type a protocol object has, and checking what protocols a value conforms to.

And finally for protocol extensions. These are strictly static typing, all the way. Which makes sense, because there's no virtual function table (or in Swift terms, protocol witness table) to put the methods into. Extensions can provide default implementations of protocol methods because the type that conforms to the protocol puts the extension method implementation into its own protocol witness table (and they only do this if the type doesn't already implement the method itself). Since the protocol witness table only contains things defined in the protocol itself, protocol extension methods that aren't part of the protocol don't get put anywhere. So invoking one of those methods has no possible virtual function table to check, the only thing it can do is statically invoke the method from the extension. And this is why you can't override them (or rather, why your override isn't called if the method resolution is done via the protocol).

The only way to make protocol extension methods work via virtual dispatch is to define a new protocol witness table for the extension itself, but types that were defined without being able to see the extension won't know to create and populate this protocol witness table. So now you need to be able to figure out if a protocol witness table exists at all before you can invoke it, and if it doesn't you have to fall back to static dispatch. Even worse, this means that even if the type already defines an appropriate override, because there's no protocol witness table the override can't get called. And that is a recipe for serious confusion, because you see that a method foo() is defined in an extension for protocol P, and you see that type T conforms to P and implements foo(), but calling foo() via the protocol will never invoke T.foo(). And there's no way for the user to know whether it will or will not work unless they know exactly which module P.foo() was defined in and exactly which module T.foo() was defined in and whether the module that defined T.foo() could see the module that defined P.foo() (and things get more complicated if the conformance of T: P was declared in yet a third module). This problem doesn't happen with protocols today because any type T that conforms to P by definition knows all the methods/properties that P defines and therefore can populate its protocol witness table.

The only other solution that comes to mind is turning all protocol dispatch into dynamic dispatch, which I hope you'll agree is not a good idea.

-Kevin Ballard

···

On Thu, Dec 10, 2015, at 10:35 PM, Brent Royal-Gordon wrote:

>> Swift loves to dispatch things statically and does so wherever possible. In some cases—such as value types not having inheritance—language features are clearly designed the way they are specifically to allow them to be statically dispatched. But Swift never uses static dispatch where dynamic dispatch would have given a different result. This contrasts with, for instance, a non-virtual C++ method’s behavior of “ha ha ha, I’m just going to ignore your override for speed." You could write a Swift compiler which dispatched everything dynamically, and you would never see any difference in semantics.
>
> Yes it does. When message dispatch is done using the obj-c runtime, the compiler is free to omit the message send and use static dispatch if it believes it knows the concrete type of the receiver. This normally behaves the same, because dynamic dispatch with a known receiver type and static dispatch are the same, except if the method is dynamically replaced using the obj-c runtime methods, the static dispatch will not invoke the dynamically overridden method. The most common way you see this is with KVO. If you try and KVO an @objc property on a Swift object, it may work sometimes and may not work at other times, depending on when the compiler believes it can safely use static dispatch. This is why Swift has a whole keyword called `dynamic` whose job is to say "no really, use dynamic dispatch for every single access to this method/property, I don't care that you know for a fact it's a Foo and not a subclass, just trust me on this”.

Okay, time to introduce some precise definitions so we don’t talk past each other.

There are three types of dispatch:

* Static. The compiler determines the exact function to execute.
* Virtual (I was previously calling this “dynamic”). The compiler determines the function to execute’s position in the instance’s vtable. (I don’t know if Swift actually calls this data structure a “vtable”, but you get my meaning.)
* Dynamic. The compiler determines the selector to send to the instance, thereby causing a function to execute.

Swift prefers virtual dispatch. Unless you request dynamic dispatch with `dynamic`, or you’re using members written in Objective-C (which aren’t included in the vtables Swift uses for virtual dispatch), Swift always behaves as if you’re going to get at least virtual dispatch. In some cases it uses static dispatch, but only where the language’s semantics guarantee that virtual dispatch would give the same result. Examples of this include `final` and `static` members (which forbid overriding the members in question, and thus prevent a mismatch between the results of virtual dispatch and static) and value types (which don’t support inheritance, so there’s no way to introduce a mismatch).

Again, the sole exception to this is protocol extensions. Unlike any other construct in the language, protocol extension methods are dispatched statically in a situation where a virtual dispatch would cause different results. No compiler error prevents this mismatch.

> More generally, I don't see there being any real difference between
>
> extension Proto {
> func someMethodNotDefinedInTheProto()
> }
>
> and saying
>
> func someFuncThatUsesTheProto<T: Proto>(x: T)
>
> except that one is invoked using method syntax and one is invoked using function syntax. Method invocation syntax does not inherently mean "dynamic dispatch" any more than function syntax does.

I’m sorry, I just don’t agree with you on this. In Swift, generics and overloads are statically resolved at compile time. Other than explicit branching constructs like `if` and `switch`, the *only* place in Swift where a dynamic, *runtime* type—as opposed to the static, *compile-time* type—chooses which code to run is in the self position of a member access. Swift may choose to use static dispatch in certain cases, but the language is designed to prevent any difference in semantics based on that decision.

Protocol extensions are the weird outlier here—the only case in which the static and virtual dispatch behaviors are different, and Swift chooses the static dispatch behavior.

--
Brent Royal-Gordon
Architechies

2 Likes

(emphasis mine)

I know that this is a bit philosophical, but let me suggest a “next level down” way to look at this. Static and dynamic are *both* great after all, and if you’re looking to type-cast languages, you need to consider them both in light of their semantics, but also factor in their compilation strategy and the programmer model that they all provide. Let me give you some examples, but keep in mind that this is a narrow view and just MHO:

1. C: Static compilation model, static semantics. While it does provide indirect function pointers, C does everything possible to punish their use (ever see the non-typedef'd prototype for signal(3/7)?), and is almost always statically compiled. It provides a very “static centric” programming model. This is great in terms of predictability - it makes it trivial to “predict” what your code will look like at a machine level.

2. Javascript: Completely dynamic compilation model, completely dynamic semantics. No one talks about statically compiling javascript, because the result of doing so would be a really really slow executable. Javascript performance hinges on dynamic profile information to be able to efficiently execute a program. This provides a very “dynamic centric” programming model, with no ability to understand how your code executes at a machine level.

3. C++: C++ is a step up from C in terms of introducing dynamism into the model with virtual functions. Sadly, C++ also provides a hostile model for static optimizability - the existence of placement new prevents a lot of interesting devirtualization opportunities, and generally makes the compiler’s life difficult. OTOH, like C, C++ provides a very predictable model: C++ programmers assume that C constructs are static, but virtual methods will be dynamically dispatched. This is correct because (except for narrow cases) the compiler has to use dynamic dispatch for C++ virtual methods. The good news here is that its dynamism is completely opt in, so C++ preserves all of the predictability, performance, and static-compilability of C while providing a higher level programming model. If virtual methods are ever actually a performance problem, a C++ programmer has ways to deal with that, directly in their code.

4. Java: Java makes nearly "everything" an object (no structs or other non-primitive value types), and all methods default to being “virtual” (in the C++ sense). Java also introduces interfaces, which offer an added dimension on dynamic dispatch. To cope with this, Java assumes a JIT compilation model, which can use dynamic behavior to de-virtualize the (almost always) monomorphic calls into checked direct calls. This works out really well in practice, because JIT compilers are great at telling when a program with apparently very dynamic semantics actually have static semantics in practice (e.g. a dynamic call has a single receiver). OTOH, since the compilation model assumes a JIT, this means that purely “AOT” static compilers (which have no profile information, no knowledge of class loaders, etc) necessarily produce inferior code. It also means that Java doesn’t “scale down” well to small embedded systems that can’t support a JIT, like a bootloader.

5) Objective-C: Objective-C provides a hybrid model which favors predictability due to its static compilation model (similar in some ways to C++). The C-like constructs provide C-like performance, and the “messaging” constructs are never “devirtualized”, so they provide very predictable performance characteristics. Because it is predictable, if the cost of a message send ever becomes an issue in practice, the programmer has many patterns to deal with it (including "imp caching", and also including the ability to define the problem away by rewriting code in terms of C constructs). The end result of this is that programmers write code which use C-level features where performance matters and dynamicism doesn’t, but use ObjC features where dynamicism is important or where performance doesn’t matter.

While it would be possible to implement a JIT compiler for ObjC, I’d expect the wins to be low, because the “hot” code which may be hinging on these dynamic features is likely to already be optimized by hand.

6) GoLang: From this narrow discussion and perspective, Go has a hybrid model that has similar characteristics to Objective-C 2013 (which introduced modules, but didn’t yet have generics). It assumes static compilation and provides a very predictable hybrid programming model. Its func’s are statically dispatched, but its interfaces are dynamically dispatched. It doesn’t provide guaranteed dynamic dispatch (or “classes") like ObjC, but it provides even more dynamic feautres in other areas (e.g. it requires a cycle-collecting garbage collector). Its "interface{}” type is pretty equivalent to “id” (e.g. all uses of it are dynamically dispatched or must be downcasted), and it encourages use of it in the same places that Objective-C does. Go introduces checked downcasts, which introduce some run-time overhead, but also provide safety compared to Objective-C. Go thankfully introduces a replacement for the imperative constructs in C, which defines away a bunch of C problems that Objective-C inherited, and it certainly is prettier!

… I can go on about other languages, but I have probably already gotten myself into enough trouble. :-)

With this as context, lets talk about Swift:

Swift is another case of a hybrid model: its semantics provide predictability between obviously static (structs, enums, and global funcs) and obviously dynamic (classes, protocols, and closures) constructs. A focus of Swift (like Java and Javascript) is to provide an apparently simple programming model. However, Swift also intentionally "cheats" in its global design by mixing in a few tricks to make the dynamic parts of the language optimizable by a static compiler in many common cases, without requiring profiling or other dynamic information.. For example, the Swift compiler can tell if methods in non-public classes are never overridden (and non-public is the default, for a lot of good reasons) - thus treating them as final. This allows eliminating much of the overhead of dynamic dispatch without requiring a JIT. Consider an “app”: because it never needs to have non-public classes, this is incredibly powerful - the design of the swift package manager extends this even further (in principle, not done yet) to external libraries. Further, Swift’s generics provide an a static performance model similar to C++ templates in release builds (though I agree we need to do more to really follow through on this) -- while Swift existentials (values of protocol type) provide a balance by giving a highly dynamic model.

The upshot of this is that Swift isn’t squarely in either of the static or dynamic camps: it aims to provide a very predictable performance model (someone writing a bootloader or firmware can stick to using Swift structs and have a simple guarantee of no dynamic overhead or runtime dependence) while also providing an expressive and clean high level programming model - simplifying learning and the common case where programmers don’t care to count cycles. If anything, I’d say that Swift is an “opportunistic” language, in that it provides a very dynamic “default" programming model, where you don’t have to think about the fact that a static compiler is able to transparently provide great performance - without needing the overhead of a JIT.

Finally, while it is possible that a JIT compiler might be interesting someday in the Swift space, if we do things right, it will never be “worth it” because programmers will have enough ability to reason about performance at their fingertips. This means that there should be no Java or Javascript-magnitude "performance delta" sitting on the table waiting for a JIT to scoop up. We’ll see how it works out long term, but I think we’re doing pretty well so far.

TL;DR: What I’m really getting at is that the old static vs dynamic trope is at the very least only half of the story. You really need to include the compilation model and thus the resultant programmer model into the story, and the programmer model is what really matters, IMHO.

-Chris

···

On Dec 11, 2015, at 8:56 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

You think that Swift prefers virtual dispatch. I think it prefers static.

I think what's really going on here is that _in most cases_ there's no observable difference between static dispatch and virtual dispatch. If you think of Swift as an OOP language with a powerful value-typed system added on, then you'll probably think Swift prefers virtual dispatch. If you think of Swift as a value-typed language with an OOP layer added, then you'll probably think Swift prefers static dispatch. In reality, Swift is a hybrid language and it uses different types of dispatch in different situations as appropriate.

1 Like

Thanks, Chris, for this writeup! It’s full of useful insight about language design, and also a helpful window into the thinking of the core team.

In this writeup, I think we have a way to unify the seemingly competing lines of thought, and have a way to move forward with a common understanding even though we bring different viewpoints.

Quoting some relevant snippers:

Swift isn’t squarely in either of the static or dynamic camps: it aims to provide a very predictable performance model … while also providing an expressive and clean high level programming model. A focus of Swift … is to provide an apparently simple programming model. However, Swift also intentionally "cheats" in its global design by mixing in a few tricks to make the dynamic parts of the language optimizable by a static compiler in many common cases, without requiring profiling or other dynamic information.

I’d say that Swift is an “opportunistic” language, in that it provides a very dynamic “default" programming model, where you don’t have to think about the fact that a static compiler is able to transparently provide great performance - without needing the overhead of a JIT.

You really need to include the compilation model and thus the resultant programmer model into the story, and the programmer model is what really matters, IMHO.

First, two clarification requests for Chris on two things I imagine might lead to confusion on this thread:

When you say “programmer model,” I understand you to mean "how a Swift programmer thinks about the language’s semantics while writing Swift code, without regard to how they’re implemented in the compiler.”

When you say “dynamic,” I take that to mean any kind of dispatch based on runtime type — whether implemented using vtables a la C++, message dispatch a la Objective-C, string-based lookup in a hash a la Javascript, or anything else that uses something’s runtime type to resolve a method call.

Do I understand you correctly?

• • •

On this thread, there are (I think?) two related goals at hand:

Allow dynamic dispatch of protocol extension methods even when the method does not appear in the extended protocol.
Provide a good mental model of the language for programmers, and prevent programmer errors caused by misunderstandings about dispatch rules (if such misunderstandings do indeed exist in the wild).

I’ll copy and paste what Chris wrote into a “Swift philosophy” checklist for Brent’s proposal, and for any others working toward these goals. Chris, please correct me if I’m putting words in your mouth!
Provide a programmer model that:
is high level
is expressive and clean
is dynamic by default
doesn’t require a programmer to think about the fact that a static compiler is able to transparently provide great performance
Provide a performance model that:
is predictable
makes the dynamic parts of the language optimizable by a static compiler in many common cases
does not requiring profiling or other dynamic information
does not require JIT compilation
How do we resolve tension between these goals? The programmer model is what really matters, but we cannot reason about it without considering its impact on the compilation model. We should give the compiler opportunities to “cheat” in its optimization whenever we can do so without undermining the programmer model.

That’s a clear set of priorities. Assuming, that is, that I don’t misunderstand Chris, and that we’re willing to follow his lead!

Cheers,

Paul

–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
https://innig.net@inthehandshttp://siestaframework.com/

···

On Dec 12, 2015, at 1:45 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 11, 2015, at 8:56 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

You think that Swift prefers virtual dispatch. I think it prefers static.

I think what's really going on here is that _in most cases_ there's no observable difference between static dispatch and virtual dispatch. If you think of Swift as an OOP language with a powerful value-typed system added on, then you'll probably think Swift prefers virtual dispatch. If you think of Swift as a value-typed language with an OOP layer added, then you'll probably think Swift prefers static dispatch. In reality, Swift is a hybrid language and it uses different types of dispatch in different situations as appropriate.

(emphasis mine)

I know that this is a bit philosophical, but let me suggest a “next level down” way to look at this. Static and dynamic are *both* great after all, and if you’re looking to type-cast languages, you need to consider them both in light of their semantics, but also factor in their compilation strategy and the programmer model that they all provide. Let me give you some examples, but keep in mind that this is a narrow view and just MHO:

1. C: Static compilation model, static semantics. While it does provide indirect function pointers, C does everything possible to punish their use (ever see the non-typedef'd prototype for signal(3/7)?), and is almost always statically compiled. It provides a very “static centric” programming model. This is great in terms of predictability - it makes it trivial to “predict” what your code will look like at a machine level.

2. Javascript: Completely dynamic compilation model, completely dynamic semantics. No one talks about statically compiling javascript, because the result of doing so would be a really really slow executable. Javascript performance hinges on dynamic profile information to be able to efficiently execute a program. This provides a very “dynamic centric” programming model, with no ability to understand how your code executes at a machine level.

3. C++: C++ is a step up from C in terms of introducing dynamism into the model with virtual functions. Sadly, C++ also provides a hostile model for static optimizability - the existence of placement new prevents a lot of interesting devirtualization opportunities, and generally makes the compiler’s life difficult. OTOH, like C, C++ provides a very predictable model: C++ programmers assume that C constructs are static, but virtual methods will be dynamically dispatched. This is correct because (except for narrow cases) the compiler has to use dynamic dispatch for C++ virtual methods. The good news here is that its dynamism is completely opt in, so C++ preserves all of the predictability, performance, and static-compilability of C while providing a higher level programming model. If virtual methods are ever actually a performance problem, a C++ programmer has ways to deal with that, directly in their code.

4. Java: Java makes nearly "everything" an object (no structs or other non-primitive value types), and all methods default to being “virtual” (in the C++ sense). Java also introduces interfaces, which offer an added dimension on dynamic dispatch. To cope with this, Java assumes a JIT compilation model, which can use dynamic behavior to de-virtualize the (almost always) monomorphic calls into checked direct calls. This works out really well in practice, because JIT compilers are great at telling when a program with apparently very dynamic semantics actually have static semantics in practice (e.g. a dynamic call has a single receiver). OTOH, since the compilation model assumes a JIT, this means that purely “AOT” static compilers (which have no profile information, no knowledge of class loaders, etc) necessarily produce inferior code. It also means that Java doesn’t “scale down” well to small embedded systems that can’t support a JIT, like a bootloader.

5) Objective-C: Objective-C provides a hybrid model which favors predictability due to its static compilation model (similar in some ways to C++). The C-like constructs provide C-like performance, and the “messaging” constructs are never “devirtualized”, so they provide very predictable performance characteristics. Because it is predictable, if the cost of a message send ever becomes an issue in practice, the programmer has many patterns to deal with it (including "imp caching", and also including the ability to define the problem away by rewriting code in terms of C constructs). The end result of this is that programmers write code which use C-level features where performance matters and dynamicism doesn’t, but use ObjC features where dynamicism is important or where performance doesn’t matter.

While it would be possible to implement a JIT compiler for ObjC, I’d expect the wins to be low, because the “hot” code which may be hinging on these dynamic features is likely to already be optimized by hand.

6) GoLang: From this narrow discussion and perspective, Go has a hybrid model that has similar characteristics to Objective-C 2013 (which introduced modules, but didn’t yet have generics). It assumes static compilation and provides a very predictable hybrid programming model. Its func’s are statically dispatched, but its interfaces are dynamically dispatched. It doesn’t provide guaranteed dynamic dispatch (or “classes") like ObjC, but it provides even more dynamic feautres in other areas (e.g. it requires a cycle-collecting garbage collector). Its "interface{}” type is pretty equivalent to “id” (e.g. all uses of it are dynamically dispatched or must be downcasted), and it encourages use of it in the same places that Objective-C does. Go introduces checked downcasts, which introduce some run-time overhead, but also provide safety compared to Objective-C. Go thankfully introduces a replacement for the imperative constructs in C, which defines away a bunch of C problems that Objective-C inherited, and it certainly is prettier!

… I can go on about other languages, but I have probably already gotten myself into enough trouble. :-)

With this as context, lets talk about Swift:

Swift is another case of a hybrid model: its semantics provide predictability between obviously static (structs, enums, and global funcs) and obviously dynamic (classes, protocols, and closures) constructs. A focus of Swift (like Java and Javascript) is to provide an apparently simple programming model. However, Swift also intentionally "cheats" in its global design by mixing in a few tricks to make the dynamic parts of the language optimizable by a static compiler in many common cases, without requiring profiling or other dynamic information.. For example, the Swift compiler can tell if methods in non-public classes are never overridden (and non-public is the default, for a lot of good reasons) - thus treating them as final. This allows eliminating much of the overhead of dynamic dispatch without requiring a JIT. Consider an “app”: because it never needs to have non-public classes, this is incredibly powerful - the design of the swift package manager extends this even further (in principle, not done yet) to external libraries. Further, Swift’s generics provide an a static performance model similar to C++ templates in release builds (though I agree we need to do more to really follow through on this) -- while Swift existentials (values of protocol type) provide a balance by giving a highly dynamic model.

The upshot of this is that Swift isn’t squarely in either of the static or dynamic camps: it aims to provide a very predictable performance model (someone writing a bootloader or firmware can stick to using Swift structs and have a simple guarantee of no dynamic overhead or runtime dependence) while also providing an expressive and clean high level programming model - simplifying learning and the common case where programmers don’t care to count cycles. If anything, I’d say that Swift is an “opportunistic” language, in that it provides a very dynamic “default" programming model, where you don’t have to think about the fact that a static compiler is able to transparently provide great performance - without needing the overhead of a JIT.

Finally, while it is possible that a JIT compiler might be interesting someday in the Swift space, if we do things right, it will never be “worth it” because programmers will have enough ability to reason about performance at their fingertips. This means that there should be no Java or Javascript-magnitude "performance delta" sitting on the table waiting for a JIT to scoop up. We’ll see how it works out long term, but I think we’re doing pretty well so far.

TL;DR: What I’m really getting at is that the old static vs dynamic trope is at the very least only half of the story. You really need to include the compilation model and thus the resultant programmer model into the story, and the programmer model is what really matters, IMHO.

-Chris

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

3. C++: C++ is a step up from C in terms of introducing dynamism into the model with virtual functions. Sadly, C++ also provides a hostile model for static optimizability - the existence of placement new prevents a lot of interesting devirtualization opportunities, and generally makes the compiler’s life difficult.

Interesting, I haven’t heard that placement new is problematic before. What are the problems involved (feel free to reply off-list). Is it legal to use placement new to replace an existing instance and change its vtable pointer or do aliasing rules prohibit that?

Swift is another case of a hybrid model: its semantics provide predictability between obviously static (structs, enums, and global funcs) and obviously dynamic (classes, protocols, and closures) constructs.

Yeah. It’s also pretty cool how a consequence of the runtime generics model is that all value types can be manipulated reflectively, without penalizing static code. I think the key concept here is separating in-memory data from metadata needed for runtime manipulation (value witness tables, protocol conformances etc). I hope more languages design around this in the future.

A focus of Swift (like Java and Javascript) is to provide an apparently simple programming model. However, Swift also intentionally "cheats" in its global design by mixing in a few tricks to make the dynamic parts of the language optimizable by a static compiler in many common cases, without requiring profiling or other dynamic information.. For example, the Swift compiler can tell if methods in non-public classes are never overridden (and non-public is the default, for a lot of good reasons) - thus treating them as final. This allows eliminating much of the overhead of dynamic dispatch without requiring a JIT. Consider an “app”: because it never needs to have non-public classes, this is incredibly powerful - the design of the swift package manager extends this even further (in principle, not done yet) to external libraries. Further, Swift’s generics provide an a static performance model similar to C++ templates in release builds (though I agree we need to do more to really follow through on this) -- while Swift existentials (values of protocol type) provide a balance by giving a highly dynamic model.

The upshot of this is that Swift isn’t squarely in either of the static or dynamic camps: it aims to provide a very predictable performance model (someone writing a bootloader or firmware can stick to using Swift structs and have a simple guarantee of no dynamic overhead or runtime dependence)

To be fair, we still emit implicit heap allocations for value types whose size isn’t known. So your boot loader will have to avoid generics (and non-@noescape closures), at least :-)

Actually, for code within a single module, are we always able to fully instantiate all generics?

Finally, while it is possible that a JIT compiler might be interesting someday in the Swift space, if we do things right, it will never be “worth it” because programmers will have enough ability to reason about performance at their fingertips. This means that there should be no Java or Javascript-magnitude "performance delta" sitting on the table waiting for a JIT to scoop up. We’ll see how it works out long term, but I think we’re doing pretty well so far.

JITs can teach us a lot about optimizing for compile time though, which would help REPL usage, tooling and scripting.

Slava

···

On Dec 11, 2015, at 11:45 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote: