> On 27 May 2017, at 08:59, Gwendal Roué <gwendal.r...@gmail.com> wrote:
> 
> 
>> Le 26 mai 2017 à 22:30, David Hart <da...@hartbit.com> a écrit :
>> 
>> Can you explain what’s the problem with Issue 2?
> 
> The problem was me, I guess :-) Of course nobody knows the list of keys, but 
> the type itself. It's a matter of injecting an encoder. I'll do that.
> 
> Thanks also Itai for your answer.
> 
>> Am I correct in suggesting that Issue 1 is more of a missing generics 
>> feature than a problem with SE-0166/0167?
> 
> There are two ways to see such issue: either a language is not ready, either 
> a library isn't designed for its language. :-) But this is not the case here. 
> Again, Itai has the correct answer:
> 
>   if T.self is DatabaseValueConvertible.Type {
>       let databaseValue: DatabaseValue = row.value(named: key.stringValue)
>       return (T.self as! 
> DataBaseValueConvertible.Type).fromDatabaseValue(databaseValue) as! T
>   } else { … }

I didn’t know that was possible either! Really cool. Even better:

  if let databaseValueType = T.self as? DatabaseValueConvertible.Type {
      let databaseValue: DatabaseValue = row.value(named: key.stringValue)
      return databaseValueType.fromDatabaseValue(databaseValue) as! T
  } else { … }

> This is the way to test a type against a protocol - I didn't know this was 
> even possible!
> 
> Thanks a lot, Itai and David: SE-0166 and SE-0167 are delivering their 
> promises, and GRDB will make good use from them :-)
> Gwendal
> 
>> 
>> David.
>> 
>>> On 26 May 2017, at 16:26, Gwendal Roué via swift-evolution 
>>> <swift-evolution@swift.org> wrote:
>>> 
>>> Hello,
>>> 
>>> I want to provide real-life feedback for the Swift Archival & Serialization 
>>> (SE-0166) and Swift Encoders (SE-0167) proposals that currently ship in 
>>> Swift 4 snapshots.
>>> 
>>> The context: I'm the author of GRDB.swift [1], a SQLite library that, among 
>>> other goals, aims at easing the conversion between database rows and custom 
>>> models (structs and class hierarchies):
>>> 
>>>     // Sample code
>>>     let arthur = Player(name: "Arthur", score: 100)
>>>     try arthur.insert(db)
>>>     print(arthur.id)
>>>     
>>>     let topPlayers = try Player
>>>             .order(Column("score").desc)
>>>             .limit(10)
>>>             .fetchAll(db) // [Player]
>>> 
>>> Due to the lack of any introspection in Swift, GRDB currently wants you to 
>>> perform explicit conversion:
>>> 
>>>     struct Player {
>>>             var id: Int64?
>>>             let name: String
>>>             let score: Int
>>>     }
>>>     
>>>     extension Player : RowConvertible {
>>>             init(row: Row) {
>>>                     id = row.value(named: "id")
>>>                     name = row.value(named: "name")
>>>                     score = row.value(named: "score")
>>>             }
>>>     }
>>>     
>>>     extension Player : TableMapping, MutablePersistable {
>>>             static let databaseTableName = "player"
>>>             var persistentDictionary: [String: DatabaseValueConvertible?] {
>>>                     return ["id": id, "name": name, "score: score]
>>>             }
>>>     }
>>> 
>>> That's enough, but that's still too much.
>>> 
>>> SE-0166 and SE-0167 sound like the promise that some boilerplate code could 
>>> be automatically generated.
>>> 
>>> Along with JSONDecoder and PListDecoder, let's introduce 
>>> DatabaseRowDecoder! The current state of the work is at 
>>> https://github.com/groue/GRDB.swift/tree/Swift4
>>> 
>>> 
>>> At first, it's very satisfying. Decodable keeps some of it promises:
>>> 
>>>     struct Player : RowConvertible, Decodable {
>>>             static let databaseTableName = "player"
>>>             var id: Int64?
>>>             let name: String
>>>             let score: Int
>>>     }
>>>     
>>>     // Yeah, no more extra code necessary for this to work!
>>>     let topPlayers = try Player
>>>             .order(Column("score").desc)
>>>             .limit(10)
>>>             .fetchAll(db)
>>> 
>>> But there are some issues.
>>> 
>>> 
>>> ### Issue 1: SE-0166/0167 merge the concepts of keyed objects and values
>>> 
>>> This is a problem. Let's take this example:
>>> 
>>>     enum Color: Int, Codable {
>>>             case blue, green, red
>>>     }
>>>     
>>>     struct Flower : RowConvertible, Decodable {
>>>             let name: String
>>>             let color: Color
>>>     }
>>>     
>>> The way to decode a color comes from KeyedDecodingContainerProtocol:
>>> 
>>>     protocol KeyedDecodingContainerProtocol {
>>>             func decode<T>(_ type: T.Type, forKey key: Key) throws -> T 
>>> where T : Decodable
>>>             func decodeIfPresent<T>(_ type: T.Type, forKey key: Key) throws 
>>> -> T? where T : Decodable
>>>     }
>>> 
>>> But the ability to decode a Color from a database row comes from the 
>>> DatabaseValueConvertible, which I can't invoke since I can't test if type T 
>>> conforms to this protocol:
>>> 
>>>     struct RowKeyedDecodingContainer<Key: CodingKey>: 
>>> KeyedDecodingContainerProtocol {
>>>             let row: Row
>>>             
>>>             // Not OK: no support for values
>>>             func decode<T>(_ type: T.Type, forKey key: Key) throws -> T 
>>> where T : Decodable {
>>>                     if <T conforms to DatabaseValueConvertible>  {
>>>                             let databaseValue: DatabaseValue = 
>>> row.value(named: key.stringValue)
>>>                             return T.fromDatabaseValue(databaseValue) 
>>>                     } else { ... }
>>>             }
>>>     }
>>> 
>>> So the current state of the Codable library disallow GRDB from supporting 
>>> value properties which are not the trivial Int, Int32, etc. Of course, GRDB 
>>> itself makes it possible, with explicit user code. But we're talking about 
>>> removing boilerplate and relying on the code generation that Codable is 
>>> blessed with, here. We're talking about sharing the immense privilege that 
>>> Codable is blessed with.
>>> 
>>> However, if I can't decode **values**, I can still decode **complex keyed 
>>> objects** (in this case the row behaves like a hierarchical container - a 
>>> concept already present in GRDB and allows it to consume complex rows like 
>>> results of joins):
>>> 
>>>     struct Book : RowConvertible, Decodable { ... }
>>>     struct Author : RowConvertible, Decodable { ... }
>>>     struct Pair : RowConvertible, Decodable {
>>>             let book: Book
>>>             let author: Author
>>>     }
>>>     
>>>     struct RowKeyedDecodingContainer<Key: CodingKey>: 
>>> KeyedDecodingContainerProtocol {
>>>             let row: Row
>>>     
>>>             // OK, support for other decodable objects
>>>             func decode<T>(_ type: T.Type, forKey key: Key) throws -> T 
>>> where T : Decodable {
>>>                     if let scopedRow = row.scoped(on: key.stringValue) {
>>>                             return try T(from: RowDecoder(row: scopedRow, 
>>> codingPath: codingPath + [key]))
>>>                     } else {
>>>                             throw DecodingError.keyNotFound(key, 
>>> DecodingError.Context(codingPath: codingPath, debugDescription: "missing 
>>> scope"))
>>>                     }
>>>             }
>>>     }
>>> 
>>> Yet this use case is much less frequent.
>>> 
>>> Is it possible to workaround this problem? Did I miss something?
>>> 
>>> 
>>> ### Issue 2: Encodable can not be used to derive other persistence 
>>> strategies.
>>> 
>>> The use case here is to derive other types of persistence from Encodable 
>>> (and take profit from the compiler-generated code).
>>> 
>>> For example, I want to write:
>>> 
>>>     extension MutablePersistable where Self: Encodable {
>>>             // Required by MutablePersistable
>>>             var persistentDictionary: [String: DatabaseValueConvertible?] {
>>>                     return ...
>>>             }
>>>     }
>>> 
>>> If it were possible, we could get the full picture, with all boilerplate 
>>> removed:
>>>     
>>>     // Wouldn't it be great?
>>>     struct Player : RowConvertible, MutablePersistable, Codable {
>>>             static let databaseTableName = "player"
>>>             var id: Int64?
>>>             let name: String
>>>             let score: Int
>>>     }
>>> 
>>>     let arthur = Player(name: "Arthur", score: 100)
>>>     try arthur.insert(db)
>>>     print(arthur.id)
>>> 
>>>     let topPlayers = try Player
>>>             .order(Column("score").desc)
>>>             .limit(10)
>>>             .fetchAll(db) // [Player]
>>> 
>>> Unfortunately, it's impossible: the Encodable protocol doesn't allow 
>>> iteration on the coding keys. I can't generate anything useful.
>>> 
>>> Again, is it possible to workaround this problem? Did I miss something?
>>> 
>>> 
>>> Thanks for your attention,
>>> Gwendal Roué
>>> 
>>> [1] https://github.com/groue/GRDB.swift
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
> 

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to