> 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