How to express this `map` function using a protocol - swift

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.

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.

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

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.

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

Protocol conformance of system classes using more generic types

To mock objects in Swift for test, I generally follow a pattern of authoring a Protocol describing the behaviour of the object I'd like, and then using Cuckoo to generate mocks for it for test.
Usually, these protocols map directly onto existing types, and this works fine, until I need to make the existing type work with my new protocol types.
public typealias RequestCompletionHandler = (Request, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler? { get }
}
extension VNRequest: Request {}
Here, VNRequest already has a member called completionHandler that returns the following type:
public typealias VNRequestCompletionHandler = (VNRequest, Error?) -> Swift.Void
Technically, all of these types should match up, but obviously it's not a very easy scenario for the type solver to solve, so the compiler isn't too cheerful about it.
At first I thought I'd be able to refer to the original completionBlock implementation by doing the following:
extension VNRequest: Request {
public var completionHandler: RequestCompletionHandler? {
return (self as VNRequest).completionHandler
}
}
But it's not too happy about that either.
Any advice about how best to do this? I've thought about using a different name in the protocol (e.g: completionBlock_ or completionBlock$), which works, but it's a bit scrappy.
The problem occurs due to the fact that Swift is covariant in respect to closure return type, and contra-variant in respect to its arguments. Which means that (VNRequest, Error?) -> Void can't be used where (Request, Error?) -> Void is needed (the other way around is possible).
You can solve your problem by using an associated type in the Request protocol:
public protocol Request {
associatedtype RequestType = Self
var results: [Any]? { get }
var completionHandler: ((RequestType, Error?) -> Void)? { get }
}
The above protocol definition will make the VNRequest class compile, as the compiler will detect the match for RequestType.
The downside, though, is that protocols with associated types have some limitations regarding where they can be used, and also passing them as function arguments will require some where clauses to make them work.
Another alternative would be to use Self as a parameter to the completion handler:
public typealias RequestCompletionHandler<T> = (T, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler<Self>? { get }
}
This will also solve the conformance issue, but also comes with some constraints: VNRequest must be final, the functions using Request must be generic:
func send<R: Request>(_ request: R)

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.