Swift Type Constraints not working as expected - swift

I've been dabbling with Swift recently, and I've hit a weird stumbling block with type constraints not operating as I would expect they should (in comparison to say, Scala).
protocol Foo {
typealias T: Hashable
func somethingWithT() -> T
}
struct Bar: Foo {
typealias T = Int
func somethingWithT() -> T { return 1 }
}
func baz() -> [Foo] {
var myBars = [Foo]()
myBars.append(Bar())
return myBars
}
This throws an error in XCode:
Any ideas what is going on here? I want T to be hashable for use as a key in a dictionary, but from what I've read Hashable has a reference to Self somewhere, so it can only be used as a generic constraint, thus removing my ability to just have a [Foo].
I want to have a list of things that do Foo, but at this rate it seems I'll either have to remove the Hashable constraint or make it less generic..
I've tried cleaning my project and re-making, but no dice :(

The problem here is that the type of T (of the protocol) has to be known at runtime. Due to the lack of generics with type aliases you can work around that by making an AnyFoo type (like in the standard library AnyGenerator and AnySequence):
protocol Foo {
typealias T: Hashable
func somethingWithT() -> T
}
struct AnyFoo<T: Hashable>: Foo {
let function: () -> T
init<F: Foo where F.T == T>(_ foo: F) {
// storing a reference to the function of the original type
function = foo.somethingWithT
}
func somethingWithT() -> T {
return function()
}
}
struct Bar: Foo {
typealias T = Int
func somethingWithT() -> T { return 1 }
}
// instead of returning [Foo] you can return [AnyFoo<Int>]
func baz() -> [AnyFoo<Int>] {
var myBars = [AnyFoo<Int>]()
// converting the type or Bar
myBars.append(AnyFoo(Bar()))
return myBars
}
This is not a generic function but you can convert any Foo type with the same T to the same AnyFoo type

myBars is an array of T: Foo. You could certainly pass in a Bar, because it satisfies the constraint on T. But so could another concrete type. Since T must be a concrete type, you can't arbitrarily put a Bar on there without guaranteeing that the only thing you'll put in there are Bars. And the only way to do that in Swift with this function signature would be to pass in a Bar.
I think maybe what you want is an array of protocol objects, not a generic array.

Related

Generics and a method returning a specialised generic instance

I am trying to create a method that will return a specialized instance of some generic type. Let's assume the following example:
class Base { }
class Foo: Base {}
class Bar: Base {}
class MyGenericView<T> where T: Base {
func show(with item: T) { }
}
class MySpecializedViewFoo: MyGenericView<Foo> { }
class MySpecializedViewBar: MyGenericView<Bar> { }
With the above given, I would like to have a function like:
func createView<T: Base>(for item: T) -> MyGenericView<T>
but when I try to implement it like
func createView<T: Base>(for item: T) -> MyGenericView<T> {
if item is Foo {
return MySpecializedViewFoo()
} else if item is Bar {
return MySpecializedViewBar()
}
fatalError("Unsupported item")
}
Then I receive
"Cannot convert return expression of type 'MyGenericView' to return type 'MyGenericView'" error.
Is that something that can be achieved? Could somebody please take a look and point an error in my understanding of that?
You can forcibly make the compiler trust you by doing:
func createView<T: Base>(for item: T) -> MyGenericView<T> {
if item is Foo {
return MySpecializedViewFoo() as! MyGenericView<T>
} else if item is Bar {
return MySpecializedViewBar() as! MyGenericView<T>
}
fatalError("Unsupported item")
}
The compiler doesn't let you do this because it is smart enough to do control flow analysis to determine the bounds of generic type parameters at any given point of a method. As far as it is concerned, return MySpecializedViewFoo() means the same thing inside and outside the if statement.
The compiler isn't designed like that because you are not supposed to checking the type of a generic type parameter anyway.
You should use two overloads instead:
func createView(for item: Foo) -> MyGenericView<Foo>
func createView(for item: Bar) -> MyGenericView<Bar>
In fact, your checks aren't adequate at all in the first place, so even if the compiler is smart enough, it will tell you that what you are doing isn't safe. Here's an example of how this could go wrong:
class FooSubclass: Foo {}
let foo = FooSubclass1()
let view: MyGenericView<FooSubclass1> = createView(foo)
Using the fix at the beginning, this will crash at runtime. createView will try to create and return a MySpecializedViewFoo, which is not a MyGenericView<FooSubclass1>. Swift generics are invariant. Note that in this case, it will go into the is Foo branch, not the fatalError branch.
Another case is using Base as T:
let foo: Base = Foo()
let view: MyGenericView<Base> = createView(for: foo)
This again will not go into the fatalError branch. If you make Base a protocol, you can make it produce an error when T is inferred to be Base. If you also make Foo and Bar final, then I think that should be safe, from the point of view of the hypothetical "smart" compiler.

Extending custom type where associated type is equal to Void

I'm in a situation where I have a custom type that contains an associatedtype. In the case where this is equal to Void, I would like to have some default behaviour (to make the call-site more convenient). I tried to boil the example down to:
protocol FooType {
associatedtype T: Any
var bar: (String) -> T { get }
}
struct Foo<T>: FooType {
let bar: (String) -> T
}
extension Foo where T == Void { // Compile error: "Same-type requirement makes generic parameter 'T' non-generic".
init() {
self.bar = { _ in return }
}
}
The idea is that, in the cases where the generic type is Void, it doesn't make sense (in my scenario) to pass in a function (named bar in the example). Therefore, I just want a default implementation for this function in this specific context.
When trying to do the above I get the Same-type requirement makes generic parameter 'T' non-generic which sounds very similar to what happens when one tries to restrict e.g. the Array type when containing specific types. A workaround for this is to introduce a protocol, but I cannot do that for Void. Is it possible to do what I want or is this currently a limitation in Swift 3?
As of Swift 3.1, the code posted in the question now works. That is, the following now works as wanted:
protocol FooType {
associatedtype T: Any
var bar: (String) -> T { get }
}
struct Foo<T>: FooType {
let bar: (String) -> T
}
extension Foo where T == Void {
init() {
self.bar = { _ in return }
}
}
let foo = Foo<String>(bar: { (t: String) in return "" })
let zoo = Foo<Void>()

Swift protocol extension implementing another protocol with shared associated type

Consider the following:
protocol Foo {
typealias A
func hello() -> A
}
protocol FooBar: Foo {
func hi() -> A
}
extension FooBar {
func hello() -> A {
return hi()
}
}
class FooBarClass: FooBar {
typealias A = String
func hi() -> String {
return "hello world"
}
}
This code compiles. But if I comment out explicit definition of associated type typealias A = String, then for some reason, swiftc fails to infer the type.
I'm sensing this has to do with two protocols sharing the same associated type but without a direct assertion through, for example, type parameterization (maybe associated type is not powerful/mature enough?), which makes it ambiguous for type inference.
I'm not sure if this is a bug / immaturity of the language, or maybe, I'm missing some nuances in protocol extension which rightfully lead to this behaviour.
Can someone shed some light on this?
look at this example
protocol Foo {
typealias A
func hello() -> A
}
protocol FooBar: Foo {
typealias B
func hi() -> B
}
extension FooBar {
func hello() -> B {
return hi()
}
}
class FooBarClass: FooBar {
//typealias A = String
func hi() -> String {
return "hello world"
}
}
with generics
class FooBarClass<T>: FooBar {
var t: T?
func hi() -> T? {
return t
}
}
let fbc: FooBarClass<Int> = FooBarClass()
fbc.t = 10
fbc.hello() // 10
fbc.hi() // 10
Providing explicit values for associated types in a protocol is required for conformance to said protocol. This can be accomplished by hard coding a type, as you've done with typealias A = String, or using a parameterized type as you mentioned, such as below:
class FooBarClass<T>: FooBar {
typealias A = T
...
}
Swift will not infer your associated type from an implemented method of the protocol, as there could be ambiguity with multiple methods with mismatching types. This is why the typealias must be explicitly resolved in your implementing class.

Generics and protocol conformance in Swift functions

I just read Brent Simmon's post on a problem he is having with Swift and I thought I had the answer: generic protocol conformance.
The problem he's having is that he has a protocol, Value, that conforms to Equatable. He has another protocol, Smashable, that requires the function valueBySmashingOtherValue. He has a struct, Bar, that does in fact conform to Smashable and Value.
In a subsequent function that takes a generic type T, a Bar is returned. The Swift type system complains that 'Bar' is not convertible to 'T'.
Here's what I thought would work:
protocol Value: Equatable { }
protocol Smashable {
func valueBySmashing​OtherValue​<T: Value, Smashable>(value: T) -> T
}
struct Bar: Smashable, Value {
func valueBySmashing​OtherValue​<T: Value, Smashable>(value: T) -> T {
return value;
}
}
func ==(lhs: Bar, rhs: Bar) -> Bool {
return false
}
struct Foo {
func valueBySmashing​OtherValue​<T: Value, Smashable>(value: T) -> T {
return Bar()
}
}
Make the generic type T conform to Value and Smashable. Bar does in fact conform to these, so the type system should be fine with you returning it.
But it isn't. Why?
While it is true that Bar conforms to Value and Smashable it is not the only type that meets those criteria. I could create a new type (using latest Swift syntax):
struct NotBar: Smashable, Value {
func valueBySmashing​OtherValue​<T:Value where T:Smashable>(value: T) -> T {
return value;
}
}
func ==(lhs: NotBar, rhs: NotBar) -> Bool {
return true
}
If I passed an instance of NotBar into Foo.valueBySmashing​OtherValue, then I would expect a NotBar in return. The compiler knows this so it does not allow you to return a Bar.

Can't create an Array of types conforming to a Protocol in Swift

I have the following protocol and a class that conforms to it:
protocol Foo{
typealias BazType
func bar(x:BazType) ->BazType
}
class Thing: Foo {
func bar(x: Int) -> Int {
return x.successor()
}
}
When I try to create an Array of foos, I get an odd error:
var foos: Array<Foo> = [Thing()]
Protocol Foo can only be used as a generic constraint because it has
Self or associated type requirements.
OK, so it can only be used if it has an associated type requirement (which it does), but for some reason this is an error?? WTF?!
I'm not sure I fully understand what the compiler is trying to tell me...
Let's say, if we could put an instance of Thing into array foos, what will happen?
protocol Foo {
associatedtype BazType
func bar(x:BazType) -> BazType
}
class Thing: Foo {
func bar(x: Int) -> Int {
return x.successor()
}
}
class AnotherThing: Foo {
func bar(x: String) -> String {
return x
}
}
var foos: [Foo] = [Thing()]
Because AnotherThing conforms to Foo too, so we can put it into foos also.
foos.append(AnotherThing())
Now we grab a foo from foos randomly.
let foo = foos[Int(arc4random_uniform(UInt32(foos.count - 1)))]
and I'm going to call method bar, can you tell me that I should send a string or an integer to bar?
foo.bar("foo") or foo.bar(1)
Swift can't.
So it can only be used as a generic constraint.
What scenario requires a protocol like this?
Example:
class MyClass<T: Foo> {
let fooThing: T?
init(fooThing: T? = nil) {
self.fooThing = fooThing
}
func myMethod() {
let thing = fooThing as? Thing // ok
thing?.bar(1) // fine
let anotherThing = fooThing as? AnotherThing // no problem
anotherThing?.bar("foo") // you can do it
// but you can't downcast it to types which doesn't conform to Foo
let string = fooThing as? String // this is an error
}
}
I have been playing with your code trying to understand how to implement the protocol. I found that you can't use Typealias as a generic type because it is just an alias not a type by itself. So if you declare the Typealias outside your protocol and your class you can effectively use it in your code without any problem.
Note: the Typealias has the Int type in its declaration, that way you can always use the alias instead of the Int type and use all of its associated methods and functions.
Here's how I make it work:
typealias BazType = Int
protocol Foo{
func bar(x:BazType) -> BazType
}
class Thing: Foo {
func bar(x: BazType) -> BazType {
return x.successor()
}
}
let elements: Array<Foo> = [Thing(), Thing()]