> Le 26 mai 2017 à 22:30, David Hart <[email protected]> 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 { ... }

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 
>> <[email protected]> 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
>> [email protected]
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to