Swift - Protocol default implementation in extension with generic superclass constraint - swift

I am trying to constraint a protocol extension to a generic class. My goal is to provide a default implementation of a protocol where Self is any instance that is subclass of some generic class. Consider the below example:
protocol Printable {
var value: String { get }
}
class Printer<P: Printable> {
let printable: P
init(printable: P) {
self.printable = printable
}
}
protocol Press {
func print()
}
// error: reference to generic type 'Printer' requires arguments in <...>
extension Press where Self: Printer {
func print() {
// Do Something with self.printable.value
}
}
The compiler gives error error: reference to generic type 'Printer' requires arguments in <...>.
I don't understand why this should not be allowed. As long as Press is some kind of Printer which always works with some kind of Printable things should work, right? Or I am missing something?
Can you point what could be the right way of achieving something like this?

This is because Printer<A> and Printer<B> are different types, even A & B are Printable, so due to possible ambiguity compiler generate error.
You need the following (tested with Xcode 11.4)
extension Press {
func print<P>() where Self: Printer<P>, P: Printable {
// Do Something with self.printable.value
}
}

Related

(Swift) extension of S<S<P>> where S is a generic structure and P is a type-constrained protocol

Given these types:
struct S<T> {...}
protocol P { associatedtype A ... }
I want something like this:
extension S<S<T>> where T:P { ... }
where the first S has the type parameter of another S and the second has the type parameter of some P
How can I do it?
Swift doesn't support this syntax currently (see SR-3716). You need to attach the constraint to each extension method directly:
extension S {
func f<U>() where T == S<U>, U: P {}
}
With that in place, you'll be able to call f() for some values, but not others:
extension String: P {
public typealias A = Int
}
S<Int>().f() // Fails
S<S<Int>>().f() // Fails
S<String>().f() // Fails
S<S<String>>().f() // Succeed
Unfortunately, autocomplete will still offer f() in places it's not allowed, and the diagnostic is not ideal:
Generic parameter 'U' could not be inferred
Instance method 'f()' requires the types 'Int' and 'S<U>' be equivalent
I believe there is work going on to improve the developer experience around this, but the compiler will enforce it.

Workaround for conditional conformance in Swift (was: Adding protocol to a constrained generic types)

The code snippet below demonstrates the problem I am trying to solve.
import Foundation
protocol Printable {
func className() -> String
}
class SomeType: Printable {
func className() -> String {
return "SomeType"
}
}
class List<T> {
}
extension List where T: SomeType {
func className() -> String {
return "List<SomeType>"
}
}
func test(type: Any, message: String) {
guard type is Printable else {
print("\(message): ERROR")
return
}
print("\(message): SUCCESS")
}
let s: Any = SomeType()
test(type: s, message: "#1")
let slist1: Any = List<Any>()
test(type: slist1, message: "#2")
let slist2: Any = List<SomeType>()
test(type: slist2, message: "#3")
How can I get this:
> #1: SUCCESS <--- as expected
> #2: ERROR <--- it's okay
> #3: SUCCESS <--- I am getting ERROR instead
It seems that adding a protocol to this line would do the trick:
extension List: Printable where T: SomeType { // COMPILE ERROR
But unfortunately, this is not allowed.
Another way to do it could be to use:
extension List where T: Printable { // COMPILES OK in Swift 2.3 but doesn't work. COMPILE ERROR in Swift 3.0
But again, no luck passing the test.
What else can I do to add a protocol to a constrained generic type?
An updated answer now that we have Swift 4.2, conditional conformance is now added as a feature.
From their spec on Github, this sort of code is now valid
extension List : Printable where T: SomeType {
func className() -> String {
return "List<SomeType>"
}
}
Okay so in your guard you're asking "if this is Printable then print success else print Error" and with your first example you have s which is SomeType which is printable. That's fine.
After that you have slist1 which is type List<Any> which is definitely not of type printable and you get "Error". That's fine
Next you have List<SomeType>. Now you have a class extension that defines T to be SomeType, correct? But you're only defining T to be SomeType and not the actual List so when you pass the entire List into the test function you're not going to get your test to pass because List<AnyTypeHere> is not Printable because the list itself doesn't implement Printable.
Now the question is, do you want the entire list to be printable? If so, then just make it conform to the SomeType or Printable protocol. That's the only way you'll get that to pass other than you passing individual List<SomeType> elements into the function. Your function logic is correct but it's just a misuse of the concept.
So if you want the List<SomeType> to make that pass then you could do something like
class List<T> : Printable where T:SomeType {
//Add code here that conforms to protocol
}
Doing that will make your second test fail because Any doesn't inherit from SomeType but it'll make your third test pass because now List<T> is Printable and T is also of type SomeType. I mean, that's just a real quick way to get what it looked like you wanted to begin with. You're not going to have the second and third tests pass at the same time unless you add something extra because the second test is List being of type Any while the third is List being of type Printable. So either one of them will throw an error (because List isn't of type Printable) or all tests show success (because List is of type Printable)

swift inconsistent generic protocol restriction

I seem to be running into what appears to be a compiler inconsistency when passing arguments to a generic function that has a protocol restriction. I can pass a concrete argument, but not an argument as a protocol type
protocol Selectable {
func select()
}
protocol Log : Selectable {
func write()
}
class DefaultLog : Log {
func select() {
print("selecting")
}
func write() {
print("writing")
}
}
let concrete = DefaultLog()
let proto: Log = DefaultLog()
func myfunc<T: Selectable>(arg: T) {
arg.select()
}
myfunc(concrete) // <-- This works
myfunc(proto) // <-- This causes a compiler error
proto.write() // <-- This works fine
The compiler reports:
error: cannot invoke 'myfunc' with an argument list of type '(Log)'
myfunc(proto)
^
note: expected an argument list of type '(T)'
myfunc(proto)
^
If I restrict the function to the Selectable or Log protocol it still fails.
Is this a compiler bug? Any thoughts?
If you are using a protocol, it doesn't need to be generic:
func myfunc(arg: Selectable) {
arg.select()
}
I believe that T needs to be a concrete type for generics.

Constraining one generic with another in Swift

I've run into a problem where I have some protocol:
protocol Baz {
func bar<T>(input:T)
}
The function bar is made generic because I don't want the protocol itself to have a Self(it needs to be useable in a collection). I have an implementation of the protocol defined as:
class Foo<S>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T>(input:T) {
value = input
}
}
This gives an error because the compiler doesn't know that S and T are the same type. Ideally I should be able to write something like:
func bar<T where T==S>(input:T) {
value = input
}
or
func bar<T:S>(input:T) {
value = input
}
The first form gives a "Same-type requirement makes generic parameter 'S' and 'T' equivalent" error (which is exactly what I'm trying to do, so not sure why it's an error). The second form gives me a "Inheritance from non-protocol, non-class type 'S'".
Any ideas of on either how to get this to work, or a better design pattern in Swift?
Update: As #luk2302 pointed out, I forgot to make Foo adhere to the Baz protocol
#luk2302 has hinted at much of this in the comments, but just to make it explicit for future searchers.
protocol Baz {
func bar<T>(input:T)
}
This protocol is almost certainly useless as written. It is effectively identical to the following protocol (which is also almost completely useless):
protocol Baz {
func bar(input:Any)
}
You very likely mean (and hint that you mean):
protocol Baz {
typealias T
func bar(input: T)
}
As you note, this makes the protocol a PAT (protocol with associated type), which means you cannot put it directly into a collection. As you note, the usual solution to that, if you really need a collection of them, is a type eraser. It would be nice if Swift would automatically write the eraser for you, which it likely will be able to do in the future, and would eliminate the problem. That said, though slightly tedious, writing type erasers is very straightforward.
Now while you cannot put a PAT directly into a collection, you can put a generically-constrained PAT into a collection. So as long as you wrap the collection into a type that constrains T, it's still no problem.
If these become complex, the constraint code can become tedious and very repetitive. This can be improved through a number of techniques, however.
Generic structs with static methods can be used to avoid repeatedly providing constraints on free-functions.
The protocol can be converted into a generic struct (this formalizes the type eraser as the primary type rather than "as needed").
Protocols can be replaced with functions in many cases. For example, given this:
protocol Bar {
typealias T
func bar(input: T)
}
struct Foo : Bar {
func bar(input: Int) {}
}
You can't do this:
let bars: [Bar] = [Foo()] // error: protocol 'Bar' can only be used as a generic constraint because it has Self or associated type requirements
But you can easily do this, which is just as good:
let bars = [(Int) -> Void] = [Foo().bar]
This is particularly powerful for single-method protocols.
A mix of protocols, generics, and functions is much more powerful than trying to force everything into the protocol box, at least until protocols add a few more missing features to fulfill their promise.
(It would be easier to give specific advice to a specific problem. There is no one answer that solves all issues.)
EDITED (Workaround for "... an error because the compiler doesn't know that S and T are the same type.")
First of all: This is just an separate note (and perhaps an attempt at redemption for my previous answer that ended up being myself chasing my own tail to compute lots and lots of redundant code) in addition to Robs excellent answer.
The following workaround will possibly let your implementation protocol Foo ... / class Bas : Foo ... mimic the behaviour you initially asked for, in the sense that class method bar(...) will know if generics S and T are actually the same type, while Foo still conforms to the protocol also in the case where S is not of the same type as T.
protocol Baz {
func bar<T>(input:T)
}
class Foo<S>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T>(input:T) {
if input is S {
value = input as! S
}
else {
print("Incompatible types. Throw...")
}
}
}
// ok
var a = Foo(value: 1.0) // Foo<Double>
print(a.value) // 1.0
a.bar(2.0)
print(a.value) // 2.0
let myInt = 1
a.bar(myInt) // Incompatible types. Throw...
print(a.value) // 2.0
// perhaps not a loophole we indended
let myAny : Any = 3.0
a.bar(myAny)
print(a.value) // 3.0
The Any and AnyObject loophole here could be redeemed by creating a dummy type constraint that you extend all types (that you wish for to use the generics) to, however not extending Any and AnyObject.
protocol NotAnyType {}
extension Int : NotAnyType {}
extension Double : NotAnyType {}
extension Optional : NotAnyType {}
// ...
protocol Baz {
func bar<T: NotAnyType>(input:T)
}
class Foo<S: NotAnyType>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T: NotAnyType>(input:T) {
if input is S {
value = input as! S
}
else {
print("Incompatible types. Throw...")
}
}
}
// ok
var a = Foo(value: 1.0) // Foo<Double>
// ...
// no longer a loophole
let myAny : Any = 3.0
a.bar(myAny) // compile time error
let myAnyObject : AnyObject = 3.0
a.bar(myAnyObject) // compile time error
This, however, excludes Any and AnyObject from the generic in full (not only for "loophole casting"), which is perhaps not a sought after behaviour.

Call generic within generic with only knowledge of protocol

Say I have a generic box, and squishable objects. If I put something squishy in the box, then the box, too, can be squished.
protocol Squishable {
}
func squish<T: Squishable>(thing: T) {
}
struct Box<T> {
let content: T
}
//Here's what I'd LIKE to write, but it's not valid code:
extension (Box where T: Squishable): Squishable {
//...so I try this instead...
extension Box: Squishable {
func hereBeDragons() {
if content is Squishable {
squish(content as! Squishable)
//...but then: "Generic parameter 'T' cannot be bound to non-#objc protocol type 'Squishable'"
}
}
}
I gather my squish function wants a specific type that happens to be Squishable. But it doesn't seem I can give it one, given the circumstances... Is there a workaround? (In this trivial example, obviously we could change squish to a non-generic that accepts a protocol, but that's a totally different situation then! The question is what to do with this one.)
Edit: As of Xcode 7 beta 2, it seems extensions are more flexible. I can now write this and I think the compiler might know what I mean:
extension Box: Squishable where T: Squishable
But it sadly responds "Extension of type 'Box' with constraints cannot have an inheritance clause". That error seems to explicitly disallow exactly what I wish to do!
If I didn't get it wrong, Xcote 7 Beta 2 which includes Swift 2.0 Beta 2 introduce extension conditionals in a way that solves your question:
protocol Squishable {
}
func squish(thing: Squishable) {
print("Squishable")
}
struct Box<T> {
let content: T
}
extension Box where T: Squishable {
func hereBeDragons() {
squish(content)
}
}
extension Int: Squishable {}
let intBox = Box<Int>(content: 4)
intBox.hereBeDragons()
let stringBox = Box<String>(content: "Foo")
stringBox.hereBeDragons() // Compiler complains since String does not conform to Squishable
Please notice this is still Beta, so things can change. Anyway I'm pretty confident that the final version will have the feature needed from your question.