Swift generic constraint on method does not behave as expected when called from instance - swift

Here is the following piece of Swift code:
class HTTP {
func run<T: Decodable>(handler: (Result<T, Error>) -> Void) {
HTTP.handle(handler: handler)
}
}
extension HTTP {
static func handle<T: Decodable>(handler: (Result<T, Error>) -> Void) {
Swift.print("Base")
}
}
extension HTTP {
static func handle<T: Decodable & Offline>(handler: (Result<T, Error>) -> Void) {
Swift.print("Offline")
}
}
protocol Offline {
associatedtype Data
var data: Data { get set }
}
struct Model: Decodable, Offline {
var data: String = "abc..."
}
let h1 = HTTP()
h1.run { (r: Result<[String], Error>) in } // 1 - Print "Base" => OK
let h2 = HTTP()
h2.run { (r: Result<Model, Error>) in } // 2 - Print "Base" => ???
HTTP.handle { (r: Result<[String], Error>) in } // 3 - Print "Base" => OK
HTTP.handle { (r: Result<Model, Error>) in } // 4 - Print "Offline" => OK
I am trying to figure out why in case 2, it prints "Base" instead of "Offline". If anyone has suggestions so the right handle method get called depending on the given type T.
I made the handle method static for demo/running purpose to show it works in a static context (case 3 and 4). As you can see, called from the HTTP instance context behaviour is different (case 2).
Any idea ?

It looks like the instance function run(handler:) because it sets as a constraint the T to be only Decodable, filters in some way the Offline constraint. So, passes a type that is only Decodable to the static function handle(handler:) and the compiler assumes that the callable function is the one with only Decodable as a constraint.I do not know if this is understandable.
You can have the expected behavior by adding an overload for your instance function in HTTP class:
class HTTP {
func run<T: Decodable>(handler: (Result<T, Error>) -> Void) {
HTTP.handle(handler: handler)
}
func run<T: Decodable & Offline>(handler: (Result<T, Error>) -> Void) {
HTTP.handle(handler: handler)
}
}

The correct HTTP.handle to call is decided at compile time, and baked into the binary. T is only promised to be Decodable in run. It might conform to other protocols, but that's all that's promised. So it compiles this into a call to the Decodable version, even if you call it with something that could be Offline.
This gets to the point of generic specializations. It's not intended to change behavior. It's intended generally to improve performance. For example, consider the following:
func f<Seq: Sequence>(_ seq: Seq) -> Bool { ... }
func f<Seq: RandomAccessCollection>(_ seq: Seq) -> Bool { ... }
Every RandomAccessCollection is a Sequence, so one or the other might be called for an Array. Both should always return the same result. But the second might be more efficient in cases where the system can prove that Seq is Array. If they returned different results, it's not going to work correctly.
Generics are not a way to reinvent class inheritance. If you really need class inheritance, then use classes and inheritance. But generally you should redesign your system to avoid that. How to do that depends on the real goal here.

Related

Cannot convert Generic value to AnyObject, when it is constrained by AnyObject

The error is Cannot convert value of type '(O?, ObservedType) -> Void' to expected argument type '(AnyObject?, ObservedType) -> Void, but I find this curious since O is constrained as AnyObject.
For context, I'm creating my own Observable class, but this question is actually about the specific error message above rather than how I might use any other third-party framework to use observables. That is, how can I properly cast my completion handler in this case.
public class Observable<ObservedType> {
struct Observer<ObservedType> {
weak var observer: AnyObject?
let completion: (AnyObject?, ObservedType) -> Void
}
private var observers: [Observer<ObservedType>]
public var value: ObservedType? {
didSet {
if let _ = value {
notifyObservers()
}
}
}
public init(_ value: ObservedType? = nil) {
self.value = value
observers = []
}
public func observe<O: AnyObject>(forward object: O?, completion: #escaping (O?, ObservedType) -> Void) {
observers.append(Observer(observer: object, completion: completion)) // error here
if let value = value {
completion(object, value)
}
}
private func notifyObservers() {
for observer in observers {
if let value = value {
DispatchQueue.main.async { observer.completion(nil, value) }
}
}
}
}
Is it possible to cast my completion handler in this case, or in some way equate O and AnyObject
According to your types, I can pass any object I want to the first parameter of Observer.completion. But the function you're assigning to .completion can only accept some specific type O.
You have to change completion to (AnyObject?, ObservedType) -> Void.
public func observe<O: AnyObject>(forward object: O?, completion: #escaping (AnyObject?, ObservedType) -> Void) {
^^^^^^^^^^
And the function you pass will have to deal with the fact that it can be passed anything. I suspect that this will break your whole system. But I don't believe this style of Observable is going to work, anyway, because of exactly these kinds of type problems.
There's really no good way to directly store the Observer inside the Observable. You're not currently using it, but I assume you want it for something like removing the observer. There are ways to do that, but you can't store the observer itself. You can return a unique identifier (UUID, for example), or you can work with ObjectIdentifiers or you can pass back "remove this item" closures that the observer must call. But you generally don't want to store the observer directly (and definitely not as an AnyObject).
I recommend using Combine for this, since that's what it's designed for. Or if you need to support older iOS versions, see this experiment for ways to make this work, or this experiment for a simplified version closer to what you're trying to do here.

How to express this `map` function using a protocol

I have the following code
enum AppSubscription<Event>: Subscription {
func map<ParentSubscription>(_ lift: #escaping (Event) -> (ParentSubscription.Event)) -> ParentSubscription where ParentSubscription: Subscription {
switch self {
case let .loadedArticles(cb):
return AppSubscription<ParentSubscription.Event>.loadedArticles { article, isHot in lift(cb(article, isHot)) } as! ParentSubscription
case let .savedArticles(cb):
return AppSubscription<ParentSubscription.Event>.savedArticles { article in lift(cb(article)) } as! ParentSubscription
case let .deletedArticles(cb):
return AppSubscription<ParentSubscription.Event>.deletedArticles { article in lift(cb(article)) } as! ParentSubscription
}
}
case loadedArticles((Article, Bool) -> Event)
case savedArticles((Article) -> Event)
case deletedArticles((Article) -> Event)
}
and I want to create a protocol for Subscription (that AppSubscription conforms to). This is my attempt so far
public protocol Subscription {
associatedtype Event
func map<ParentSubscription>(_ lift:
#escaping (Event) -> (ParentSubscription.Event)) -> ParentSubscription where
ParentSubscription: Subscription
}
but I get the errors
Subs.playground:21:31: Protocol 'Subscription' can only be used as a generic constraint because it has Self or associated type requirements
and
Subs.playground:21:28: Same-type requirement makes generic parameter 'ParentSubscription' non-generic
How do I write the protocol's map function to match the implementation in AppSubscription?
Edit: Switched to ParentSubscription: Subscription as per #Fabian's suggestion. Also added missing #escaping to AppSubscription.
With the changes I'm not sure how to reconcile the protocol with the implementation. If I use the signature exactly as written in the protocol
func map<ParentSubscription, ParentEvent>(_ lift: #escaping (Event) -> (ParentEvent)) -> ParentSubscription where ParentSubscription : Subscription, ParentEvent == ParentSubscription.Event {
then I'm forced to use as! ParentSubscription which seems redundant. If I use the signature
func map<ParentEvent>(_ lift: (Event) -> ParentEvent) -> AppSubscription<ParentEvent> {
then it complains that the AppSubscription does not conform to the protocol. Is the only way to get it to compile to use as! ?
Edit2: Removed redundant ParentEvent and replaced with ParentSubscription.Event as per another suggestion by #Fabian.
The code now compiles which is great and many thanks to #Fabian.
I do think it's fair to say that the amount of type hinting seems awkward, is there any way to avoid or abbreviate any of it?
Edit3: Adding a repl link https://repl.it/#opsb/ScratchyTrustworthyArchitect to make it easy to play with.

Extension to generic struct where element is generic struct

I have a generic structs FutureValue<Element> and Failable<Element>, which both implement map…
struct FutureValue<Element> {
func map<U>(_ t: (Element) -> U) -> FutureValue<U> …
}
struct Failable<Element> {
func map<U>(_ t: (Element) -> U) -> Failable<U> …
}
I'd like to write an extension on FutureValue to specialise it when its Element is any Failable, so that I can implement a map like function that maps on the contained Element in the FutureValue<Failable<Element>>
How can I do this in Swift?
You just need to create a protocol that captures "any Failable" and captures the pieces you want for your algorithm.
protocol AnyFailable {
associatedtype Element
func map<U>(_ t: (Element) -> U) -> Failable<U>
}
And express that all Failables are AnyFailable.
extension Failable: AnyFailable {}
You may want to add methods on the protocol to extract data you need or provide methods.
Then, create your extension:
extension FutureValue where Element: AnyFailable {
func map<U>(_ t: (Element.Element) -> U) -> FutureValue<Failable<U>> {
// You will probably need to provide your own implementation here
return FutureValue<Failable<U>>(element: element.map(t))
}
}
It's worth noting how I constructed this. I started by writing a more concrete form based on String (just to pick a random thing):
extension FutureValue where Element == Failable<String> {
func map<U>(_ t: (String) -> U) -> FutureValue<Failable<U>> {
...
}
}
And I wrote a simple piece of consuming code:
let f = FutureValue(element: Failable(element: "alice"))
print(f.map { $0.first })
And from there, I extracted the pieces I needed into a protocol. This tends to get you moving in the right direction, step by step. It is very challenging sometimes to jump directly to the most generic form.
Many thanks to Rob for his super answer.
The approach I took in the end is slightly different so I'm adding it as a second answer. For the case of an extension to a generic that is constrained on the element being of some kind, I feel this approach is simpler. It's also a readily introduced "pattern" that can be easily dropped in to similar situations.
/*
Protocol for things that can be _concretely_ represented as a `Failable`.
I keep it private so it's just used to constrain the protocol extension
below.
*/
private protocol AsFailable {
associatedtype Element
var asFailable: Failable<Element> {get}
}
/*
`Failable` can definitely be represented `AsFailable`…
*/
extension Failable: AsFailable {
var asFailable: Failable<Element> {
return self
}
}
/*
Use the `AsFailable` protocol to constrain an extension to `FutureValue`
for any `FutureValue` who's `Element` is a `Failable`.
*/
extension FutureValue where Element: AsFailable {
func happyMap<U>(_ t: #escaping (Element.Element) -> U)
-> FutureValue<Failable<U>> {
return map { $0.asFailable.map(t) }
}
}
Rob's approach let me implement map (as set out in the OP), but I started to flounder when I wanted to implement flatMap as well. Switching to the use of AsFailable let me quickly write a simple implementation of flatMap.
I think the AsXXX approach is simper for a case like this where a protocol is just required to act as a constraint.
Here's what happyFlatMap looks like:
func happyFlatMap<U>(_ t: #escaping (FailableElement) -> FutureValue<Failable<U>>)
-> FutureValue<Failable<U>>
{
typealias Out = FutureValue<Failable<U>>
return flatMap {
failable in
switch failable.asFailable {
case let .happy(element):
return t(element)
case let .error(error):
return Out(Failable<U>.error(error))
case let .canceled(reason):
return Out(Failable<U>.canceled(reason))
}
}
}

Why doesn't this behave the way I expect (hope) it would?

I have a several protocols set up in my framework to deal with resources. In one of the protocols, I have set up an extension to provide a default implementation for a decode function. It's simpler to show the code and what happens (see calls to fatalError). There's a lot more code in the actual implementation, but this illustrates the issue:
This is the "base" protocol:
public protocol Resourceful {
associatedtype AssociatedResource
typealias ResourceCompletionHandler = (AssociatedResource?, Error?) -> Void
func fetch(_ completion: #escaping ResourceCompletionHandler)
}
This is a generic, concrete implementaion of Resourceful:
open class WebResourceApiCall<Resource>: Resourceful {
public typealias AssociatedResource = Resource
public typealias FetchedResponse = (data: Data?, urlResponse: URLResponse?)
public init() {
}
public func fetch(_ completion: #escaping ResourceCompletionHandler) {
try! decode(fetched: (data: nil, urlResponse: nil))
}
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("It ends up here, but I don't want it to!")
}
}
extension WebResourceApiCall where Resource: Decodable {
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("This is where I want it to go...")
}
}
This is how I'm attempting to use it:
public struct Something: Decodable { }
var apiCall = WebResourceApiCall<Something>()
apiCall.fetch { _, _ in } // Implictly calls decode... but not the decode I expected it to! See fatalError() calls...
Instead of calling decode on the extension, like I hoped it would, the "default" decode method with no constraints is always called.
Why doesn't this work the way I expect it to?
Thanks in advance!
Swift is a statically dispatched language, thus the address of the decode() function to be called is computed at compile time, and because the call happens inside the base definition of the class, the compiler picks the original implementation.
Now, if you call the method from a place where the compiler has enough information to pick the implementation you need, it will work:
var apiCall = WebResourceApiCall<Something>()
try apiCall.decode(fetched: (nil, nil))
The above code will call the method from the specialized extension, as at this point the compiler is a better position to know that it has a more specialized implementation to call.
It should be possible to achieve the behaviour you need if you move the decode() method in the dynamic dispatch world - i.e. at the protocol level.

Swift 3: Inheritance from non-named type

I have the following SSCIE:
protocol Foo {
associatedtype Bar
associatedtype Baz: (Self.Bar) -> Void
var currentValue: Bar { get }
}
That I want to use like this:
func call<T: Foo>(foo: T, callback: #escaping T.Baz) {
DispatchQueue.main.async {
callback(foo.currentValue)
}
}
But it fails to compile, with the error:
Inheritance from non-named type '(`Self`.Bar)'
This also fails to compile when I use (Bar) -> Void and (Foo.Bar) -> Void.
Sadly, Googling this didn't come up with any useful results.
Does anyone have any idea what this error means, what I'm doing wrong, and how to correct it?
Associated types in Swift 3 can only be "is-a"-constrained. So your Bar is required to be an Any. Which, by the way, is not much of a constraint ;). In other words, you can remove it.
However, (Self.Bar) -> Void is a function type and you can't constrain an associated type like this.
If you want to define a callback type, you can use a typealias:
protocol Foo
{
associatedtype Bar
typealias Callback = (Self.Bar) -> Void
var currentValue: Bar { get }
func f(callback: Callback) -> Void
}
Using #escaping does not currently work in a typealias (see SR-2316 and its various duplicates). This is a bug that was supposed to have a fix soon (as of August 2016). So you will have to spell it out for now:
func call<T: Foo>(foo: T, callback: #escaping (T.Bar) -> Void) {
DispatchQueue.main.async {
callback(foo.currentValue)
}
}
Update: As Hamish suggested, I filed SR-4967. I'll update this post as soon as there is any news about it.
As already mentioned, function types can't be used as associated types.
Try this instead:
func call<T: Foo>(foo: T, callback: #escaping (T.Bar) -> Void) {
...
}
and using this design you can mix-and-match function types (for the callback arg) for each specific helper function (call in your example) you come up with.