What an object is, which is roughly equivalent to its observed behavior, should never depend on how it is declared.
let greeter = LazyGreeter()
let greeter1: Greeter = greeter
print(greeter)
print(greeter1)
greeter.greet()
greeter1.greet()
UPDATE: just in case it's not clear, this prints the following: greeter.LazyGreeter
greeter.LazyGreeter
sup
Hello, World!
So the same object responds differently to the same message, depending on how it is declared. Yikes!This is a fundamental structural issue with the language, philosophically, and will definitely cost programmer productivity. I'd argue this isn't a problem so much as behaves correctly due to insufficient planning.
It's not that they're taking a position on dynamic dispatch, it's that they're trying to take both positions, likely in support of Objective-C compatibility.
IMO dynamic dispatch has caused way more programmer productivity issues than anything else - you write code the compiler can't validate because you explicitly chose not to give it the information it needs to do it's job. It can't tell you your code is right. Nothing really can. The new world is a better place, Go and Rust have it right, inheritance is dead.
It would be slightly less infuriating had that chase actually delivered, but it dramatically has not.
So why should behavior not depend on the (static) type of the term?
That's a very standard use of static typing. It seems like you're coming from a dynamic background.
There really isn't any such principle or rule here, only it seems, unfamiliarity with type systems.
This is actually a statement that strong typing should not exist. A "declaration" (really a type declaration) indicates the type, and if observed behavior cannot depend on the types there is not much point to types. Or stated positively: in a strongly typed language, the types are part of 'what an object is'.
Or, stated by example:
let a: Int = 3
let b: Float = 3
print(a/2) //prints 1
print(b/2) //prints 1.5
Both a and b are "the same" value, insofar as they are `==` and they model the same underlying mathematical concept. But behavior varies because they differ in type.OP's snippet actually works quite a lot differently than the way you may expect; LazyGreeter.greet() and Greeter.greet() are two completely independent functions that share the same name. Name resolution is a complex topic in any language, but in Swift if you wanted to override another function you would say `override`, in which case the compiler will complain there is nothing you can override in `BaseGreeter` at which point you will understand the whole mistake.
There is actually no way to "pick" LazyGreeter's implementation of the unrelated function (as perhaps you expect). The only function we can call on a Greeter is Greeter.greet (or its overloads, and there are none). So if Greeter.greet did not exist, we would not get LazyGreeter.greet() but rather a type error.
I do think a case can be made that we need a similar `implements` keyword like `override` to check that we are implementing a protocol requirement.
Nope. It is a statement that objects shouldn't be different depending on how you look at them.
The example is a class, so a reference type. That means that greeter and greeter1 are just two references to the exact same underlying object.
Your example are two distinct value type instances that you happened to initialize from the same literal.
So not even close to comparable situations. Speaking of comparable:
> Both a and b are "the same" value, insofar as they are `==`
Also nope.
let a: Int = 3
let b: Float = 3
let r=a==b
print(r)
Let's compile this: swiftc numbers.swift
numbers.swift:4:8: error: binary operator '==' cannot be applied to operands of type 'Int' and 'Float'
let r=a==b
~^ ~
numbers.swift:4:8: note: expected an argument list of type '(Int, Int)'
let r=a==b
^> strong typing
Strong typing is not the same as static typing. `let greeter: Greeter = LazyGreeter()` is a strongly-typed `LazyGreeter`: you cannot use it where a value of another incompatible type is required, nor can you change its runtime type. But its static type is `Greeter`. Swift uses the latter for method resolution, but there's nothing inevitable or inherent about that.
`NSArray * array = [NSArray new];` is statically typed as an `NSArray`, but its dynamic (and strong) type is `__NSArray0`, and that's where the value's implementations come from.
> they model the same underlying mathematical concept
They don't: one models an integral and the other models(/approximates) a real. Which is why, as another commentor already pointed out, they're not `==`.
a: Int would be 0x0000000000000003.
b: Float would be 0x40400000.
While both are specified literally as "3" the compiler de-sugars that into the values above, and further, the type system takes that knowledge into consideration to prevent you from doing things that don't make sense like trying to equate "a" and "b" without explicitly performing a lossy conversion (Binary operator '==' cannot be applied to operands of type 'Float' and 'Int').
I don't believe this to be a name resolution issue, but rather a protocol conformance creates a vtable for the protocol but beyond that dynamic dispatch doesn't exist so it's not possible to resolve the actual method overridden in the subclass.
This isn't a statement on typing (other than your example actually making a solid case for strong typing); rather the boundary between static and dynamic dispatch is not well-constructed in the case of protocol conformances.
Prelude> a = 1
Prelude> a/2
0.5
Prelude> :t a/2
a/2 :: Fractional a => a
Prelude> b = 1 :: Int
Prelude> b/2
<interactive>:6:1: error:
• No instance for (Fractional Int) arising from a use of ‘/’
• In the expression: b / 2
In an equation for ‘it’: it = b / 2
Prelude> b `div` 2
0
Prelude> :t div
div :: Integral a => a -> a -> aIs this similar to the C# construct....
interface IGreeter { void Greet()}
class BaseGreeter :IGreeter {
public void Greet {Console.WriteLine(“Hello World”)}
public void IGreeter:Greet {Console.Writeline(“sup”)} }
}
Greeter greet1 = new Greeter();
IGreeter greet2 = greet1();
Console.Writeline(greet1 == greet2) greet1.Greet() greet2.Greet()
Would print I believe.
true
Hello World
sup
e.g. in Kubernetes you can give a Pod, Deployment, Service, Ingress, Volume, etc. etc. the exact same name and in fact many examples encourage that (because they are namespaced on the type). However this is practically just an artefact of a previous choice and when you're looking at your YAML files you may not immediately know that distinction unless you have some familiarity with the tech.
What it says is that you can call everything the exact same thing. And it will work, and it will seem nice or elegant.
So it is with this Swift example and having a protocol, an extension, and a base class all declaring the exact same thing. Even if the compiler figures it out, how would you without reading the spec or knowing where those files live (because in the real world they would be spread across the filesystem)? It's just poor consideration for comprehension.
Doesn't this situation deserve similar compiler scrutiny?
We don't see many context-sensitive grammars for programming languages, not because very difficult to implement, but I suspect because they're hard for humans to understand.
There is something to be said for the simplicity of naming things to describe what they are (e.g. Hungarian notation), but I really think it's a mistake for languages (configuration, programming, etc.) to even allow identifiers to be reused for differing types.
I'm not sure what you mean. The only thing that's named the same is 'greet', and of course that has to be the same because it's the thing you're implementing.
Edit: Perhaps the protocol and extension could have different names? I don't see how that would make the behavior clearer.
To be fair, I was primed to really think about the code execution given he'd indicated there was a trap in there somewhere, so it certainly would not have been immediately obvious on first glance. But I think, generally developers understand that <code>override</code> is necessary to override code from a parent class.
Typically XCode helps out with this sort of thing – adding <code>override</code> where it seems like it should be added – but perhaps not when a parent class is using a default protocol implementation.
But again, in this scenario where a class is inheriting from another class, it's typically known you need to override the function in order to use your own implementation, so this does seem a bit contrived.
That actually doesn't work because the greet function is implemented on an extension. You can only override class functions.
IF the inferred type of a variable is the protocol:
AND the method is defined in the original protocol
THEN the runtime type’s implementation is called, irrespective of whether there is a default implementation in the extension.
AND the method is not defined in the original protocol,
THEN the default implementation is called.
ELSE IF the inferred type of the variable is the type
THEN the type’s implementation is called.It's weird how people preach over and over how arcane and cumbersome old languages like C(++) are, with the neckbeards defending it with "well you just have to adhere to this list of best practices and it's absolutely fine", and then when some new and hip language comes along and offers just as many traps, the same people giving the "old languages" crap suddenly go all "well it's bad design to do that! Just adhere to this list of best practices here..."
I learned not to say anything as apparently every software engineer "generation" needs to follow something "hip" when very young. Some will grow to see the cycle as it repeats itself in front of them, others will die believing "my programming language was the best, you just had to stick to this list of best practices".
Also, "bad design" quite often means "not the way I'm used to and feel comfortable with".
Correct; it's the same thing in essence.
But in this case, OOP principles aren't being followed—the subclass is attempting to override stuff from the protocol, which the superclass didn't override. Essentially, the superclass made an assertion, by not overriding that method, that it wanted the default behavior from the protocol extension. The subclass, by overriding that behavior, is breaking the contract that the superclass declares.
Would I have caught the bug? Probably not. But it looks like bad form to me to provide that default implementation to Greeter, so please rewrite your code.
This bug is now another good reason not to use default implementations on interfaces.
My question to swifters: what if LazyGreeter had another method, say lazyGreet, and you typed it as Greeter? Then you called let greeter : Greeter = LazyGreeter(); greeter.lazyGreet();
Would you expect this to work or fail?
I'd expect it to fail.
In that sense, I assume its simply that extension methods override child overrides. And its just something you need to know. That can even have interesting utility in some scenarios.
I concur that the behaviour is a design decision, not a bug, but I think most programmers would in general prefer design decisions to bring the language closer to python.
There's been some proposals around fixing these, one that comes to mind is: https://forums.swift.org/t/introducing-role-keywords-to-redu...
public interface IGreeter {
}
public static class GreeterExtensions {
public void Greet(this IGreeter greeter) {
// If you have CA turned on,
// you'll get a warning for not
// using the parameter "greeter"
Console.WriteLine("Hello, World!");
}
}
public class BaseGreeter : IGreeter {
public abstract void Greet();
}
public class LazyGreeter : BaseGreeter {
public override void Greet() {
Console.WriteLine("sup");
}
}
IGreeter greeter = new LazyGreeter();
// This has to be GreeterExtensions.Greet(greeter),
// because IGreeter is an empty interface
greeter.Greet();
I think the syntax in C# makes it clearer that you're doing something funky mixing inheritance with extension methods. If you wanted to do it the "correct" way, I think it would look something like: public interface IGreeter {
void Greet();
}
public class BaseGreeter : IGreeter {
// Edit: abstract method wouldn't be equivalent to example
// public abstract void Greet();
public virtual void Greet() {
Console.WriteLine("Hello, World!");
}
}
public class LazyGreeter : BaseGreeter {
public override void Greet() {
Console.WriteLine("sup");
}
}
IGreeter greeter = new LazyGreeter();
greeter.Greet();
Which is more complicated than the idiomatic way of providing default behavior with inheritance: public class Greeter {
public virtual void Greet() {
Console.WriteLine("Hello, World!");
}
}
public class LazyGreeter : Greeter {
public override void Greet() {
Console.WriteLine("sup");
}
}
Greeter greeter = new LazyGreeter();
greeter.Greet();
I'm not a huge fan of the Swift syntax; specifically, IMO, the declaration `class BaseGreeter: Greeter {}` hides where the implementation of `greet` is coming from. Without the method declaration it looks like it's being inherited from the protocol extension, when it's really not.IE:
#include <iostream>
using namespace std;
struct Greeter {
void greet() {
cout << "Hello, World!" << endl;
}
};
struct BaseGreeter: Greeter {};
struct LazyGreeter: BaseGreeter {
void greet() {
cout << "sup" << endl;
}
};
int main() {
Greeter greeter = LazyGreeter();
greeter.greet(); // Prints "Hello, World"
return 0;
} struct Greeter {
virtual void greet() {
cout << "Hello, world!" << endl;
}
};
struct BaseGreeter: Greeter {};
struct LazyGreeter: BaseGreeter {
void greet() override {
cout << "sup" << endl;
}
};
int main() {
Greeter *greeter = new LazyGreeter();
greeter->greet(); // In C++ prints "sup", but in Swift "Hello, World!"
delete greeter;
return 0;
}1. Adding a default implementation for a protocol requirement shouldn't stop existing types that correctly conform to that protocol from compiling. This is a reasonable user expectation and makes protocol composition a more powerful feature.
2. "Retroactive conformance" should be possible: someone should be able to conform a type they didn't write to a protocol of their own creation. It's a nifty feature that makes Swift extensions very powerful but also sometimes difficult to reason about.
3. Classes should inherit protocol conformances from their superclasses. As some of the Swift core team members have explained, this is not something that absolutely had to be the case, but it seemed sensible initially.
Points (1) and (2) preclude the language from requiring the `override` keyword for the subclass `LazyGreeter` to override a default implementation of a protocol requirement not itself overridden in the superclass `BaseGreeter`, since that would mean that (1) and/or (2) would become impossible. Getting rid of (3) would remove the surprising behavior but it is rather late in the evolution of the language to make such a large change.
The way to reason about this behavior is to consider what are intended "customization points" in the language. It isn't really covered in The Swift Programming Language, but (although not intuitive) it's a fairly teachable concept:
A separate but similar issue exists with the distinction between default implementations of protocol requirements (methods declared in both a protocol and a protocol extension) versus extension methods (methods not declared in a protocol but found in a protocol extension). The former is dynamically dispatched and is considered a customization point for conforming types, whereas the latter can only be shadowed by conforming types but is not a customization point. As described here: https://nomothetis.svbtle.com/the-ghost-of-swift-bugs-future
(The Swift standard library actually uses this concept to its advantage. For instance, the inequality operator != is not a customization point for the Equatable protocol and is implemented in an extension method, guaranteeing that !(a == b) is always equivalent to (a != b) in the generic context.)
When implementing subclasses of a third-party open class, only open methods are customization points. In that case, Swift provides compile-time checking to prohibit overriding non-open public methods. This is possible because Swift does not support "retroactive inheritance" from a superclass as it does retroactive conformance to a protocol. Also, the language allows later versions of a third-party open class to stop your existing subclasses from compiling because of a conflict between a later-added superclass method and a pre-existing method in your subclass that now lacks the `override` keyword. In other words, the compiler-enforced prohibition is possible because points (1) and (2) above were not design goals for subclassing as they were for protocol conformance.
In the case illustrated by the present article, where protocols are used in class hierarchies, a third notion of customization points arises. As demonstrated here, a protocol requirement not overridden by a base class is not a customization point for a subclass. The protocol requirement can be shadowed by the subclass but not overridden. Consistent with the design goals enumerated above, this allows for the vendor of a library protocol to provide additional default implementations at a later point without breaking user code. (But if the vendor of a conforming superclass then implements an overriding implementation, your subclass would cease to compile just as it would for any other conflict between superclass methods and subclass methods.)
I would disagree with that; adding that default implementation is not a simply internal change. It flat-out changes compilibility; it makes invalid code into valid code:
protocol P {
func f()
}
class C : P {} // Error!
--- protocol P {
func f()
}
extension P {
func f() {}
}
class C : P {} // Okay
Given the trap demonstrated here, the converse should hold as well. Doubly so in light of the ways the compiler (mostly helpfully) enforces various aspects of subclassing, as you pointed out.In many ways, protocols (right now at least) feel like Swift having its cake but not eating it: strictness floats around them in ways that do not help developers (ugh, PATs!) but is absent where it would:
// This all compiles without any warnings?!
protocol P {
var i: Int { get set }
}
protocol Q : P {
var i: Int { get }
}
class C : Q, P {
var i = 10
}
// Now make a protocol that inherits from another,
// where both are class/AnyObject constrained
//---
struct S : Hashable { // Swift 4.1, synthesized
let s: String
}
extension S : Equatable {
static func == (lhs: S, rhs: S) -> Bool {
// Hashable semantics, smashable semantics. Hold my beer.
return lhs.s.first == rhs.s.first
}
}I’ve run into the author’s problem a few times, and I’d like to think that this is just a bug or behaviour detail that a future Swift version could correct. Swift hasn’t quite matured fully yet, and this is a great example of that (these examples are becoming fewer every year though).
This bug bit me one time back before remote debugging came out when I was developing an app for a device that plugged into the lightning port. The only way to debug was to send log messages over the network to a log server running on my laptop, and the connected device sent messages to satellites where there was often a 10-minute gap in connectivity. And I couldn’t reproduce it in tests. That took some time to diagnose!
I like to bring this issue up if an interview candidate seems extremely bought in to protocol-driven development. I wouldn’t be surprised if any level of dev wasn’t aware of it, but would be impressed if anybody identified it.
I can understand the advantage of using static dispatch, i.e. Greeter#greet(object), but I assume there is some mechanism that avoids calling Greeter#greet if BaseGreeter would implement greet and call BaseGreeter#greet(object) instead.
Why does extending a class which extends a protocol not make the extending class implement that protocol in Swift?
But if that's the only reason it catches the problem, I think it could have room for improvement. For example in one place the error could exist, but at a different place, the real function could be called because the variable is declared as a LazyGreeter. Then Periphery wouldn't catch the problem.
One way to fully catch it would be to give a warning if a base class implements a protocol but a derived class doesn't. I'm not sure how many false positives this would have. Possibly you might only do this if the derived class tries to override a function from the base class's protocol.
It's just a matter of global scoping and precedence. But yeah, sharing notation between inheritance and extension is a tad bit reductionist.