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

Itai Ferber iferber at apple.com
Thu Mar 16 16:36:33 CDT 2017



On 16 Mar 2017, at 13:27, David Hart wrote:

> > On 16 Mar 2017, at 20:55, Itai Ferber via swift-evolution 
> <swift-evolution at swift.org> wrote:
>>
>> 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,
>>
>> 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:
>>
>> 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.
>>
> That’s where I disagree. Let me try to prove my point:
>
> You bring up the example of having to store the decoded value in a 
> variable before setting it to a typed property. But its also not 
> unreasonable to want to do the same thing when encoding the value, 
> possibly storing it into a different type. If we follow that argument, 
> its also not very intuitive to have to do
>
> container.encode(x as Double, forKey: .x).
>
> Wouldn’t that be an argument to have an API like this:
>
> func encode<T>(_ value: Data?, forKey key: Key, as type: T.Type) 
> throws
I don’t agree that these are equivalent cases.

Here, for an `as` cast to be valid, the type of `x` must be an 
existential (I’m guessing `Any`).
The original `container.encode(x, forKey: .x)` call is not ambiguous 
because `x` has no type, but rather because the type of `x` does not 
match any of the overloads. You would get the same error as if you wrote

```swift
struct NonCodableFoo {}
let x = NonCodableFoo()
container.encode(x, forKey: .x)
```

You have to convert the type to something that fits one of the 
overloads.

On encode, there cannot be any true ambiguity because it’s not 
possible to satisfy more than one of these concrete overloads. You 
cannot have a thing with a type which would satisfy both, say, `Int` and 
`Double`.

> I would argue that type inference is a core feature in Swift and that 
> we should embrace it. I believe that in most cases the return value of 
> encode will be stored into a typed property and type inference will do 
> the right thing. In the few cases where the type has to be enforced, 
> the patterns you mention above are not weird syntax; they are used and 
> useful all over Swift:
Sure, but I think these cases are not equivalent.

> let cgFloat: CGFloat = 42
42 has a default value of `Int`, but since `CGFloat` is 
`ExpressibleByIntLiteral`, this becomes the equivalent of writing `let 
cfFloat = CGFloat(42)`, which would not be ambiguous without the 
`CGFloat()`; you would just get an `Int`.

With `let x = container.decode(forKey: .x)`, has _has no type_ unless 
otherwise specified.

> let pi = 3.14159265359 as Float
Same here, but with `Double` instead of `Int`, and `Float` instead of 
`CGFloat`…

> let person = factory.get<Person>() // potential feature in Generics 
> Manifesto
This isn’t type inference. This is type specification, which is 
exactly what we are trying to do. At the moment, explicit type 
specification has a different syntax: passing a metatype as an argument.

If this feature were available, this is what we would use.

> The way I think about it is that the type argument is already there as 
> a generic parameter. Adding an extra argument that needs to be 
> explicitly given on every single call feels like unneeded verbosity to 
> me.
For consideration: why does `let person = factory.get<Person>()` seem 
reasonable, but `let person = factory.get(Person.self)` does not?

>> 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:
>>
>> Doesn’t help resolve the ambiguity either
>> 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.
>>
> Agreed. Foundation should not add API just because 3rd parties do it. 
> But 3rd parties should not be dismissed entirely nonetheless. They are 
> a good breeding ground for ideas to spawn and shape Swift in 
> interesting ways.
True. I don’t want to seem dismissive of third parties here. The 
limitations that we operate within are more conservative than those 
third parties tend to work within; we would prefer to offer consistency 
over concision.

>> 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 <mailto: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 <mailto: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 
>> <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/9698733d/attachment.html>


More information about the swift-evolution mailing list