thoughtbot/Argo

Swift 3.2 / 4 Decodable name conflict

jshier opened this issue · 7 comments

The new Codable protocols are available in both Swift 3.2 and 4, and include a Decodable protocol. Argo may want to look into a rename, perhaps as a larger reevaluation of the future of the library. Personally, I still think there's a place, perhaps with enhancements to Codable and the JSON type, but it's up to you guys.

Yeah, I agree. I've been thinking about this for the past few days and have been having some internal conversations about it with people. I think that what we'll probably end up doing is renaming some stuff to reposition Argo as being more than a JSON parser, and reveal more of its power as a general applicative parser for moving from untyped data structures to strongly typed ones. Renaming JSON to Value (which will also bring us even more in line with Aeson) is part of that, and finding a new name for Decodable to avoid a known conflict is another. I'm hoping to have some time today/this week to spike something out against Swift 4. I'll open a PR for discussion and link to it from here when I do.

For what it's worth as a consumer of the existing library, prefixing references with Argo. to satisfy the compiler is a lot less painful than having to re-learn and re-write everything.

Not that I have to say this (because I know how @gfontenot rolls), but as long as the major version bumps to signal the API breakage then I'm fine with any renaming/repurposing/etc…

(Oh, and some kind of explanation for the changes and their benefits would be appreciated for those following along on the sidelines. I figured {Co,Deco}dable would put the nail in Argo's coffin for me, and am open to learning how else it might come in handy…)

prefixing references with Argo. to satisfy the compiler is a lot less painful than having to re-learn and re-write everything.

I wish I could ship compiler fixits. If I can, and someone knows how to do that, please let me know because I will add them to save people of this pain.

This would definitely be a major version bump, and would be largely cosmetic (at least initially). I'd still want to make it as painless as possible, and it's possible that we could ship some public typealiases for the big ones (typalias Decodable = ???, typealias JSON = Value) to minimize the pain and let users migrate over time.

Also, I fully expect a large portion of users to migrate away from Argo (and all other JSON parsers) for the default stuff. Including us, actually. But I do think there are still interesting use-cases for Argo that are more cumbersome with the {Co,Deco}dable stuff and I'm super interested in highlighting these use cases (the Router stuff from the Kickstarter app comes to mind)

I wish I could ship compiler fixits. If I can, and someone knows how to do that, please let me know because I will add them to save people of this pain.

This seems like a chicken & egg problem. How would the compiler know which {Co,Deco}dable to supply the fixit for? Also, I think you might need to deprecate the names in the process to invoke the fixits.

Anyway, Xcode's search & replace tool isn't half-bad for this sort of thing in my experience. Then again, I'm talking a dozen or so models at the most, so I can only speak for myself on that front.

This would definitely be a major version bump, and would be largely cosmetic (at least initially). I'd still want to make it as painless as possible, and it's possible that we could ship some public typealiases for the big ones (typalias Decodable = ???, typealias JSON = Value) to minimize the pain and let users migrate over time.

Yeah, typealiases will definitely do the trick, and will allow you to possibly supply the fixits you're after at that time.

[…] But I do think there are still interesting use-cases for Argo that are more cumbersome with the {Co,Deco}dable stuff and I'm super interested in highlighting these use cases (the Router stuff from the Kickstarter app comes to mind)

OK, so that is really nifty, and is definitely worth documenting/highlighting/whatever. It's been on my todo list for a while to read through that stuff after watching the recent Kickstarter episodes on http://talk.objc.io.

mdiep commented

This seems like a chicken & egg problem. How would the compiler know which {Co,Deco}dable to supply the fixit for? Also, I think you might need to deprecate the names in the process to invoke the fixits.

It seems like a sufficiently smart compiler could suss this out in most cases by determining which you conformed to.

Having now actually used Codable (and argued about it on swift-users), I feel pretty strongly there's a still a place for a library like Argo. I'm not quite sure what @gfontenot had in mind when he said "a general applicative parser for moving from untyped data structures to strongly typed ones", but it sounds a lot like what I'd want.

A pattern I've adopted for using Decodable for JSON responses is what I call RawDecodable. Essentially, in order to keep the compiler-generated Decodable implementation and not have to mess with the rather terrible API for manually implementing it, I (and others online) have started using RawDecodable to parse real types from other Decodable types that exactly match whatever terrible JSON you have to parse. It's a simple protocol:

protocol RawDecodable {
    associatedtype RawValue: Decodable
    init(_ rawValue: RawValue) throws
}

Which I can turn this:

struct RawEvent: Decodable {
    let locs: [RawLocation]
    let sTime: Double?
    let eTime: Double?
    let title: String
    let html: String
}

into this:

public struct Event: Codable {
    public let locations: [Location]
    public let title: String
    public let rawDescription: String
    public let interval: EventInterval?
}

using this initializer:

extension Event: RawDecodable {
    init(_ rawValue: RawEvent) throws {
        locations = rawValue.locs.flatMap(Location.init)
        title = rawValue.title
        rawDescription = rawValue.html
        interval = EventInterval(startDate: rawValue.sTime, endDate: rawValue.eTime)
    }
}

without writing any parsing code myself. However, this approach falls down when it comes to that initializer and parsing the values into the types I want. I think Argo could provide a much more elegant way to perform those transforms.

So moving forward, there may be a few things Argo can do:

  1. Keep the JSON type, though a rename might be in order. It's may still be useful to some, especially since JSONDecoder only works with Data and can't parse [String: Any] values.
  2. Perhaps add an ArgoJSONDecoder/Encoder implementations, which can encode and decode from that type.
  3. Transform operators, similar to what's already available, but which using throwing functions for error handling.
  4. Or perhaps provide a single error type / enum to help with the transform process, similar to how Argo works currently.
  5. Or some other wholesale reimagining of Argo that words with Codable.

I'm going to go ahead and close this due to inactivity. I'd still like to push ahead with the larger rename, but need to find the time to do that.