Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-09-05 Thread Jordan Rose via swift-evolution



> On Sep 2, 2017, at 00:30, Fabian Ehrentraud  
> wrote:
> 
> 
> Am 03.08.2017 um 02:09 schrieb Jordan Rose via swift-evolution 
> >:
> 
>> David Hart recently asked on Twitter 
>>  if there was a good 
>> way to add Decodable support to somebody else's class. The short answer is 
>> "no, because you don't control all the subclasses", but David already 
>> understood that and wanted to know if there was anything working to mitigate 
>> the problem. So I decided to write up a long email about it instead. (Well, 
>> actually I decided to write a short email and then failed at doing so.)
>> 
>> The Problem
>> 
>> You can add Decodable to someone else's struct today with no problems:
>> 
>> extension Point: Decodable {
>>   enum CodingKeys: String, CodingKey {
>> case x
>> case y
>>   }
>>   public init(from decoder: Decoder) throws {
>> let container = try decoder.container(keyedBy: CodingKeys.self)
>> let x = try container.decode(Double.self, forKey: .x)
>> let y = try container.decode(Double.self, forKey: .y)
>> self.init(x: x, y: y)
>>   }
>> }
>> 
>> But if Point is a (non-final) class, then this gives you a pile of errors:
>> 
>> - init(from:) needs to be 'required' to satisfy a protocol requirement. 
>> 'required' means the initializer can be invoked dynamically on subclasses. 
>> Why is this important? Because someone might write code like this:
>> 
>> func decodeMe() -> Result {
>>   let decoder = getDecoderFromSomewhere()
>>   return Result(from: decoder)
>> }
>> let specialPoint: VerySpecialSubclassOfPoint = decodeMe()
>> 
>> …and the compiler can't stop them, because VerySpecialSubclassOfPoint is a 
>> Point, and Point is Decodable, and therefore VerySpecialSubclassOfPoint is 
>> Decodable. A bit more on this later, but for now let's say that's a sensible 
>> requirement.
>> 
>> - init(from:) also has to be a 'convenience' initializer. That one makes 
>> sense too—if you're outside the module, you can't necessarily see private 
>> properties, and so of course you'll have to call another initializer that 
>> can.
>> 
>> But once it's marked 'convenience' and 'required' we get "'required' 
>> initializer must be declared directly in class 'Point' (not in an 
>> extension)", and that defeats the whole purpose. Why this restriction?
>> 
>> 
>> The Semantic Reason
>> 
>> The initializer is 'required', right? So all subclasses need to have access 
>> to it. But the implementation we provided here might not make sense for all 
>> subclasses—what if VerySpecialSubclassOfPoint doesn't have an 'init(x:y:)' 
>> initializer? Normally, the compiler checks for this situation and makes the 
>> subclass reimplement the 'required' initializer…but that only works if the 
>> 'required' initializers are all known up front. So it can't allow this new 
>> 'required' initializer to go by, because someone might try to call it 
>> dynamically on a subclass. Here's a dynamic version of the code from above:
>> 
>> func decodeDynamic(_ pointType: Point.Type) -> Point {
>>   let decoder = getDecoderFromSomewhere()
>>   return pointType.init(from: decoder)
>> }
>> let specialPoint = decodeDynamic(VerySpecialSubclassOfPoint.self)
>> 
>> 
>> The Implementation Reason
>> 
>> 'required' initializers are like methods: they may require dynamic dispatch. 
>> That means that they get an entry in the class's dynamic dispatch table, 
>> commonly known as its vtable. Unlike Objective-C method tables, vtables 
>> aren't set up to have entries arbitrarily added at run time.
>> 
>> (Aside: This is one of the reasons why non-@objc methods in Swift extensions 
>> can't be overridden; if we ever lift that restriction, it'll be by using a 
>> separate table and a form of dispatch similar to objc_msgSend. I sent a 
>> proposal to swift-evolution about this last year but there wasn't much 
>> interest.)
>> 
>> 
>> The Workaround
>> 
>> Today's answer isn't wonderful, but it does work: write a wrapper struct 
>> that conforms to Decodable instead:
>> 
>> struct DecodedPoint: Decodable {
>>   var value: Point
>>   enum CodingKeys: String, CodingKey {
>> case x
>> case y
>>   }
>>   public init(from decoder: Decoder) throws {
>> let container = try decoder.container(keyedBy: CodingKeys.self)
>> let x = try container.decode(Double.self, forKey: .x)
>> let y = try container.decode(Double.self, forKey: .y)
>> self.value = Point(x: x, y: y)
>>   }
>> }
>> 
>> This doesn't have any of the problems with inheritance, because it only 
>> handles the base class, Point. But it makes everywhere else a little less 
>> convenient—instead of directly encoding or decoding Point, you have to use 
>> the wrapper, and that means no implicitly-generated Codable implementations 
>> either.
>> 
>> I'm not going to spend more time talking about this, but it is the 
>> 

Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-09-02 Thread Fabian Ehrentraud via swift-evolution

Am 03.08.2017 um 02:09 schrieb Jordan Rose via swift-evolution 
>:

David Hart recently asked on 
Twitter if there was a 
good way to add Decodable support to somebody else's class. The short answer is 
"no, because you don't control all the subclasses", but David already 
understood that and wanted to know if there was anything working to mitigate 
the problem. So I decided to write up a long email about it instead. (Well, 
actually I decided to write a short email and then failed at doing so.)

The Problem

You can add Decodable to someone else's struct today with no problems:

extension Point: Decodable {
  enum CodingKeys: String, CodingKey {
case x
case y
  }
  public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let x = try container.decode(Double.self, forKey: .x)
let y = try container.decode(Double.self, forKey: .y)
self.init(x: x, y: y)
  }
}

But if Point is a (non-final) class, then this gives you a pile of errors:

- init(from:) needs to be 'required' to satisfy a protocol requirement. 
'required' means the initializer can be invoked dynamically on subclasses. Why 
is this important? Because someone might write code like this:

func decodeMe() -> Result {
  let decoder = getDecoderFromSomewhere()
  return Result(from: decoder)
}
let specialPoint: VerySpecialSubclassOfPoint = decodeMe()

…and the compiler can't stop them, because VerySpecialSubclassOfPoint is a 
Point, and Point is Decodable, and therefore VerySpecialSubclassOfPoint is 
Decodable. A bit more on this later, but for now let's say that's a sensible 
requirement.

- init(from:) also has to be a 'convenience' initializer. That one makes sense 
too—if you're outside the module, you can't necessarily see private properties, 
and so of course you'll have to call another initializer that can.

But once it's marked 'convenience' and 'required' we get "'required' 
initializer must be declared directly in class 'Point' (not in an extension)", 
and that defeats the whole purpose. Why this restriction?


The Semantic Reason

The initializer is 'required', right? So all subclasses need to have access to 
it. But the implementation we provided here might not make sense for all 
subclasses—what if VerySpecialSubclassOfPoint doesn't have an 'init(x:y:)' 
initializer? Normally, the compiler checks for this situation and makes the 
subclass reimplement the 'required' initializer…but that only works if the 
'required' initializers are all known up front. So it can't allow this new 
'required' initializer to go by, because someone might try to call it 
dynamically on a subclass. Here's a dynamic version of the code from above:

func decodeDynamic(_ pointType: Point.Type) -> Point {
  let decoder = getDecoderFromSomewhere()
  return pointType.init(from: decoder)
}
let specialPoint = decodeDynamic(VerySpecialSubclassOfPoint.self)


The Implementation Reason

'required' initializers are like methods: they may require dynamic dispatch. 
That means that they get an entry in the class's dynamic dispatch table, 
commonly known as its vtable. Unlike Objective-C method tables, vtables aren't 
set up to have entries arbitrarily added at run time.

(Aside: This is one of the reasons why non-@objc methods in Swift extensions 
can't be overridden; if we ever lift that restriction, it'll be by using a 
separate table and a form of dispatch similar to objc_msgSend. I sent a 
proposal to swift-evolution about this last year but there wasn't much 
interest.)


The Workaround

Today's answer isn't wonderful, but it does work: write a wrapper struct that 
conforms to Decodable instead:

struct DecodedPoint: Decodable {
  var value: Point
  enum CodingKeys: String, CodingKey {
case x
case y
  }
  public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let x = try container.decode(Double.self, forKey: .x)
let y = try container.decode(Double.self, forKey: .y)
self.value = Point(x: x, y: y)
  }
}

This doesn't have any of the problems with inheritance, because it only handles 
the base class, Point. But it makes everywhere else a little less 
convenient—instead of directly encoding or decoding Point, you have to use the 
wrapper, and that means no implicitly-generated Codable implementations either.

I'm not going to spend more time talking about this, but it is the officially 
recommended answer at the moment. You can also just have all your own types 
that contain points manually decode the 'x' and 'y' values and then construct a 
Point from that.


Future Direction: 'required' + 'final'

One language feature we could add to make this work is a 'required' initializer 
that is also 'final'. Because it's 'final', it wouldn't have to go into the 
dynamic dispatch table. But because it's 'final', we have to 

Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-07 Thread Itai Ferber via swift-evolution


> On Aug 6, 2017, at 12:58 PM, Charles Srstka  wrote:
> 
>> On Aug 3, 2017, at 12:05 PM, Itai Ferber via swift-evolution 
>> > wrote:
>> 
>> Thanks for putting these thoughts together, Jordan! Some additional comments 
>> inline.
>> 
>>> On Aug 2, 2017, at 5:08 PM, Jordan Rose >> > wrote:
>>> 
>>> David Hart recently asked on Twitter 
>>>  if there was a 
>>> good way to add Decodable support to somebody else's class. The short 
>>> answer is "no, because you don't control all the subclasses", but David 
>>> already understood that and wanted to know if there was anything working to 
>>> mitigate the problem. So I decided to write up a long email about it 
>>> instead. (Well, actually I decided to write a short email and then failed 
>>> at doing so.)
>>> 
>>> The Problem
>>> 
>>> You can add Decodable to someone else's struct today with no problems:
>>> 
>>> extension Point: Decodable {
>>>   enum CodingKeys: String, CodingKey {
>>> case x
>>> case y
>>>   }
>>>   public init(from decoder: Decoder) throws {
>>> let container = try decoder.container(keyedBy: CodingKeys.self)
>>> let x = try container.decode(Double.self, forKey: .x)
>>> let y = try container.decode(Double.self, forKey: .y)
>>> self.init(x: x, y: y)
>>>   }
>>> }
>>> 
>>> But if Point is a (non-final) class, then this gives you a pile of errors:
>>> 
>>> - init(from:) needs to be 'required' to satisfy a protocol requirement. 
>>> 'required' means the initializer can be invoked dynamically on subclasses. 
>>> Why is this important? Because someone might write code like this:
>>> 
>>> func decodeMe() -> Result {
>>>   let decoder = getDecoderFromSomewhere()
>>>   return Result(from: decoder)
>>> }
>>> let specialPoint: VerySpecialSubclassOfPoint = decodeMe()
>>> 
>>> …and the compiler can't stop them, because VerySpecialSubclassOfPoint is a 
>>> Point, and Point is Decodable, and therefore VerySpecialSubclassOfPoint is 
>>> Decodable. A bit more on this later, but for now let's say that's a 
>>> sensible requirement.
>>> 
>>> - init(from:) also has to be a 'convenience' initializer. That one makes 
>>> sense too—if you're outside the module, you can't necessarily see private 
>>> properties, and so of course you'll have to call another initializer that 
>>> can.
>>> 
>>> But once it's marked 'convenience' and 'required' we get "'required' 
>>> initializer must be declared directly in class 'Point' (not in an 
>>> extension)", and that defeats the whole purpose. Why this restriction?
>>> 
>>> 
>>> The Semantic Reason
>>> 
>>> The initializer is 'required', right? So all subclasses need to have access 
>>> to it. But the implementation we provided here might not make sense for all 
>>> subclasses—what if VerySpecialSubclassOfPoint doesn't have an 'init(x:y:)' 
>>> initializer? Normally, the compiler checks for this situation and makes the 
>>> subclass reimplement the 'required' initializer…but that only works if the 
>>> 'required' initializers are all known up front. So it can't allow this new 
>>> 'required' initializer to go by, because someone might try to call it 
>>> dynamically on a subclass. Here's a dynamic version of the code from above:
>>> 
>>> func decodeDynamic(_ pointType: Point.Type) -> Point {
>>>   let decoder = getDecoderFromSomewhere()
>>>   return pointType.init(from: decoder)
>>> }
>>> let specialPoint = decodeDynamic(VerySpecialSubclassOfPoint.self)
>>> 
>>> 
>>> The Implementation Reason
>>> 
>>> 'required' initializers are like methods: they may require dynamic 
>>> dispatch. That means that they get an entry in the class's dynamic dispatch 
>>> table, commonly known as its vtable. Unlike Objective-C method tables, 
>>> vtables aren't set up to have entries arbitrarily added at run time.
>>> 
>>> (Aside: This is one of the reasons why non-@objc methods in Swift 
>>> extensions can't be overridden; if we ever lift that restriction, it'll be 
>>> by using a separate table and a form of dispatch similar to objc_msgSend. I 
>>> sent a proposal to swift-evolution about this last year but there wasn't 
>>> much interest.)
>>> 
>>> 
>>> The Workaround
>>> 
>>> Today's answer isn't wonderful, but it does work: write a wrapper struct 
>>> that conforms to Decodable instead:
>>> 
>>> struct DecodedPoint: Decodable {
>>>   var value: Point
>>>   enum CodingKeys: String, CodingKey {
>>> case x
>>> case y
>>>   }
>>>   public init(from decoder: Decoder) throws {
>>> let container = try decoder.container(keyedBy: CodingKeys.self)
>>> let x = try container.decode(Double.self, forKey: .x)
>>> let y = try container.decode(Double.self, forKey: .y)
>>> self.value = Point(x: x, y: y)
>>>   }
>>> }
>>> 
>>> This doesn't have any of the problems with inheritance, because it only 
>>> handles the 

Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-06 Thread Charles Srstka via swift-evolution
> On Aug 3, 2017, at 12:05 PM, Itai Ferber via swift-evolution 
>  wrote:
> 
> Thanks for putting these thoughts together, Jordan! Some additional comments 
> inline.
> 
>> On Aug 2, 2017, at 5:08 PM, Jordan Rose > > wrote:
>> 
>> David Hart recently asked on Twitter 
>>  if there was a good 
>> way to add Decodable support to somebody else's class. The short answer is 
>> "no, because you don't control all the subclasses", but David already 
>> understood that and wanted to know if there was anything working to mitigate 
>> the problem. So I decided to write up a long email about it instead. (Well, 
>> actually I decided to write a short email and then failed at doing so.)
>> 
>> The Problem
>> 
>> You can add Decodable to someone else's struct today with no problems:
>> 
>> extension Point: Decodable {
>>   enum CodingKeys: String, CodingKey {
>> case x
>> case y
>>   }
>>   public init(from decoder: Decoder) throws {
>> let container = try decoder.container(keyedBy: CodingKeys.self)
>> let x = try container.decode(Double.self, forKey: .x)
>> let y = try container.decode(Double.self, forKey: .y)
>> self.init(x: x, y: y)
>>   }
>> }
>> 
>> But if Point is a (non-final) class, then this gives you a pile of errors:
>> 
>> - init(from:) needs to be 'required' to satisfy a protocol requirement. 
>> 'required' means the initializer can be invoked dynamically on subclasses. 
>> Why is this important? Because someone might write code like this:
>> 
>> func decodeMe() -> Result {
>>   let decoder = getDecoderFromSomewhere()
>>   return Result(from: decoder)
>> }
>> let specialPoint: VerySpecialSubclassOfPoint = decodeMe()
>> 
>> …and the compiler can't stop them, because VerySpecialSubclassOfPoint is a 
>> Point, and Point is Decodable, and therefore VerySpecialSubclassOfPoint is 
>> Decodable. A bit more on this later, but for now let's say that's a sensible 
>> requirement.
>> 
>> - init(from:) also has to be a 'convenience' initializer. That one makes 
>> sense too—if you're outside the module, you can't necessarily see private 
>> properties, and so of course you'll have to call another initializer that 
>> can.
>> 
>> But once it's marked 'convenience' and 'required' we get "'required' 
>> initializer must be declared directly in class 'Point' (not in an 
>> extension)", and that defeats the whole purpose. Why this restriction?
>> 
>> 
>> The Semantic Reason
>> 
>> The initializer is 'required', right? So all subclasses need to have access 
>> to it. But the implementation we provided here might not make sense for all 
>> subclasses—what if VerySpecialSubclassOfPoint doesn't have an 'init(x:y:)' 
>> initializer? Normally, the compiler checks for this situation and makes the 
>> subclass reimplement the 'required' initializer…but that only works if the 
>> 'required' initializers are all known up front. So it can't allow this new 
>> 'required' initializer to go by, because someone might try to call it 
>> dynamically on a subclass. Here's a dynamic version of the code from above:
>> 
>> func decodeDynamic(_ pointType: Point.Type) -> Point {
>>   let decoder = getDecoderFromSomewhere()
>>   return pointType.init(from: decoder)
>> }
>> let specialPoint = decodeDynamic(VerySpecialSubclassOfPoint.self)
>> 
>> 
>> The Implementation Reason
>> 
>> 'required' initializers are like methods: they may require dynamic dispatch. 
>> That means that they get an entry in the class's dynamic dispatch table, 
>> commonly known as its vtable. Unlike Objective-C method tables, vtables 
>> aren't set up to have entries arbitrarily added at run time.
>> 
>> (Aside: This is one of the reasons why non-@objc methods in Swift extensions 
>> can't be overridden; if we ever lift that restriction, it'll be by using a 
>> separate table and a form of dispatch similar to objc_msgSend. I sent a 
>> proposal to swift-evolution about this last year but there wasn't much 
>> interest.)
>> 
>> 
>> The Workaround
>> 
>> Today's answer isn't wonderful, but it does work: write a wrapper struct 
>> that conforms to Decodable instead:
>> 
>> struct DecodedPoint: Decodable {
>>   var value: Point
>>   enum CodingKeys: String, CodingKey {
>> case x
>> case y
>>   }
>>   public init(from decoder: Decoder) throws {
>> let container = try decoder.container(keyedBy: CodingKeys.self)
>> let x = try container.decode(Double.self, forKey: .x)
>> let y = try container.decode(Double.self, forKey: .y)
>> self.value = Point(x: x, y: y)
>>   }
>> }
>> 
>> This doesn't have any of the problems with inheritance, because it only 
>> handles the base class, Point. But it makes everywhere else a little less 
>> convenient—instead of directly encoding or decoding Point, you have to use 
>> the wrapper, and that means no implicitly-generated Codable implementations 
>> either.
>> 
>> 

Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-04 Thread Itai Ferber via swift-evolution
To clarify a bit here — this isn’t a "privilege" so much so as a 
property of the design of these classes.
`NSData`, `NSString`, `NSArray`, and some others, are all known as 
_class clusters_; the classes you know and use are essentially abstract 
base classes whose implementation is given in private concrete 
subclasses that specialize based on usage. These classes are essentially 
an abstract interface for subclasses to follow. You can take a look at 
the [subclassing notes for 
`NSArray`](https://developer.apple.com/documentation/foundation/nsarray#1651549), 
for instance, to see the guidelines offered for subclassing such a base 
class.


The reason you can relatively safely offer `static` extensions on these 
types is that it’s reasonably rare to need to subclass them, and at 
that, even rarer to offer any interface _besides_ what’s given by the 
base class. You can rely on the, say, `NSString` interface to access all 
functionality needed to represent a string. If I were to subclass 
`NSString` with totally different properties, though, your `static` 
extension might not take that into account.


Not all types you list here are class clusters, BTW, but they largely 
fall into the same category of "never really subclassed". There’s no 
real need for anyone to subclass `NSDate` or `NSDecimalNumber` (since 
they’re pretty low-level structural types), so this should apply to 
those as well.


In general, this property applies to all types like this which are 
rarely subclassed. In Swift, types like this might fall under a `final 
class` designation, though in Objective-C it’s more by convention/lack 
of need than by strict enforcement. There’s a reason we offer some of 
these as `struct`s in Swift (e.g. `Date`, `Decimal`, `Data`, etc.).


On 3 Aug 2017, at 21:03, Gwendal Roué wrote:


Le 3 août 2017 à 19:10, Itai Ferber  a écrit :

I just mentioned this in my other email, but to point out here: the 
reason this works in your case is because you adopt these methods as 
static funcs and can reasonably rely on subclasses of NSData, 
NSNumber, NSString, etc. to do the right thing because of work done 
behind the scenes in the ObjC implementations of these classes (and 
because we’ve got established subclassing requirements on these 
methods — all subclasses of these classes are going to look 
approximately the same without doing anything crazy).


This would not work for Codable in the general case, however, where 
subclasses likely need to add additional storage, properties, encoded 
representations, etc., without equivalent requirements, either via 
additional protocols or conventions.


Thaks for your explanation why a static method in a protocol is able 
to instantiate non final classes like NSData, NSDate, NSNumber, 
NSDecimalNumber, NSString, etc.


Is this "privilege" stable? Can I rely on it to be maintained over 
time? Or would it be a better idea to drop support for those low-level 
Foundation classes, because they'll eventually become regular classes 
without any specific support? This would not harm that much: Data, 
Date, String are there for a reason. NSDecimalNumber is the only one 
of its kind, though.


Gwendal
___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Xiaodi Wu via swift-evolution
On Thu, Aug 3, 2017 at 11:03 PM, Gwendal Roué via swift-evolution <
swift-evolution@swift.org> wrote:

>
> > Le 3 août 2017 à 19:10, Itai Ferber  a écrit :
> >
> > I just mentioned this in my other email, but to point out here: the
> reason this works in your case is because you adopt these methods as static
> funcs and can reasonably rely on subclasses of NSData, NSNumber, NSString,
> etc. to do the right thing because of work done behind the scenes in the
> ObjC implementations of these classes (and because we’ve got established
> subclassing requirements on these methods — all subclasses of these classes
> are going to look approximately the same without doing anything crazy).
> >
> > This would not work for Codable in the general case, however, where
> subclasses likely need to add additional storage, properties, encoded
> representations, etc., without equivalent requirements, either via
> additional protocols or conventions.
>
> Thaks for your explanation why a static method in a protocol is able to
> instantiate non final classes like NSData, NSDate, NSNumber,
> NSDecimalNumber, NSString, etc.
>
> Is this "privilege" stable? Can I rely on it to be maintained over time?
> Or would it be a better idea to drop support for those low-level Foundation
> classes, because they'll eventually become regular classes without any
> specific support? This would not harm that much: Data, Date, String are
> there for a reason. NSDecimalNumber is the only one of its kind, though.
>

Why not Decimal?
___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Gwendal Roué via swift-evolution

> Le 3 août 2017 à 19:10, Itai Ferber  a écrit :
> 
> I just mentioned this in my other email, but to point out here: the reason 
> this works in your case is because you adopt these methods as static funcs 
> and can reasonably rely on subclasses of NSData, NSNumber, NSString, etc. to 
> do the right thing because of work done behind the scenes in the ObjC 
> implementations of these classes (and because we’ve got established 
> subclassing requirements on these methods — all subclasses of these classes 
> are going to look approximately the same without doing anything crazy).
> 
> This would not work for Codable in the general case, however, where 
> subclasses likely need to add additional storage, properties, encoded 
> representations, etc., without equivalent requirements, either via additional 
> protocols or conventions.

Thaks for your explanation why a static method in a protocol is able to 
instantiate non final classes like NSData, NSDate, NSNumber, NSDecimalNumber, 
NSString, etc.

Is this "privilege" stable? Can I rely on it to be maintained over time? Or 
would it be a better idea to drop support for those low-level Foundation 
classes, because they'll eventually become regular classes without any specific 
support? This would not harm that much: Data, Date, String are there for a 
reason. NSDecimalNumber is the only one of its kind, though.

Gwendal

___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Itai Ferber via swift-evolution
I just mentioned this in my other email, but to point out here: the reason this 
works in your case is because you adopt these methods as static funcs and can 
reasonably rely on subclasses of NSData, NSNumber, NSString, etc. to do the 
right thing because of work done behind the scenes in the ObjC implementations 
of these classes (and because we’ve got established subclassing requirements on 
these methods — all subclasses of these classes are going to look approximately 
the same without doing anything crazy).

This would not work for Codable in the general case, however, where subclasses 
likely need to add additional storage, properties, encoded representations, 
etc., without equivalent requirements, either via additional protocols or 
conventions.

> On Aug 3, 2017, at 1:50 AM, Gwendal Roué via swift-evolution 
>  wrote:
> 
> 
>> Le 3 août 2017 à 02:09, Jordan Rose via swift-evolution 
>> > a écrit :
>> 
>> P.S. There's a reason why Decodable uses an initializer instead of a 
>> factory-like method on the type but I can't remember what it is right now. I 
>> think it's something to do with having the right result type, which would 
>> have to be either 'Any' or an associated type if it wasn't just 'Self'. (And 
>> if it is 'Self' then it has all the same problems as an initializer and 
>> would require extra syntax.) Itai would know for sure.
> 
> For anyone interested, factory methods *can* retroactivaly be added to 
> existing classes. This is how the SQLite library GRDB.swift is able to decode 
> classes hierarchies like NSString, NSNumber, NSDecimalNumber, etc. from 
> SQLite values:
> 
> The protocol for types that can instantiate from SQLite values has a factory 
> method:
> 
> public protocol DatabaseValueConvertible {
> /// Returns a value initialized from *dbValue*, if possible.
> static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self?
> }
> 
> Having Foundation classes implement it uses various techniques:
> 
> 1. "Casting" (Data to NSData, or NSDate to Date, depending on which type 
> provides the root conformance)
> 
> // Workaround Swift inconvenience around factory methods of non-final 
> classes
> func cast(_ value: T) -> U? {
> return value as? U
> }
> 
> extension NSData : DatabaseValueConvertible {
> public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> 
> Self? {
> // Use Data conformance
> guard let data = Data.fromDatabaseValue(dbValue) else {
> return nil
> }
> return cast(data)
> }
> }
> 
> // Derives Date conformance from NSDate, for example
> extension ReferenceConvertible where Self: DatabaseValueConvertible, 
> Self.ReferenceType: DatabaseValueConvertible {
> public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> 
> Self? {
> return ReferenceType.fromDatabaseValue(dbValue).flatMap { 
> cast($0) }
> }
> }
> 
> 
> 2. Using magic Foundation initializers (magic because the code below compiles 
> even if those are not *required* initializers). Works for NSNumber, 
> NSDecimalNumber, NSString:
> 
> extension NSNumber : DatabaseValueConvertible {
> public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> 
> Self? {
> switch dbValue.storage {
> case .int64(let int64):
> return self.init(value: int64)
> case .double(let double):
> return self.init(value: double)
> default:
> return nil
> }
> }
> }
> 
> extension NSString : DatabaseValueConvertible {
> public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> 
> Self? {
> // Use String conformance
> guard let string = String.fromDatabaseValue(dbValue) else {
> return nil
> }
> return self.init(string: string)
> }
> }
> 
> The magic about Foundation initializers above makes me doubt that this 
> technique is general enough for Decodable to profit from it, though. Yes it 
> runs on Linux, so I'm not even sure if objc runtime is required or not. I'm 
> clueless ???
> 
> Gwendal Roué
> 
> ___
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Itai Ferber via swift-evolution
Thanks for putting these thoughts together, Jordan! Some additional comments 
inline.

> On Aug 2, 2017, at 5:08 PM, Jordan Rose  wrote:
> 
> David Hart recently asked on Twitter 
>  if there was a good 
> way to add Decodable support to somebody else's class. The short answer is 
> "no, because you don't control all the subclasses", but David already 
> understood that and wanted to know if there was anything working to mitigate 
> the problem. So I decided to write up a long email about it instead. (Well, 
> actually I decided to write a short email and then failed at doing so.)
> 
> The Problem
> 
> You can add Decodable to someone else's struct today with no problems:
> 
> extension Point: Decodable {
>   enum CodingKeys: String, CodingKey {
> case x
> case y
>   }
>   public init(from decoder: Decoder) throws {
> let container = try decoder.container(keyedBy: CodingKeys.self)
> let x = try container.decode(Double.self, forKey: .x)
> let y = try container.decode(Double.self, forKey: .y)
> self.init(x: x, y: y)
>   }
> }
> 
> But if Point is a (non-final) class, then this gives you a pile of errors:
> 
> - init(from:) needs to be 'required' to satisfy a protocol requirement. 
> 'required' means the initializer can be invoked dynamically on subclasses. 
> Why is this important? Because someone might write code like this:
> 
> func decodeMe() -> Result {
>   let decoder = getDecoderFromSomewhere()
>   return Result(from: decoder)
> }
> let specialPoint: VerySpecialSubclassOfPoint = decodeMe()
> 
> …and the compiler can't stop them, because VerySpecialSubclassOfPoint is a 
> Point, and Point is Decodable, and therefore VerySpecialSubclassOfPoint is 
> Decodable. A bit more on this later, but for now let's say that's a sensible 
> requirement.
> 
> - init(from:) also has to be a 'convenience' initializer. That one makes 
> sense too—if you're outside the module, you can't necessarily see private 
> properties, and so of course you'll have to call another initializer that can.
> 
> But once it's marked 'convenience' and 'required' we get "'required' 
> initializer must be declared directly in class 'Point' (not in an 
> extension)", and that defeats the whole purpose. Why this restriction?
> 
> 
> The Semantic Reason
> 
> The initializer is 'required', right? So all subclasses need to have access 
> to it. But the implementation we provided here might not make sense for all 
> subclasses—what if VerySpecialSubclassOfPoint doesn't have an 'init(x:y:)' 
> initializer? Normally, the compiler checks for this situation and makes the 
> subclass reimplement the 'required' initializer…but that only works if the 
> 'required' initializers are all known up front. So it can't allow this new 
> 'required' initializer to go by, because someone might try to call it 
> dynamically on a subclass. Here's a dynamic version of the code from above:
> 
> func decodeDynamic(_ pointType: Point.Type) -> Point {
>   let decoder = getDecoderFromSomewhere()
>   return pointType.init(from: decoder)
> }
> let specialPoint = decodeDynamic(VerySpecialSubclassOfPoint.self)
> 
> 
> The Implementation Reason
> 
> 'required' initializers are like methods: they may require dynamic dispatch. 
> That means that they get an entry in the class's dynamic dispatch table, 
> commonly known as its vtable. Unlike Objective-C method tables, vtables 
> aren't set up to have entries arbitrarily added at run time.
> 
> (Aside: This is one of the reasons why non-@objc methods in Swift extensions 
> can't be overridden; if we ever lift that restriction, it'll be by using a 
> separate table and a form of dispatch similar to objc_msgSend. I sent a 
> proposal to swift-evolution about this last year but there wasn't much 
> interest.)
> 
> 
> The Workaround
> 
> Today's answer isn't wonderful, but it does work: write a wrapper struct that 
> conforms to Decodable instead:
> 
> struct DecodedPoint: Decodable {
>   var value: Point
>   enum CodingKeys: String, CodingKey {
> case x
> case y
>   }
>   public init(from decoder: Decoder) throws {
> let container = try decoder.container(keyedBy: CodingKeys.self)
> let x = try container.decode(Double.self, forKey: .x)
> let y = try container.decode(Double.self, forKey: .y)
> self.value = Point(x: x, y: y)
>   }
> }
> 
> This doesn't have any of the problems with inheritance, because it only 
> handles the base class, Point. But it makes everywhere else a little less 
> convenient—instead of directly encoding or decoding Point, you have to use 
> the wrapper, and that means no implicitly-generated Codable implementations 
> either.
> 
> I'm not going to spend more time talking about this, but it is the officially 
> recommended answer at the moment. You can also just have all your own types 
> that contain points manually decode the 'x' and 'y' values and then construct 
> a Point from that.

Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Slava Pestov via swift-evolution

> On Aug 2, 2017, at 10:48 PM, David Hart  wrote:
> 
> Somewhat related: I have a similar problem in a project where I need two 
> different Codable conformances for a type: one for coding/decoding from/to 
> JSON, and another one for coding/decoding from/to a database row. The keys 
> and formatting are not identical. The only solution around that for now is 
> separate types, which can be sub-optimal from a performance point of view.

Actually if the wrapper types are structs with a single field, their use should 
not introduce any additional overhead at runtime.

Slava
___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Gwendal Roué via swift-evolution

> Le 3 août 2017 à 02:09, Jordan Rose via swift-evolution 
>  a écrit :
> 
> P.S. There's a reason why Decodable uses an initializer instead of a 
> factory-like method on the type but I can't remember what it is right now. I 
> think it's something to do with having the right result type, which would 
> have to be either 'Any' or an associated type if it wasn't just 'Self'. (And 
> if it is 'Self' then it has all the same problems as an initializer and would 
> require extra syntax.) Itai would know for sure.

For anyone interested, factory methods *can* retroactivaly be added to existing 
classes. This is how the SQLite library GRDB.swift is able to decode classes 
hierarchies like NSString, NSNumber, NSDecimalNumber, etc. from SQLite values:

The protocol for types that can instantiate from SQLite values has a factory 
method:

public protocol DatabaseValueConvertible {
/// Returns a value initialized from *dbValue*, if possible.
static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self?
}

Having Foundation classes implement it uses various techniques:

1. "Casting" (Data to NSData, or NSDate to Date, depending on which type 
provides the root conformance)

// Workaround Swift inconvenience around factory methods of non-final 
classes
func cast(_ value: T) -> U? {
return value as? U
}

extension NSData : DatabaseValueConvertible {
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? 
{
// Use Data conformance
guard let data = Data.fromDatabaseValue(dbValue) else {
return nil
}
return cast(data)
}
}

// Derives Date conformance from NSDate, for example
extension ReferenceConvertible where Self: DatabaseValueConvertible, 
Self.ReferenceType: DatabaseValueConvertible {
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? 
{
return ReferenceType.fromDatabaseValue(dbValue).flatMap { cast($0) }
}
}


2. Using magic Foundation initializers (magic because the code below compiles 
even if those are not *required* initializers). Works for NSNumber, 
NSDecimalNumber, NSString:

extension NSNumber : DatabaseValueConvertible {
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? 
{
switch dbValue.storage {
case .int64(let int64):
return self.init(value: int64)
case .double(let double):
return self.init(value: double)
default:
return nil
}
}
}

extension NSString : DatabaseValueConvertible {
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? 
{
// Use String conformance
guard let string = String.fromDatabaseValue(dbValue) else {
return nil
}
return self.init(string: string)
}
}

The magic about Foundation initializers above makes me doubt that this 
technique is general enough for Decodable to profit from it, though. Yes it 
runs on Linux, so I'm not even sure if objc runtime is required or not. I'm 
clueless ???

Gwendal Roué

___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Elviro Rocca via swift-evolution
Wrapper structs FTW. I think it's a lovely pattern that's super Swifty and 
really should be advertised more for solving these kinds of problems. 
Language-level features could also be useful for making that more usable, and 
the discussion about "strong" typealiases seems oriented in that direction.


Elviro


> Il giorno 03 ago 2017, alle ore 08:35, David Hart via swift-evolution 
> > ha scritto:
> 
> 
>> On 3 Aug 2017, at 08:00, Slava Pestov > > wrote:
>> 
>> 
>> 
>>> On Aug 2, 2017, at 10:48 PM, David Hart >> > wrote:
>>> 
>>> Somewhat related: I have a similar problem in a project where I need two 
>>> different Codable conformances for a type: one for coding/decoding from/to 
>>> JSON, and another one for coding/decoding from/to a database row. The keys 
>>> and formatting are not identical. The only solution around that for now is 
>>> separate types, which can be sub-optimal from a performance point of view.
>> Actually if the wrapper types are structs with a single field, their use 
>> should not introduce any additional overhead at runtime.
> 
> I ❤️ Swift
> 
> 
> ___
> swift-evolution mailing list
> swift-evolution@swift.org 
> https://lists.swift.org/mailman/listinfo/swift-evolution
>> Slava

___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread David Hart via swift-evolution

> On 3 Aug 2017, at 08:00, Slava Pestov  wrote:
> 
> 
>> On Aug 2, 2017, at 10:48 PM, David Hart > > wrote:
>> 
>> Somewhat related: I have a similar problem in a project where I need two 
>> different Codable conformances for a type: one for coding/decoding from/to 
>> JSON, and another one for coding/decoding from/to a database row. The keys 
>> and formatting are not identical. The only solution around that for now is 
>> separate types, which can be sub-optimal from a performance point of view.
> 
> Actually if the wrapper types are structs with a single field, their use 
> should not introduce any additional overhead at runtime.

I ❤️ Swift

> Slava

___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-03 Thread Goffredo Marocchi via swift-evolution

Sent from my iPhone

> On 3 Aug 2017, at 01:09, Jordan Rose via swift-evolution 
>  wrote:
> 
> 
> 'required' initializers are like methods: they may require dynamic dispatch. 
> That means that they get an entry in the class's dynamic dispatch table, 
> commonly known as its vtable. Unlike Objective-C method tables, vtables 
> aren't set up to have entries arbitrarily added at run time.
> 
> (Aside: This is one of the reasons why non-@objc methods in Swift extensions 
> can't be overridden; if we ever lift that restriction, it'll be by using a 
> separate table and a form of dispatch similar to objc_msgSend. I sent a 
> proposal to swift-evolution about this last year but there wasn't much 
> interest.)

If I missed replying to that originally I also missed the chance to say that it 
would be a lovely idea and dynamic dispatch in some cases is just what the 
doctor ordered (runtime editable method tables).
This is especially especially important with extensions for classes and default 
methods (and the current rules for overriding methods in the implementing 
class), please resubmit the proposal :).___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] Why you can't make someone else's class Decodable: a long-winded explanation of 'required' initializers

2017-08-02 Thread David Hart via swift-evolution
Thanks for the detailed explanation Jordan! Comment inline:

> On 3 Aug 2017, at 02:08, Jordan Rose  wrote:
> 
> David Hart recently asked on Twitter 
>  if there was a good 
> way to add Decodable support to somebody else's class. The short answer is 
> "no, because you don't control all the subclasses", but David already 
> understood that and wanted to know if there was anything working to mitigate 
> the problem. So I decided to write up a long email about it instead. (Well, 
> actually I decided to write a short email and then failed at doing so.)
> 
> The Problem
> 
> You can add Decodable to someone else's struct today with no problems:
> 
> extension Point: Decodable {
>   enum CodingKeys: String, CodingKey {
> case x
> case y
>   }
>   public init(from decoder: Decoder) throws {
> let container = try decoder.container(keyedBy: CodingKeys.self)
> let x = try container.decode(Double.self, forKey: .x)
> let y = try container.decode(Double.self, forKey: .y)
> self.init(x: x, y: y)
>   }
> }
> 
> But if Point is a (non-final) class, then this gives you a pile of errors:
> 
> - init(from:) needs to be 'required' to satisfy a protocol requirement. 
> 'required' means the initializer can be invoked dynamically on subclasses. 
> Why is this important? Because someone might write code like this:
> 
> func decodeMe() -> Result {
>   let decoder = getDecoderFromSomewhere()
>   return Result(from: decoder)
> }
> let specialPoint: VerySpecialSubclassOfPoint = decodeMe()
> 
> …and the compiler can't stop them, because VerySpecialSubclassOfPoint is a 
> Point, and Point is Decodable, and therefore VerySpecialSubclassOfPoint is 
> Decodable. A bit more on this later, but for now let's say that's a sensible 
> requirement.
> 
> - init(from:) also has to be a 'convenience' initializer. That one makes 
> sense too—if you're outside the module, you can't necessarily see private 
> properties, and so of course you'll have to call another initializer that can.
> 
> But once it's marked 'convenience' and 'required' we get "'required' 
> initializer must be declared directly in class 'Point' (not in an 
> extension)", and that defeats the whole purpose. Why this restriction?
> 
> 
> The Semantic Reason
> 
> The initializer is 'required', right? So all subclasses need to have access 
> to it. But the implementation we provided here might not make sense for all 
> subclasses—what if VerySpecialSubclassOfPoint doesn't have an 'init(x:y:)' 
> initializer? Normally, the compiler checks for this situation and makes the 
> subclass reimplement the 'required' initializer…but that only works if the 
> 'required' initializers are all known up front. So it can't allow this new 
> 'required' initializer to go by, because someone might try to call it 
> dynamically on a subclass. Here's a dynamic version of the code from above:
> 
> func decodeDynamic(_ pointType: Point.Type) -> Point {
>   let decoder = getDecoderFromSomewhere()
>   return pointType.init(from: decoder)
> }
> let specialPoint = decodeDynamic(VerySpecialSubclassOfPoint.self)
> 
> 
> The Implementation Reason
> 
> 'required' initializers are like methods: they may require dynamic dispatch. 
> That means that they get an entry in the class's dynamic dispatch table, 
> commonly known as its vtable. Unlike Objective-C method tables, vtables 
> aren't set up to have entries arbitrarily added at run time.
> 
> (Aside: This is one of the reasons why non-@objc methods in Swift extensions 
> can't be overridden; if we ever lift that restriction, it'll be by using a 
> separate table and a form of dispatch similar to objc_msgSend. I sent a 
> proposal to swift-evolution about this last year but there wasn't much 
> interest.)

I may have missed the proposal you sent, because I’d be quite interested about 
this. I hit this restriction once in a while and I really wish we could 
override methods in Swift extensions.

> The Workaround
> 
> Today's answer isn't wonderful, but it does work: write a wrapper struct that 
> conforms to Decodable instead:
> 
> struct DecodedPoint: Decodable {
>   var value: Point
>   enum CodingKeys: String, CodingKey {
> case x
> case y
>   }
>   public init(from decoder: Decoder) throws {
> let container = try decoder.container(keyedBy: CodingKeys.self)
> let x = try container.decode(Double.self, forKey: .x)
> let y = try container.decode(Double.self, forKey: .y)
> self.value = Point(x: x, y: y)
>   }
> }
> 
> This doesn't have any of the problems with inheritance, because it only 
> handles the base class, Point. But it makes everywhere else a little less 
> convenient—instead of directly encoding or decoding Point, you have to use 
> the wrapper, and that means no implicitly-generated Codable implementations 
> either.
> 
> I'm not going to spend more time talking about this, but it is the officially 
> recommended answer at