spring-media / PiedPiper

A small set of classes and functions to make easy use of Futures, Promises and async computation in general. All written in Swift for iOS 10+, WatchOS 3, tvOS and Mac OS X apps.

Repository from Github https://github.comspring-media/PiedPiperRepository from Github https://github.comspring-media/PiedPiper

Think how to make Future generic on the ErrorType with no impact on Carlos API

vittoriom opened this issue · comments

From @vittoriom on April 9, 2016 13:43

Copied from original issue: spring-media/Carlos#152

From @bkase on April 13, 2016 5:55

Hi!

I have some ideas:
How about supplying the Future rather than the specific output type in a CacheLevel? Then you still only need two associatedtypes.

protocol CacheLevel {
   associatedtype KeyType
   associatedtype AsyncOutputType

   func get(key: KeyType) -> Self.AsyncOutputType
   func set(value: Self.AsyncOutputType.ValueType, forKey key: KeyType)
   /* ... */
}

struct MemoryCacheLevel<..>: CacheLevel {
  typealias AsyncOutputType = Future<UIImage, NSCacheError>
  /* ... */
}

For composition problems you could introduce a =!>> operator to transform errors like:

let cache1: BasicCache<String, Future<Int, ErrorA>>
let cache2: BasicCache<String, Future<Int, ErrorB>>
let errorTransformA: ErrorTransform<ErrorA, CompositeError>
let errorTransformB: ErrorTransform<ErrorB, CompositeError>

let cacheLayered: BasicCache<String, Future<Int, CompositeError>> =
      ((cache1 =!>> errorTransformA) >>> (cache2 =!>> errorTransformB)).normalize()

For simplicity -- for the builtin caches (DiskCacheLevel, NetworkFetcher), you could use a IOError to ease composition.

This has a slight impact on the Carlos API, but I think it's worth it for better type-safety.

Hi @bkase , thanks for your suggestion!
I'm a bit confused though how I could constrain the AsyncOutputType associatedtype of CacheLevel to be a Future. I actually need that to be the case otherwise get could return whatever type and when composing CacheLevels I can't compose the Futures like I do now.
I think in Swift 2.2 this is not possible yet, maybe in Swift 3?

What I was thinking is maybe I can add a generic error type to Futures and Results and then in Carlos APIs they will just be "promoted" to the ErrorType common supertype. This would still be a bit suboptimal but doesn't impact PiedPiper, actually it improves it.

From @bkase on April 13, 2016 10:11

Oh good point -- didn't think of that; yeah that seems not possible in the current version of Swift.

What I was thinking is maybe I can add a generic error type to Futures and Results and then in Carlos APIs they will just be "promoted" to the ErrorType common supertype. This would still be a bit suboptimal but doesn't impact PiedPiper, actually it improves it.

Nice -- makes sense

From @bkase on April 14, 2016 4:32

Not saying this is necessarily a good idea, but you could sort of constrain the associatedtype to be a Future.

Start with the same definition of a cache as above:

protocol CacheLevel {
   associatedtype KeyType
   associatedtype AsyncOutputType

   func get(key: KeyType) -> Self.AsyncOutputType
   func set(value: Self.AsyncOutputType.ValueType, forKey key: KeyType)
   /* ... */
}

Implement all the combinators on constrained extensions to the protocol rather than constraining the associated type within the protocol (since we can't)

extension CacheLevel where Self.AsyncOutputType: Async {
   public func compose<A: CacheLevel where A.AsyncOutputType: Async, A.KeyType == KeyType, A.AsyncOutputType.Value == Self.AsyncOutputType.Value>(cache: A) -> BasicCache<A.KeyType, A.AsyncOutputType> { /* ... */ }
}

Unfortunately, you'll still be able to conform to CacheLevel without using an Async for your AsyncOutputType, you just won't be able to use certain operators.

Yeah, as you said this is quite hacky because in the definition of CacheLevel there is no constraint and it's just up to the API (all of them) to make this constraint explicit in their signature.
Also, in your snippet don't forget you can't actually write set(value: Self.AsyncOutputType.ValueType) since AsyncOutputType can be whatever. I think until Swift 3.0 comes with full generics this is just not viable.

From @bkase on April 15, 2016 1:34

Oops, good point