[swift-evolution] Question regarding SE-0167 Swift Encoders

David Hart david at hartbit.com
Wed May 31 08:39:06 CDT 2017


> On 31 May 2017, at 14:36, Gwendal Roué via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Itai,
> 
> (This email is not technical)
> 
> I'm not claiming that SE-0166 should be able to address all archival formats. I've been talking about GRDB to show at least one format that SE-0166 doesn't cover well. And should SE-0166 be fixed to support SQL (in the GRDB fashion), this does not mean that other developers won't eventually fight with SE-0166 until they understand it does not fit their bill.
> 
> But there's something very special with SE-0166:
> 
> It's in the standard library, with all the backward-compatibility constraints that come with such a position.
> 
> IT'S BLESSED WITH CODE GENERATION.

One important future goal I hope we can address in future versions of Swift is powerful macro/meta-programming features. I think like Sourcery (https://github.com/krzysztofzablocki/Sourcery <https://github.com/krzysztofzablocki/Sourcery>) but at compile-time, in the language. Once we have that, I hope we can re-implement SE-0166’s code generation using those meta-programming features into the Standard Library and tear it out of the compiler. If we can achieve that, we will have a strong toolbox for any third-library which have similar needs.

PS: With meta-programming, we could also re-implement the automatic Equatable/Hashable conformances. More magic in the compiler is usually a bad thing I think.

> I don't know if you, Michael LeHew, Tony Parker, and the core team, realize the importance of this insanely great privilege granted to this proposal.
> 
> The lack of introspection and macros in Swift makes SE-0166 immensely attractive for a whole category of libraries.
> 
> When SE-0166 is lacking, should those libs ignore it, and lose CODE GENERATION, which means looking like it's still Swift 3?
> 
> Should those libs claim SE-0166 conformance, and raise runtime errors for invalid inputs (where "invalid" does not mean "invalid data", or "invalid code", but "impossible to fit in SE-0166" <=> "invalid library")?
> 
> I'd like to hear a little better than that :-) GRDB is a library of unusual quality (sorry for the auto-congratulation). Until now, fatal errors thrown by GRDB were always a sign of programmer mistake. Not of defects in the foundations. I wish this would remain true.
> 
> Less caveats and runtime/fatal errors mean less user frustration.
> 
> Less caveats also mean less documentation to write. Ideally, this should be enough: https://github.com/groue/GRDB.swift/tree/Swift4#codable-records <https://github.com/groue/GRDB.swift/tree/Swift4#codable-records>
> 
> Gwendal
> Just a guy that write Swift apps and libraries
> 
> 
>> Le 30 mai 2017 à 20:49, Itai Ferber <iferber at apple.com <mailto:iferber at apple.com>> a écrit :
>> 
>> Hi Gwendal,
>> 
>> There are no stupid questions — everything helps hammer out this API, so I appreciate you taking the time to look at this so deeply.
>> I have to confess that I’m not familiar with this concept, but let’s take a look:
>> 
>> if let valueType = T.self as? DatabaseValueConvertible.Type {
>>     // if column is missing, trigger the "missing key" error or return nil.
>> } else if let complexType = T.self as? RowConvertible.Type {
>>     // if row scope is missing, trigger the "missing key" error or return nil.
>> } else {
>>     // don't know what to do
>>     fatalError("unsupported")
>> }
>> Is it appropriate for a type which is neither DatabaseValueConvertible nor RowConvertible to be decoded with your decoder? If not, then this warrants a preconditionFailure or an error of some sort, right? In this case, that would be valid.
>> 
>> You also mention that "it’s still impossible to support other Codable types" — what do you mean by this? Perhaps there’s a way to accomplish what you’re looking to do.
>> In any case, one option (which is not recommended unless if there are other avenues to solve this by) is to perform a "dry run" decoding. Attempt to decode the type with a dummy decoder to see what container it will need, then prepare your approach and do it again for real. Obviously, this isn’t a clean way to do it if we can find alternatives, but it’s an option.
>> 
>> — Itai
>> 
>> On 29 May 2017, at 4:51, Gwendal Roué via swift-evolution wrote:
>> 
>> Hello,
>> 
>> I have already asked stupid questions about SE-0167 and SE-0166, but this time I hope this is a real one.
>> 
>> According so SE-0166, codable types themselves instantiate a single value decoder, or a keyed container:
>> 
>> public struct Farm : Codable {
>> public init(from decoder: Decoder) throws {
>> let container = try decoder.container(keyedBy: CodingKeys.self
>> ...
>> }
>> }
>> 
>> public enum Animal : Int, Codable {
>> public init(from decoder: Decoder) throws
>> let intValue = try decoder.singleValueContainer().decode(Int.self)
>> ...
>> }
>> }
>> 
>> According to SE-0167, decoder decode non-trivial types in their decode(_:forKey:) and decodeIfPresent(_:forKey:) methods:
>> 
>> 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
>> 
>> My trouble is that the decoder does not know whether the Decodable type will ask for a keyed container, or for a single value container.
>> 
>> Why is it a problem?
>> 
>> In the context of decoding of SQL rows, keys may refer to different things, depending on whether we are decoding a *value*, or a *complex object*:
>> 
>> - for values, keys are column names, as everybody can expect
>> - for complex objects, keys are names of "row scopes". Row scopes are a concept introduced by GRDB.swift and allows a type that knows how to consume `SELECT * FROM table1` to consume as well the results of `SELECT table1.*, table2.* FROM table1 JOIN table2` through a "scope" that presents the row in the shape expected by the consumer (here, only columns from table1).
>> 
>> This is supposed to allow support for types that contain both nested types and values (one of the goals of SE-0166 and SE-0167):
>> 
>> struct Compound : Codable {
>> let someStruct: SomeStruct // object that feeds on the "someStruct" scope
>> let name: String // value that feeds on the "name" column
>> }
>> 
>> The two decoding methods decode(_:forKey:) and decodeIfPresent(_:forKey:) can't be implemented nicely, because they don't know whether the decodable type will ask for a keyed container or a single value container, and thus they don't know whether they should look for the presence of a row scope, or of a column:
>> 
>> A workaround is to perform runtime checks on the GRDB protocols adopted by T, as below. But it's still impossible to support other codable types:
>> 
>> if let valueType = T.self as? DatabaseValueConvertible.Type {
>> // if column is missing, trigger the "missing key" error or return nil.
>> } else if let complexType = T.self as? RowConvertible.Type {
>> // if row scope is missing, trigger the "missing key" error or return nil.
>> } else {
>> // don't know what to do
>> fatalError("unsupported")
>> }
>> 
>> Do you have any advice?
>> 
>> Gwendal Roué
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170531/0a4d4bfe/attachment.html>


More information about the swift-evolution mailing list