You’re right, my current implementation doesn’t win anything over what you’re 
written -  in fact your technique is basically what I wrote at first.

I was trying to work towards encapsulating the behavior in the encoder/decoder 
so that the automatic init/encode methods could work, so I wanted to introduce 
my first (more manual) attempt and then say, here’s where I’d like to get with 
this.

-Wil


> On Jul 11, 2017, at 10:16 AM, Itai Ferber <[email protected]> wrote:
> 
> Hi Wil,
> 
> Thanks for putting this together! My biggest thought on this is — what does 
> this provide that you can’t already do yourself today?
> Since you have to go through the work to put together default values and 
> override init(from:) and encode(to:) to use them, I’m wondering whether this 
> saves you any work over doing something like the following:
> 
> struct Theme {
>     private static let _defaultName = ""
>     private static let _defaultStyles: [String] = []
> 
>     public let name: String
>     public let styles: [String]
> 
>     private enum CodingKeys : String, CodingKey {
>         case name
>         case styles
>     }
> 
>     public init(from decoder: Decoder) throws {
>         let container = try decoder.container(keyedBy: CodingKeys.self)
>         name = try? decoder.decode(String.self, forKey: .name) ?? 
> Theme._defaultName
>         styles = try? decoder.decode([String.self], forKey: .styles) ?? 
> Theme._defaultStyles
>     }
> 
>     public func encode(to encoder: Encoder) throws {
>         var container = encoder.container(keyedBy: CodingKeys.self)
>         if (name != Theme._defaultName) try container.encode(name, forKey: 
> .name)
>         if (styles != Theme._defaultStyles) try container.encode(styles, 
> forKey: .styles)
>     }
> }
> This reads just as clearly to me as the defaults: variation while having the 
> added benefit of low complexity and stronger type safety (as there’s no 
> as!-casting down from Any, which could fail).
> 
> Thoughts?
> 
> — Itai
> 
> On 10 Jul 2017, at 17:16, William Shipley via swift-evolution wrote:
> 
> Automatic substitution / removal of default values is very useful when 
> reading or writing a file, respectively, and should be supported by the 
> <Codable> family of protocols and objects:
> 
> • When reading, swapping in a default value for missing or corrupted values 
> makes it so hand-created or third-party-created files don’t have to write 
> every single value to make a valid file, and allows slightly corrupted files 
> to auto-repair (or get close, and let the user fix up any data that needs it 
> after) rather than completely fail to load. (Repairing on read creates a 
> virtuous cycle with user-created files, as the user will get _some_ feedback 
> on her input even if she’s messed up, for example, the type of one of the 
> properties.)
> 
> • When writing, providing a default value allows the container to skip keys 
> that don’t contain useful information. This can dramatically reduce file 
> sizes, but I think its other advantages are bigger wins: just like having 
> less source code makes a program easier to debug, having less “data code” 
> makes files easier to work with in every way — they’re easier to see 
> differences in, easier to determine corruption in, easier to edit by hand, 
> and easier to learn from.
> 
> 
> My first pass attempt at adding defaults to Codable looks like this:
> 
> 
> public class ReferencePieceFromModel : Codable {
> 
> // MARK: properties
> public let name: String = ""
> public let styles: [String] = []
> 
> 
> // MARK: <Codable>
> public required init(from decoder: Decoder) throws {
> let container = try decoder.container(keyedBy: CodingKeys.self)
> 
> self.name = container.decode(String.self, forKey: .name, defaults: type(of: 
> self).defaultsByCodingKey)
> self.styles = container.decode([String].self, forKey: .styles, defaults: 
> type(of: self).defaultsByCodingKey)
> }
> public func encode(to encoder: Encoder) throws {
> var container = encoder.container(keyedBy: CodingKeys.self)
> 
> try container.encode(name, forKey: .name, defaults: type(of: 
> self).defaultsByCodingKey)
> try container.encode(styles, forKey: .styles, defaults: type(of: 
> self).defaultsByCodingKey)
> }
> private static let defaultsByCodingKey: [CodingKeys : Any] = [
> .name : "",
> .styles : [String]()
> ]
> 
> 
> // MARK: private
> private enum CodingKeys : String, CodingKey {
> case name
> case styles
> }
> }
> 
> With just a couple additions to the Swift libraries:
> 
> extension KeyedDecodingContainer where Key : Hashable {
> func decode<T>(_ type: T.Type, forKey key: Key, defaults: [Key : Any]) -> T 
> where T : Decodable {
> if let typedValueOptional = try? decodeIfPresent(T.self, forKey: key), let 
> typedValue = typedValueOptional {
> return typedValue
> } else {
> return defaults[key] as! T
> }
> }
> }
> 
> extension KeyedEncodingContainer where Key : Hashable {
> mutating func encode<T>(_ value: T, forKey key: Key, defaults: [Key : Any]) 
> throws where T : Encodable & Equatable {
> if value != (defaults[key] as! T) {
> try encode(value, forKey: key)
> }
> }
> 
> mutating func encode<T>(_ value: [T], forKey key: Key, defaults: [Key : Any]) 
> throws where T : Encodable & Equatable { // I AM SO SORRY THIS IS ALL I COULD 
> FIGURE OUT TO MAKE [String] WORK!
> if value != (defaults[key] as! [T]) {
> try encode(value, forKey: key)
> }
> }
> }
> 
> 
> (Note the horrible hack on KeyedEncodingContainer where I had to special-case 
> arrays of <Equatable>s, I guess because the compiler doesn’t know an array of 
> <Equatable>s is Equatable itself?)
> 
> 
> Problems with this technique I’ve identified are:
> 
> ⑴ It doesn’t allow one to add defaults without manually writing the 
> init(from:) and encode(to:), ugh.
> ⑵ The programmer has to add 'type(of: self).defaultsByCodingKey’ to every 
> call, ugh.
> 
> Both of these could possibly be worked around if we could add an optional 
> method to the <Codable> protocol, that would look something like:
> 
> public static func default<Key>(keyedBy type: Key.Type, key: Key) -> Any? 
> where Key : CodingKey
> 
> (the above line isn’t tested and doubtlessly won’t work as typed and has tons 
> of think-os.)
> 
> This would get called by KeyedEncodingContainers and KeyedDecodingContainers 
> only for keys that are Hashable (which I think is all keys, but you can stick 
> un-keyed sub-things in Keyed containers and obviously those can’t have 
> defaults just for them) and the container would be asked to do the comparison 
> itself, with ‘==‘.
> 
> Something I haven’t tried to address here is what to do if values are NOT 
> <Equatable> — then of course ‘==‘ won’t work. One approach to this would be 
> to provide a way for the static func above to return ‘Hey, I don’t have 
> anything meaningful for you for this particular property, because it’s not 
> Equatable.’ This could be as simple as returning ‘nil’, which would also be a 
> decent way to say, “This property has no meaningful default” which is also 
> needed.
> 
> Alternatively, one could imagine adding TWO callbacks in the <Codable> for 
> this kind of case, which are essentially *WAVES HANDS*:
> 
> public static func isThisValueTheDefault(_ value: Any, forKey key: Self.Key) 
> throws -> Any?
> public static func defaultValue<Key>(keyedBy type: Key.Type, key: Key) -> 
> Any? where Key : CodingKey
> 
> These might also need a 'keyedBy type: Key.Type’ parameter — to be honest I 
> haven’t messed with different key spaces so I’m not sure how they work. Also 
> I’m not the best at generics yet. (At this point I’m not even sure if 
> protocols can contain ‘class’ functions, so maybe none of this would work.)
> 
> Another advantage to the two-method approach (besides not requiring the 
> values to be < Equatable >) is that it allows one to provide defaults for 
> floating values, which can often be changed just by floating-point error by 
> like 0.00000000001 and then end up registering false changes. In the 
> isValueDefault(…) the programmer could implement a comparison with a ‘slop’ 
> so if the encoder were about to write 0.000000000001 and the default were 0 
> nothing would be written.
> 
> 
> -Wil
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <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