How can I make my generic parameter conform to OR in Swift and how can I found the conformation of parameter? - swift

I want build a function which accept to kind of protocols, the function should work if one of protocols passed, I have 2 issue with this function, first I do not know how I can apply OR to protocols, and second i do not know how can I find out my incoming value inside the function conform to which protocol, then I could run right code!
func printFunction<T: CustomStringConvertible OR CustomDebugStringConvertible>(value: T) { // 1: issue with OR!
if value.description { // 2: issue with finding out which protocol conformation is!
print(value.description)
}
else if value.debugDescription { // 3: issue with finding out which protocol conformation is!
print(value.debugDescription)
}
else {
print("not printable!")
}
}

There is no OR operator for protocol conformance. Protocol is like a contract you can't conform to one or another. You need to conform to both. What you need is to implement two methods. One for each. Btw no need to explicitly type description when using CustomStringConvertible:
func printFunction<T: CustomStringConvertible>(value: T) { print(value) }
func printFunction<T: CustomDebugStringConvertible>(value: T) { print(value.debugDescription) }

Related

Set of protocols

Suppose I have this:
protocol MyStuff: Hashable {
var stuff: String { get }
}
extension MyStuff {
func hash(into hasher: inout Hasher) {
hasher.combine(stuff)
}
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.stuff == rhs.stuff
}
}
struct Stuff: MyStuff {
let stuff: String
}
Where I have a protocol which conforms to Hashable, and I extend that protocol to implement the requirements of Hashable. This works great.
I'm now trying to use the api like this:
let set: Set<MyStuff> = [Stuff(stuff: "Stuff")]
However I get this error:
Protocol 'MyStuff' as a type cannot conform to 'Hashable'
This question is actually a follow up to this answer written by George, and in that answer it says that
I believe SE-0309 (Unlock existentials for all protocols) could fix this.
I was wondering if that (the above link) would actually apply into this situation (listed in the code block above), as I'm having trouble fully understanding the proposal.
There are a couple of issues here, not necessarily due to the code you wrote, but due to how Swift is currently designed:
Protocols don't conform to other protocols, which means that MyStuff is not a sub-type of Hashable, which means you cannot use it as argument for the generic Set
Protocols with associated types, or self requirements, like Equatable, from which Hashable derives, can't be used as generic arguments, can only be used as generic constraints
The compiler runs into issue #1 from above, and that one gives the (clear maybe) message that protocols as types cannot be used as generic arguments instead of other protocols.
SE-0309 might solve issue #2, however you're stuck on #1, so you'll have to change your design.
Solutions? As others have suggested in the comments:
use a type eraser (e.g. https://stackoverflow.com/a/64476569/1974224)
use classes, and replace the protocol by a base class

Create a Swift extension with a where clause that filters on a struct that takes a generic

I'm trying to create an extension on Set that uses a where clause so that it only works on a struct I have that accepts a generic. But I keep running into errors about it the extension wanting the generic to be defined in the struct
In this example I'm getting the following error, with the compiler hint suggesting I use <Any>: Reference to generic type 'Test' requires arguments in <...>
struct Test<T> {
var value : T
func printIt() {
print("value")
}
}
extension Set where Element == Test {
}
However, when I use <Any> in the struct, I'm getting this error: Same-type constraint type 'Test' does not conform to required protocol 'Equatable'
extension Set where Element == Test<Any> {
}
Any suggestions on how to get the where clause to accept the Test struct for any type I'm using in the generic?
Thanks for your help
This is a limitation of Swift's type system. There's no way to talk about generic types without concrete type parameters, even when those type parameters are unrelated to the use of the type. For this particular situation (an extension for all possible type parameters), I don't believe there's any deep problem stopping this. It's a simpler version of parameterized extensions, which is a desired feature. It's just not supported (though there is an implementation in progress).
The standard way to address this today is with a protocol.
First, a little cleanup that's not related to your question (and you probably already know). Your example requires Test to be Hashable:
struct Test<T: Hashable>: Hashable {
var value : T
func printIt() {
print("value")
}
}
Make a protocol that requires whatever pieces you want for the extension, and make Test conform:
protocol PrintItable {
func printIt()
}
extension Test: PrintItable {}
And then use the protocol rather than the type:
extension Set where Element: PrintItable {
func printAll() {
for item in self { item.printIt() }
}
}
let s: Set<Test<Int>> = [Test(value: 1)]
s.printAll() // value
Just one more note on the error messages you're getting. The first error, asking you to add Any is really just complaining that Swift can't talk about unparameterized generics, and suggesting it's fallback type when it doesn't know what type to suggests: Any.
But Set<Any> isn't "any kind of Set." It's a Set where Element == Any. So Any has to be Hashable, which isn't possible. And a Set<Int> isn't a subtype of Set<Any>. There' completely different types. So the errors are a little confusing and take you down an unhelpful road.
This is not possible. The where clause requires a specific data type and simply passing a Test will not work unless I specify something more concrete like Test<String>.
Thank you to Joakim and flanker for answering the question in the comments
If you want to add extension for Set with where clause your Test must confirm to Hashable protocol.
Your Struct must look like this.
struct Test<T: Hashable> : Hashable {
var value : T
func printIt() {
print("value")
}
func hash(into hasher: inout Hasher) {
hasher.combine(value.hashValue)
}
}
So you can't use Any for your extension you must specify type that confirm to Hashable protocol.

Swift - Protocol default implementation in extension with generic superclass constraint

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
}
}

Difference between extension and direct call for protocol

I got this code:
protocol Protocol {
var id: Int { get }
}
extension Array where Element: Protocol {
func contains(_protocol: Protocol) -> Bool {
return contains(where: { $0.id == _protocol.id })
}
}
class Class {
func method<T: Protocol>(_protocol: T) {
var arr = [Protocol]()
// Does compile
let contains = arr.contains(where: { $0.id == _protocol.id })
// Doens't compile
arr.contains(_protocol: _protocol)
}
}
Why doesn't the line of code compile where I commented 'Doens't compile'? This is the error:
Incorrect argument label in call (have '_protocol:', expected 'where:')
When I change the method name in the extension to something else, like containz (and ofcourse change the name of the method that calls it to containz), I get this error when I try to call it:
Using 'Protocol' as a concrete type conforming to protocol 'Protocol' is not supported
But why doesn't it work when I try to call it through an extension, but it does work when I create the function in the extension directly? There isn't really any difference that I can see.
I agree with matt that the underlying answer is Protocol doesn't conform to itself?, but it's probably worth answering anyway, since in this case the answer is very simple. First, read the linked question about why [Protocol] doesn't work the way you think it does (especially Hamish's answer, which is much more extensive than the accepted answer that I wrote). [Protocol] does not match the where Element: Protocol clause because Protocol is not a concrete type that conforms to Protocol (because it's not a concrete type).
But you don't need [Protocol] here. You have T: Protocol, so you can (and should) just use that:
var arr = [T]()
With that change, the rest should work as you expect because T is a concrete type that conforms to Protocol.

Protocol: Cannot assign to 'X' in 'Y' in Swift

I just defined a very simple protocol and a a class using generics which can handle this protocol.
In the lines marked with error you will get the error: "Cannot assign to 'flag' in 'aObj'.
protocol Flag {
var flag: Bool {get set}
}
class TestFlag<T: Flag> {
func toggle(aObj: T) {
if aObj.flag {
aObj.flag = false; // <--- error
} else {
aObj.flag = true; // <--- error
}
}
}
Do you have an idea why and what I have to change to fix it?
From the docs:
Function parameters are constants by default. Trying to change the
value of a function parameter from within the body of that function
results in a compile-time error. This means that you can’t change the
value of a parameter by mistake.
In this case, you can add inout so that the toggle is persisted beyond your function call:
func toggle(inout aObj: T) {
if aObj.flag {
aObj.flag = false;
else {
aObj.flag = true;
}
}
You could have also done:
func toggle(var aObj: T) {
}
but that might not achieve what you wanted.
manojlds answer is correct and therefore I accepted it.
Nevertheless there was a similar answer some days ago with the same solution but with a other argumentation (seems now deleted).
The argumentation was about that the compliler can not know if the protocol is used for a class, a struct or a enum. With Swift, protocols can by applied on all this types. But struct instances use a by-value call and for classes instances (objects) it us a by-reference call.
From my perspective this answer was correct too, because you can solve the problem with a 2nd solution:
#objc
protocol Flag {
var flag: Bool {get set}
}
Just add the #obj attriute on the protocol. As a result you can use this protocol only for a class which lead to the result only by-refernece calls are allowd. Therefore the compiler don't need anymore the inout information.
But I searched for a solution to increase the reuse of the protocol and use now manojlds suggestions.