I updated the reply, might wanna take a look.
This is obviously a diamond (collision) so you would have to manually choose whose implementation you want to use in the current module.
If you would call it from the application module, it would use the witness from that context, if not specified otherwise.
I see you choose 2:
which, I'd deem it a bit drifted away from simplicity that you seem to allude to in the beginning. Nonetheless, that style of resolution does gain some support in the link I posted above. Not to say that it's easy to implement, though.
To clarify:
// Module A
import Mixin
protocol A: Mixin {...}
extension A { /* Implements Mixin */ }
func take(a: A) {...}
struct Concrete: A {...}
// Application
import A
protocol B: Mixin {...}
extension B { /* Implements Mixin */ }
extension Concrete: B {...}
//that uses Application.Mixin implementation,
//because it is the most recent refinement
take (a: Concrete()) //uses implementation of Mixin from the current module
//because it has been explicitly redeclared
Couldn't you refine to all types conforming to CustomStringConvertible
or CustomDebugStringConvertible
or TextOutputStream
? There are plenty of existing protocols which might give you the functionality you need and most o f the built-in types conform to them.
This is a basic hack. It doesn't solve the real problem which was admitted by many people on this site. Despite that, I want all other types to opt to conform, while, as I already wrote, the extension of CustomStringConvertable and such provides default implementation to only limited set of types.
Alright, I see what you mean. I was going to suggest "\(anything)"
as your final solution.
The problem is that in my example, there's no one place that's at fault. This code is obviously valid:
protocol A {}
protocol B {}
struct Concrete: A, B {}
And we want this code to also be valid:
protocol Mixin {
func fin() -> Int
}
extension A: Mixin {
func fin() -> Int { return 1 }
}
extension B: Mixin {
func fin() -> Int { return 2 }
}
Neither of them has done anything wrong. What about this code?
func useMixin<Value: A>(_ value: Value) {
value.fin()
}
That should be okay. And this?
useMixin(Concrete())
If we don't look at the body of useMixin
, this should be okay too. That means there's nowhere to emit the error about the collision.
There's a premise in here that I haven't stated, which is that supporting separate compilation is a design goal for Swift. Unlike several other modern compiled languages (mainly thinking of Rust), the Swift compiler cannot see the entire program at compile time. There are a few reasons for this, but most of them have to do with how Apple ships its OS: using dynamically-linked closed-source libraries that preserve binary compatibility across versions. So there will always be libraries where all the compiler can see is the interface.
When languages with separate compilation want to do cross-module analysis, they typically use some kind of runtime support for this, possibly even a JIT. While Swift doesn't currently have any sort of JIT, it does have plenty of runtime functions that set up various bits of metadata on first use. So it's totally fine to suggest features that require runtime support, though if it needs a full-on JIT the current runtime isn't set up for that.
Uhmm, what do you mean by that? I don't see any reason why compiler wouldn't be able to emit an error about a collision. Because see I think about it in terms of a scope that is some module, and that module should be the single point of witness resolution. See:
//module A
protocol A {}
protocol B {}
struct Concrete: A, B {}
//module B - imagine that it is a current root of compilation
protocol Mixin {
func fin() -> Int
}
extension A: Mixin {
func fin() -> Int { return 1 }
}
extension B: Mixin {
func fin() -> Int { return 2 }
}
func useMixin<Value: A>(_ value: Value) {
value.fin()
}
useMixin(Concrete())
//aha! usage of type with vague witness found.
//compiler now can emit error about that
//in current module the witness for fin() of Concrete
//is unclear, so you should resolve manually
extension Concrete: Mixin { func fin() -> Int { return Self.A.fin() } }
//ok in current module Concrete is explicitly declared to
//have witness from Concrete: Mixin.
Regarding dynamic linking, I don't see that it is related to this because using a dynamic library is basically putting everything through a layer of indirection. Also, every library has to be compiled at some point (right?) and it should be totally sane to construct a separate witness record for each context (module) - from the example above: module A gets its own witness record, module B gets its own (witness data can be even shared between them).
//module A
protocol A {}
protocol B {}
struct Concrete: A, B {}
//witness record of Concrete is unique to current context (module A)
//module B
protocol Mixin {
func fin() -> Int
}
extension A: Mixin {
func fin() -> Int { return 1 }
}
extension B: Mixin {
func fin() -> Int { return 2 }
}
extension Concrete: Mixin { func fin() -> Int {140} }
//this Concrete gets new/modified witness record
//that is unique to module B
So for example when you need to import a dynlib, the code that uses objects from module B is routed to witnesses records from that context, and other code that uses objects from module A gets routed to that module's witness records. There is no problem here.
That is perfectly fine and not a problem: if library has internal witness (private extension and likes), it should never conflict with code from other module/file
//module S
public struct Zeta {}
internal extension Zeta: Equatable { ... }
//module T
import S
internal extension Zeta: Equatable {}
//this is other module, a different witness table here!
That is disappointing! Does it imply that swift has stuck forever with crappy dispatch semantic?
A lot of this has already been heavily discussed in the thread I mentioned:
I would suggest that you read through it. I know it's long and arduous, but I personally find the thread very entertaining to say the least.
FWIW the way the discussion has unfolded seem to be similar to my older question when I asked for Swift's equivalent of Scala's implicits.
Yep, these are related things. Unfortunately, it seems to me that to allow this, the dispatching problems have to be solved first.
Given that Objective-C style dispatching is unacceptable, I hope JIT in Swift's runtime is the next big thing.
This is a valid question even if asked in a provactive way. I'd answer it with a few layers:
-
Protocol-based dispatch may not mean dynamic dispatch in the final binary, even with dynamic libraries involved. However, this is considered an optimization in Swift: if the compiler can "devirtualize" a call through a protocol, it is permitted to. (That can happen even for libraries compiled with library evolution enabled, though I won't get into details of when that can and can't happen.) The catch is that as an optimization, this has to not change which implementation gets called.
-
Binary compatibility does limit what changes can be made, but it doesn't mean no changes can be made—it just means the old stuff has to keep working. That means new features can be added as new entry point symbols in a library, or by rewriting them in terms of the old features if possible, or by storing extra data alongside the existing data. It might mean that the new features are only available when running on newer Apple OSs. (Non-Apple OSs don't have ABI stability at this time.)
-
Source compatibility may actually be trickier here: when recompiling a program changes its behavior, that's concerning. There are discussions elsewhere of when we can introduce such breaking changes, and usually we'll have to keep the old behavior around as a language mode. So yes, it may indeed be hard to make improvements here.
Separately, I wanted to clarify my example, and in doing so I realized that I hadn't quite gotten it right. Here it is again with proper imports:
// Module BaseProtocols
public protocol A {}
public protocol B {}
// Module ConcreteStruct
import BaseProtocols
public struct Concrete: A, B {
public init() {}
}
// Module MixinProtocol
import BaseProtocols
public protocol Mixin {
func fin() -> Int
}
extension A: Mixin {
public func fin() -> Int { return 1 }
}
extension B: Mixin {
public func fin() -> Int { return 2 }
}
// Module MixinUser
import MixinProtocol
public func doSomething<Value: A>(_ value: Value) {
value.fin() // from Mixin
}
// App
import MixinUser
import ConcreteStruct
doSomething(Concrete())
In this example, Concrete and Mixin aren't brought together until you get to the app, but the app doesn't know that doSomething(_:)
is using Mixin. You could resolve this by saying that it'll use the default conformance A: Mixin
, but…then it won't be compatible with situations where Concrete ends up using B: Mixin
.
Protocols can refine other protocols, but they don't inherit from other protocols.
This is false. Protocol inheritance is pervasive throughout the standard library.
You cannot extend a protocol to inherit from another protocol.
Just take the protocol hierarchy for number types as an example.
The same thing applies to sequences.
Whether or not protocol hierarchies count as "inheritance" is something different people have different opinions on. They do introduce a subtyping relationship and they do allow you to use operations defined on the base, so I personally would say "yes, it counts", but you can state the same relationship as a constraint rather than using inheritance syntax, and that's closer to how they behave at both compile and run time. (Requirements on the parent protocol aren't copied into the child protocol, for example.)
protocol Collection where Self: Sequence { … }

This is a valid question even if asked in a provactive way.
Sorry, didn't mean any harm, just used my charm .

In this example, Concrete and Mixin aren't brought together until you get to the app, but the app doesn't know that
doSomething(_:)
is using Mixin.
Why it wouldn't know? The imports are there. Is it a current implementation limitation? If so, than it is to be fixed.

You could resolve this by saying that it'll use the default conformance
A: Mixin
That defaults might be set per unique module per unique member of the type, I wish. (and per unique scope/namespace for this matter)

but…then it won't be compatible with situations where Concrete ends up using
B: Mixin
What situations? In other modules, programmers may set different defaults (not necessarily for the whole protocol, but for its members).
Probably, to not spawn a crazy amount of modules if such a case has arisen, it is better to use namespacing, so a programmer can choose required conformance within a certain scope within a single module.
In your example, though, I don't see any problem. My intuition says that in App
doSomething(_:)
would use the implementation of A: Mixin
from MixinProtocol
since it sees the argument as type A
and thus it returns 1
. The problem would be if it would see it as a Mixin
type (because there are multiple defaults for Concrete
, the compiler wouldn't know which to pick. And in such a case, the manual resolution (for the current module, but not restricted to it) would be required).
Oh, where did you get these pretty hierarchies?

Why it wouldn't know?
Compiler can probably know alright, but why would the programmer know?
Using a function doSomething<Value: A>(_: Value)
, requires me to resolve clashing Mixin
conformance?! I'd just call bogus and file a bug report without thinking too much about it.

Using a function
doSomething<Value: A>(_: Value)
, requires me to resolve clashingMixin
conformance?!
It doesn't (shouldn't). App
doSomething(_:)
would use the implementation of A: Mixin
from MixinProtocol
since it sees the argument as type A
and thus it returns 1
, means that you don't have to type a lot in this case. But in general, you might be, if some library has been structured badly (has a lot of unnecessary public exposure or such), although these cases are unlikely, I believe.

I'd just call bogus and file a bug report without thinking too much about it.
This resolution should be transparent obviously. Probably some sort of report (that can be enabled with flag) about compilation should be provided by the compiler about what it has chosen and what not.