[swift-evolution] Proposals: (1) Forbidding custom `==` for value types, (2) `dispatch` keyword, (3) `default`-result for methods with `Self`, and (4) Poor-Mans-Existentials

Johannes Neubauer neubauer at kingsware.de
Mon Jul 18 06:52:25 CDT 2016


> Am 18.07.2016 um 13:05 schrieb L. Mihalkovic <laurent.mihalkovic at gmail.com>:
> 
> IMHO implementing your proposal would close the door on some of the things you do when building in-memory dbs (T == U -> TRUE for T not related to U), which if swift remains for small apps is not a terrible loss, but may be more of an issue for one day doing big-data with it.

You talk about reference types now, right? I proposed a `default` keyword, which (in a pattern matching fashion) would catch all calls to T == U for which no implementation exists (so this is exactly when T != U). You could of course change for a given type hierarchy the `default` result to `true` if appropriate.

If you recall how virtual functions work. There is a V-table, which contains an entry for each overridden function and the correct one is looked up and executed with an implicit _self parameter for the instance on which the method has been executed.

You could write something like this:

```swift
// you could equally write: `func ==(lhs: dispatch A, rhs: dispatch A) -> Bool = false`
dispatch func ==(lhs: A, rhs: A) -> Bool = false {
  lhs === rhs
}

// the default value `= false` is inferred...
dispatch func ==(lhs: Aa, rhs: Aa) -> Bool {
  lhs.a == rhs.a
}
```

You could change the default value of course. For comparisons (`Comparable`) you could add a closure to calculate the default value (e.g. more specialized instances are greater than less specialized so Point3D is always greater than Point2D, in order to get total ordering).

The implementation would create a dispatch-table for this function. The key would be a tuple `(MetaType, MetaType)`. And the value would be the corresponding function. If the lookup `(lhs.dynamicType, rhs.dynamicType)` does not find a table entry (e.g. because it is `lhs.dynamicType != rhs.dynamicType`), then a auto-closure returning the default value (or if it is a closure, the closure itself) is returned and executed.

Cool?

All the best
Johannes
> 
> Regards
> (From mobile)
> 
>> On Jul 18, 2016, at 12:21 PM, Johannes Neubauer via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> See below...
>> 
>>> Am 18.07.2016 um 12:08 schrieb Johannes Neubauer via swift-evolution <swift-evolution at swift.org>:
>>> 
>>> Dear  Félix,
>>> 
>>> As a small follow-up, because you asked what I am protecting you from. Dictionaries and Sets, for instance, will work only, if equality and hash value are computed contract conform. As soon as you let (unintendedly) differing values collapse or same values break up, you will have unintended behavior. This is crucial and I think every developer should be thankful for any help he gets here from a language. If (in the future) the swift runtime will create value pools for you, and you have a wrong implementation of equality, the complete system will just misbehave. **That** will be bugs hard to find.
>>> 
>>> All the best
>>> Johannes
>>> 
>>>> Am 18.07.2016 um 11:50 schrieb Johannes Neubauer via swift-evolution <swift-evolution at swift.org>:
>>>> 
>>>> 
>>>>> Am 18.07.2016 um 03:51 schrieb Félix Cloutier <felixcca at yahoo.ca>:
>>>>> 
>>>>> Your initial rationale no longer makes sense with your suggested solution. If the dumb comparison returns false, people can still introduce side effects in the comparison method, except that now it's even harder to find out because all of my equality tests have been rewritten as "memcmp(a, b) || ==(a, b)“.
>>>> 
>>>> No its `memcmp(a, b) && ==(a,b)`, since if the „standard equality“ says `true` there is no short-circuit, but the custom implementation has to be `true` either! It is just a pre-condition.
>> 
>> Sorry, wrote this in a hurry. It is `||` of course. But still the rest holds.
>> 
>>>> 
>>>>> What are you trying to protect me from?
>>>> 
>>>> 1. You cannot say something is unequal although the system says it is equal
>>>> 2. You do not have to implement equality for value types, only if you really need custom behavior (so you do not write boiler-plate code, which is error prone), so side effects will be less common
>>>> 3. With unique indirect storage (and copy-on-write) you would be able use `==` for large values, because these values are only shared for reads not for writes  (future, not yet available in swift), so no race conditions
>>>> 4. With `dispatch` in operator-methods (or any other) as well as a `default` clause for reference types, so that equality of mixed-types just result in `false`, so that this is not possible anymore (see excerpt of discussion):
>>>> 
>>>>> Am 16.07.2016 um 15:18 schrieb Johannes Neubauer via swift-evolution <swift-evolution at swift.org>:
>>>>> 
>>>>> This is not true for reference types. Consider the following **bad** (but compiling code):
>>>>> 
>>>>> ```swift
>>>>> class A: Equatable {}
>>>>> 
>>>>> class Aa: A {
>>>>> let a: Int
>>>>> 
>>>>> init(a: Int) {
>>>>>    self.a = a
>>>>> }
>>>>> }
>>>>> 
>>>>> func ==(lhs: A, rhs: A) -> Bool {
>>>>> return lhs === rhs
>>>>> }
>>>>> 
>>>>> func ==(lhs: Aa, rhs: Aa) -> Bool {
>>>>> return lhs.a == rhs.a
>>>>> }
>>>>> ```
>>>>> 
>>>>> Now let us use this:
>>>>> 
>>>>> ```swift
>>>>> let a = A()
>>>>> let a2 = A()
>>>>> let aa = Aa(a: 0)
>>>>> let aa2 = Aa(a: 1)
>>>>> let aa3 = Aa(a: 1)
>>>>> 
>>>>> // prints `true`
>>>>> print(a == a)
>>>>> 
>>>>> // prints `false`
>>>>> print(a == a2)
>>>>> 
>>>>> // prints `false`
>>>>> print(a == aa)
>>>>> 
>>>>> // prints `false`
>>>>> print(a == aa3)
>>>>> 
>>>>> // prints `false`
>>>>> print(aa == aa2)
>>>>> 
>>>>> // prints `true` because it compares the `a: Int` values.
>>>>> print(aa2 == aa3)
>>>>> 
>>>>> // now mixed-type comparison (returns `false`)
>>>>> print(a == aa2)
>>>>> ```
>>>>> 
>>>>> Hence, you can do mixed-type equality checks in Swift. Even worse is, you can do this:
>>>>> 
>>>>> ```swift
>>>>> let aa2AsA: A = aa2,
>>>>> aa3AsA: A = aa3
>>>>> 
>>>>> // prints `true` because it compares the `a: Int` values.
>>>>> print(aa2 == aa3)
>>>>> 
>>>>> // prints `false`, because the equals method of `A` is used
>>>>> print(aa2AsA == aa3AsA)
>>>>> ```
>>>>> 
>>>>> Just by assigning an object to a variable that is typed differently the result is completely different. This is because method parameters are dispatched statically. This is fast, but results in really unintended results, you can do a **lot** of things breaking the contract of `==` with that. This is why I wanted to add a `default` clause (in *3.* of my original proposal) for such methods involving two references to `Self`. Further on, I wanted to add the keyword `dispatch` for method (and operator) parameters, where dispatching is necessary (see *2.* of my original proposal).
>>>> 
>>>> 
>>>> _______________________________________________
>>>> 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
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list