Constraining a generic initilizer parameter to class generic type - swift

I'm trying to implement a type erasing wrapper for a protocol. I have a protocol with an associatedtype constrainted to CaseIterable and a method using that type.
Given the following definitions:
protocol Foo {
associatedtype A: CaseIterable
func doSomething(input: A)
}
final class AnyFoo<A: CaseIterable>: Foo {
private let _doSomething: (A) -> Void
init<Other: Foo>(wrappedFoo: Other) where Other.A == A {
// "Cannot assign value of type '(Other.A) -> ()' to type '(A) -> Void'"
_doSomething = wrappedFoo.doSomething(input:)
}
func doSomething(input: A) {
_doSomething(input)
}
}
I'm getting the error Cannot assign value of type '(Other.A) -> ()' to type '(A) -> Void' in the initializer of the class. It seems that the compiler interprets A and Other.A as different types, and I can't figure out why, because the initializer has constrained Other.A and A to be the same. If I replace CaseIterable with Hashable, there's no problem.
Can somebody explain why this is happening?

Of course you can't. The _doSomething in class AnyFoo is type of Block which is an Anonymous function. So you can assign wrappedFoo.doSomething(input:) to it. When you call _doSomething(input), it actually call wrappedFoo.doSomething(input: input).
In another way, you can define a Block like:
let completion: (bool) -> void = { finished in
}
but you can't define it like:
let completion: (true) -> void = { finished in
}

Related

Swift protocol conformance when returning a generic

Here's an example:
protocol Feed {
func items<T>() -> [T]? where T: FeedItem
}
protocol FeedItem {}
class FeedModel: Feed, Decodable {
func items<T>() -> [T]? where T : FeedItem {
return [FeedItemModel]() // Error: Cannot convert return expression of type '[FeedItemModel]' to return type '[T]?'
}
}
class FeedItemModel: FeedItem, Decodable {}
Why does it:
A) try to convert to T when T is a generic, not a type?
B) does not recognize FeedItemModel as conforming to FeedItem?
func items<T>() -> [T]? where T : FeedItem
This says that the caller can define T to be whatever they want, as long as T conforms to FeedItemModel, and this function will return an optional array of those.
FeedItemModel is something that conforms to FeedItem, but it is not promised to be the type T that the caller requested.
As an example, consider:
class OtherModel: FeedItem {}
According to your function signature, I can do this:
let ms: [OtherModel]? = FeedModel().items()
But your function won't then return [OtherModel]? to me. I suspect you don't actually mean this to be generic. I expect you mean:
func items() -> [FeedItemModel]?
or possibly
func items() -> [FeedItem]?
(Though I would think very hard before doing the latter one and make sure that the protocol existential is really doing useful work here.)
A)
T is a type, a homogenous concrete type specified at runtime.
Imaging T is class Foo : FeedItem it's obvious that FeedItemModel cannot be converted to Foo
B)
FeedItemModel is recognized as conforming to FeedItem but this is irrelevant.
It's often a mix-up of generics and protocols. Generic types are not covariant. If you need covariant types use an associated type.
Either you can ignore generics because because it only applies to that one function and it isn't needed since directly saying that the return type is [FeedItem]? yields the same result
protocol Feed {
func items() -> [FeedItem]?
}
class FeedModel: Feed, Decodable {
func items() -> [FeedItem]? {
return [OtherModel]()
}
}
If you on the other hand want a generic protocol then you should use a associated type
protocol Feed2 {
associatedtype T: FeedItem
func items() -> [T]?
}
class FeedModel2: Feed2, Decodable {
typealias T = FeedItemModel
func items() -> [T]? {
return [FeedItemModel]()
}
}

SwifT: Generic function that gets generic protocol as parameter not working

Suppose I have this simple generic protocol
protocol FooProtocol {
associatedtype Element: CustomStringConvertible
func echo(element: Element) -> Element
}
And this simple generic struct that implements it
struct FooStruct<Element: CustomStringConvertible>: FooProtocol {
func echo(element: Element) -> Element {
return element
}
}
Lastly, I have the following function:
func callEcho<T: FooProtocol>(container: T) {
container.echo(element: "some string")
}
This results in error: cannot invoke 'echo' with an argument list of type '(element: String)'
The solution is to change the function to
func callEcho<T: FooProtocol>(container: T) where T.Element == String {
container.echo(element: "some string")
}
My question is: why is the where T.Element == String constraint necessary? The compiler knows that T is some entity that implements FooProtocol, and the protocol only demands that Element implements CustomStringConvertible, which my string literal does. So why does it not work without the constraint?
Thanks!
I'm not sure why you say "the protocol only demands that Element implements CustomStringConvertible." The protocol demands that echo accept and return its Element, which may or may not be String. For example, I can implement this conforming type:
struct AnotherFoo: FooProtocol {
func echo(element: Int) -> Int { fatalError() }
}
So what should happen if I then call:
callEcho(container: AnotherFoo())
This would try to pass a string literal to a function that requires an Int.
container has type T, so that container.echo(element:) expects an argument of type T.Element – and that is not necessarily a string.
If the intention is to pass string literals to the method then T.Element must adopt ExpressibleByStringLiteral, not CustomStringConvertible:
protocol FooProtocol {
associatedtype Element: ExpressibleByStringLiteral
func echo(element: Element) -> Element
}
Now this compiles:
func callEcho<T: FooProtocol>(container: T) {
_ = container.echo(element: "some string")
}

Protocol associatedType and <>

what is a difference between using generic with function or using associatedType in swift protocols?
protocol Repository {
associatedtype T
func add(data : T) -> Bool
}
and
protocol Repository {
func add<T>(data : T) -> Bool
}
Defined associated type makes classes which conform protocol strong typed. This provide compile-time error handling.
In other hand, generic type makes classes which conform protocol more flexible.
For example:
protocol AssociatedRepository {
associatedtype T
func add(data : T) -> Bool
}
protocol GenericRepository {
func add<T>(data : T) -> Bool
}
class A: GenericRepository {
func add<T>(data : T) -> Bool {
return true
}
}
class B: AssociatedRepository {
typealias T = UIViewController
func add(data : T) -> Bool {
return true
}
}
class A could put any class into add(data:) function, so you need to makes your sure that function handle all cases.
A().add(data: UIView())
A().add(data: UIViewController())
both would be valid
But for class B you will get compile-time error when you will try to put anything except UIViewController
B().add(data: UIView()) // compile-time error here
B().add(data: UIViewController())
An associatedtype is a static type in struct/class which adopts the protocol either via a typealias declaration or via type inference. The type is always the same for that class.
A generic can be anything, even different types in the same class.
This case
protocol Repository {
func add<T>(data : T) -> Bool
}
Is understood by compiler like: "Any type that is fed to the func add will be acceptable and the result of the function will be Bool"
But this
protocol Repository {
associatedtype T
func add(data : T) -> Bool
}
is understood by compiler like: "func add will accept only the type that is defined in the typealias T = ... and return Bool"
In the second case you restrict generic parameters only to the typealiased types.
Another important feature shows up when you use generic parameters in multiple functions of the protocol. In that case it guarantees that func add<T> and func multiply<T> will have the same type T. In case of generic functions it is not guaranteed.
protocol Calculable {
associatedtype T
func add<T>(a: T, b: T) -> T
func multiply<T>(a: T, b: T) -> T
}
// In this case you know that T is the same for all functions
protocol CalculableDifferent {
func add<T>(a: T, b: T) -> T
func multiply<T>(a: T, b: T) -> T
}
// In this case add can accept One type, when multiply can accept Another

Swift: Generics and type constraints, strange behavior

I can’t figure out how to perform a cast which would let me eventually to introduce some sort of dynamism working with generics.
Next piece of code does not compile. It shows this error:
Cannot invoke 'createContainer' with an argument list of type
'(FooProtocol)' Expected an argument list of type '(T)'
protocol FooProtocol {
func doSomething()
}
class Foo : FooProtocol {
func doSomething() {}
}
class Container<T : FooProtocol> {
let someDataConformingFooProtocol : T
init(someDataConformingFooProtocol : T) {
self.someDataConformingFooProtocol = someDataConformingFooProtocol
}
}
class AllTogether {
init () {
createContainer(Foo()) //So far, so good
let foo2Mask : AnyObject = Foo()
if let foo2MaskChecked = foo2Mask as? FooProtocol {
createContainer(foo2MaskChecked)
//ERROR: Cannot invoke 'createContainer' with an argument list of type '(FooProtocol)'
//Expected an argument list of type '(T)'
}
}
func createContainer<T : FooProtocol>(data: T){
Container<T>(someDataConformingFooProtocol: data)
}
}
Is this really the expected behaviour? Because personally I can’t understand what or why the compiler is complaining about it.
What would be the appropriate cast? Without referencing to the concrete class, I mean NOT like this:
if let foo2MaskChecked = foo2Mask as? Foo
Thanks!
What it comes down to is the difference between T: FooProtocol and FooProtocol as #Hamish mentioned in his comment.
When I have a function like this:
func foo(_ i: FooProtocol)
I'm taking in an instance of type FooProtocol, so I can pass in a value that conforms to FooProtocol or its type is FooProtocol. This is similar to subclassing where you can pass both SuperClass and SubClass into SuperClass.
But if you have a function like this:
func foo<T>(_ i: T) where T: FooProtocol
I can pass in any type that conforms to FooProtocol, but because FootProtocol does not conform to itself, you can't pass that in.
So in your question, you are trying to pass in a type FooProtocol where the types must conform to FooProtocol. You could probably fix this by doing:
class Container {
let someDataConformingFooProtocol: FooProtocol
init(someDataConformingFooProtocol: FooProtocol) {
self.someDataConformingFooProtocol = someDataConformingFooProtocol
}
}
// Later
func createContainer(data: FooProtocol){
Container(someDataConformingFooProtocol: data)
}

Error using associated types and generics

The following code gives me an error on line return p.foo(self). The error says: 'P' does not have a member named 'foo'.
protocol P {
typealias T
func foo(c: C<T>) -> T
func foo2() -> T
}
class C<T> {
var p: P
init (p: P) {
self.p = p
}
func bar() -> T {
return p.foo(self);
}
}
The protocol P defines an associated type which should match correctly with any specialized C type. Am I missing something? Or not?
I'll rename the types a bit before answering the question to make the problem a bit clearer:
protocol P {
typealias ElementType
func foo(c: C<ElementType>) -> ElementType
func foo2() -> ElementType
}
class C<T> {
var p: P
init (p: P) {
self.p = p
}
func bar() -> T {
return p.foo(self)
}
}
In that case you get three compiler errors:
error: <EXPR>:8:12: error: protocol 'P' can only be used as a generic constraint because it has Self or associated type requirements
var p: P
^
<EXPR>:9:14: error: protocol 'P' can only be used as a generic constraint because it has Self or associated type requirements
init (p: P) {
^
<EXPR>:13:16: error: 'P' does not have a member named 'foo'
return p.foo(self)
^ ~~~
The interesting one is the first/second one (they point out the same problem): "protocol 'P' can only be used as a generic constraint because it has Self or associated type requirements".
So the problem is the associated type. In the current configuration, you specify that the parameter for the initializer and the variable are of type P. But because you specified an associated type for P, that type is not specific enough to be used as a proper type. Only subtypes that actually specify what the ElementType is can be used. However, you can specific a generic parameter that has to be a subtype of P. In the case of the initializer you could write
init <S:P>(p: S) {
self.p = p
}
which would eliminate the compiler error for the initializer. Now the compiler knows the parameter has to be a subtype of P and a valid subtype always specifies what the ElementType is, so it is happy.
But this doesn't help you with this line:
var p: P
You still can't use the incomplete type P here. You would probably want to use S, but at the moment there is no connection between the S in the initializer and an S you would use as the type for you variable but they obviously need to be the same.
Time to introduce a second generic parameter to your class:
class C<T, S:P> {
var p: S
init (p: S) {
self.p = p
}
func bar() -> T {
return p.foo(self)
}
}
Almost done, now you have a properly specified type to use for your variable. But no your protocol specification is incorrect:
func foo(c: C<ElementType>) -> ElementType
C now takes two parameters and you need to specify these here. We would like to use `C here, but we can't:
error: :3:17: error
: type 'P' does not conform to protocol 'P'
func foo(c: C<ElementType, P>) -> ElementType
^
<EXPR>:2:15: note: associated type 'ElementType' prevents protocol from conforming to itself
typealias ElementType
Since P does not specify the associated type ElementType it does not properly conform to P and can't be used in a place where a type conforming to P is needed. But there is a nice special type: Self. That references the actual type of the implementing protocol, so we can write the following:
protocol P {
typealias ElementType
func foo(c: C<ElementType, Self>) -> ElementType
func foo2() -> ElementType
}
Now we specified that the foo-function that is implemented by any confirming type actually takes a C with the specified ElementType and the implementing type itself. Fancy, isn't it?
But we aren't fully done yet, one last error remains:
error: <EXPR>:13:18: error: cannot convert the expression's type 'T' to type 'S.ElementType'
return p.foo(self)
At this point the compiler knows the following:
p.foo(self) returns something of the ElementType of S
The function bar() should return something of type T
But there is nothing to tell it, that ElementType and T are actually the same, so it can't be sure whether this works and complains. So what we actually want is that the ElementType of S is always the same as T and we can specify this:
class C<T, S:P where S.ElementType == T> {
Complete code:
protocol P {
typealias ElementType
func foo(c: C<ElementType, Self>) -> ElementType
func foo2() -> ElementType
}
class C<T, S:P where S.ElementType == T> {
var p: S
init (p: S) {
self.p = p
}
func bar() -> T {
return p.foo(self);
}
}
You never required that the two Ts match. They are each in their own scope. Therefore the compiler looks for a foo function with the wrong parameter types.
I believe something like this would be correct:
protocol P {
typealias T
func foo<ConcreteP>(c: C<T, ConcreteP>) -> T
func foo2() -> T
}
class C<T, ConcreteP: P where T == ConcreteP.T> {
var p: ConcreteP
init (p: ConcreteP) {
self.p = p
}
func bar() -> T {
return p.foo(self);
}
}
At least I don't get any syntax errors. However on compilation I get:
error: unimplemented IR generation feature non-fixed class layout
var p: ConcreteP
^
LLVM ERROR: unimplemented IRGen feature! non-fixed class layout
That sounds like a compiler bug.
From The Swift Programming Language:
Protocols do not actually implement any functionality themselves. Nonetheless, any protocol you create will become a fully-fledged type for use in your code.
In order to use the .foo method, you need a struct or class that implements your protocol.