[swift-evolution] [Proposal] Foundation Swift Archival & Serialization

Itai Ferber iferber at apple.com
Thu Mar 16 14:55:41 CDT 2017


I’m going to reply to this thread as a whole — apologies if 
there’s someone’s comment that I’ve missed.

This is something that has come up in internal review, and we’ve 
certainly given it thought. As Zach has already mentioned, the primary 
concern with overloading based on return type is ambiguity.
There are many cases in which Swift’s type system currently does not 
handle ambiguity in the way that you would expect, and it can be very 
surprising. For instance,

```swift
func foo() -> Int { return 42 }
func foo() -> Double { return .pi }
func consumesInt(_ x : Int) { print(x) }

let x = foo() // Ambiguous use of foo()
consumesInt(x) // Even though x is going to be used as an Int
let y: Int = x // Same here
```

`let x = foo() as Int` works now, but it actually didn’t always — 
until a somewhat recent version of Swift AFAICT, the only way to resolve 
the ambiguity was through `let x: Int = foo()`. This has since been 
fixed, but it was very confusing to try to figure out the unambiguous 
way to call it.

Keep in mind that this isn’t an unreasonable thing to want to do:

```swift
struct Foo {
     var x: Int
     init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)

         // Want to process an element before it’s assigned.
         let x = container.decode(forKey: .x) // Ambiguous call

         // Or whatever.
         if x < 0 {
             self.x = x + 100
         else {
             self.x = x * 200
         }
	}
}
```

You can write `let x: Int = container.decode(…)` or `let x = 
container.decode(…) as Int`, but this isn’t always intuitive.
Consider also that the metatype would also be necessary for 
`decode<Value : Codable>(_ type: Value.Type, forKey: Key) -> Value` 
because the return value of that certainly could be ambiguous in many 
cases.

Finally, the metatype arg allows you to express the following 
succinctly: `let v: SuperClass = container.decode(SubClass.self, forKey: 
.v)`.

In the general case (`decode<Value : Codable>`) we would need the 
metatype to avoid ambiguity. It’s not strictly necessary for primitive 
types, but helps in the case of ambiguity, and solves the conceptual 
overhead of "Why do I specify the type sometimes but not others? Why are 
some of these types special? Should I always provide the type? Why 
wouldn’t I?"

Matthew offered `func decode<T>(_ key: Key, as type: T.Type = T.self) 
throws -> T` which looks appealing, but:

1. Doesn’t help resolve the ambiguity either
2. Allows for 3 ways of expressing the same thing (`let x: Int = 
decode(key)`, `let x = decode(key) as Int`, and `let x = decode(key, as: 
Int.self)`)

The cognitive overhead of figuring out all of the ambiguity goes away 
when we’re consistent everywhere.
FWIW, too, I am not convinced that Foundation should add API just 
because 3rd parties will add it. The ambiguity in the general case 
cannot be solved by wrappers, and I would prefer to provide one simple, 
consistent solution; if 3rd parties would like to add wrappers for their 
own sake, then I certainly encourage that.

On 16 Mar 2017, at 11:46, Matthew Johnson via swift-evolution wrote:

> > On Mar 16, 2017, at 1:34 PM, Zach Waldowski via swift-evolution 
> <swift-evolution at swift.org> wrote:
>>
>> On Thu, Mar 16, 2017, at 02:23 PM, Matthew Johnson via 
>> swift-evolution wrote:
>>> I don’t have an example but I don’t see a problem either.  There 
>>> are two options for specifying the return type manually.  We can use 
>>> the signature you used above and use `as` to specify the expected 
>>> type:
>>>
>>> let i = decode(.myKey) as Int
>>
>> The awkwardness of this syntax is exactly what I'm referring to. 
>> Would a beginner know to use "as Int" or ": Int"? Why would they? The 
>> "prettiness" of the simple case doesn't make up for how difficult it 
>> is to understand and fix its failure cases.
>>
>> Any official Swift or Foundation API shouldn't, or shouldn't need to, 
>> make use of "tricky" syntax.
>
> I don’t think this is especially tricky.  Nevertheless, we can avoid 
> requiring this syntax by moving the type argument to the end and 
> providing a default.  But I think return type inference is worth 
> supporting.  It has become widely adopted by the community already in 
> this use case.
>
>>
>>> If we don’t support this in Foundation we will continue to see 3rd 
>>> party libraries that do this.
>>
>> The proposal's been out for less than 24 hours, is it really 
>> productive to already be taking our ball and go home over such a 
>> minor thing?
>
> I don’t think that’s what I’m doing at all.  This is a fantastic 
> proposal.  I’m still working through it and writing up my more 
> detailed thoughts.
>
> That said, as with many (most?) first drafts, there is room for 
> improvement.  I think it’s worth pointing out the syntax that many 
> of us would like to use for decoding and at least considering 
> including it in the proposal.  If the answer is that it’s trivial 
> for those who want to use subscripts to write the wrappers for return 
> type inference and / or subscripts themselves that’s ok.  But it’s 
> a fair topic for discussion and should at least be addressed as an 
> alternative that was rejected for a specific reason.
>
>>
>> Zach Waldowski
>> zach at waldowski.me <mailto:zach at waldowski.me>
>>
>>
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> 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/20170316/4c9ff4e3/attachment.html>


More information about the swift-evolution mailing list