> 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