Union instead of Optional


(frogcjn) #1

for example, there is a method input union of 3 types: A, B, C,

This is the three class.

class A {}

class B {}

class C {}

This is how it implemented under Swift 2:

enum UnionABC {
    case classA(A)
    case classB(B)
    case classC(C)
}

func input(value: UnionABC) {
    
}

let a = A()
let b = B()
let c = C()
input(UnionABC.classA(a))

It needs announce all the cases and name each cases and cannot use class names as their case names.

what about using union? It is more easy and rational.

func input(value: (A | B | C)) {
    
}

let a= A()
input(a)

Or you can implement it with protocol and extension, but compiler will not know how many cases it should have.

protocol UnionABC {
    
}

extension A: UnionABC {}
extension B: UnionABC {}
extension C: UnionABC {}

func input(value: UnionABC) {
    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There are other cases? Compiler doesn't know
    }
}

let a = A()
input(a)

···

下面是被转发的邮件:

发件人: frogcjn@163.com
主题: 回复: [swift-evolution] Union instead of Optional
日期: 2016年5月15日 GMT+8 18:00:55
收件人: Austin Zheng <austinzheng@gmail.com>

Enum and Union are two things.

If you use Enum to implement Union, you should announce it with case name.

Another issue using enum instead of union is that, union can combine types as many as possible, you just write ( A | B | C ... | Z), but for enum, you should carefully announce it for each case.

在 2016年5月15日,15:22,Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> 写道:

In addition, not everything in Swift can be modeled in terms of inheritance relationships.

I'm a little curious as to why union types keep on coming up, when enums can do everything they can and much more (methods, constraints on generic types, conformance to protocols).

Austin

swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(frogcjn) #2

This is the example implemented with generic and enum.

enum UnionOf3 <T,U,V>{
    case type0(T)
    case type1(U)
    case type2(V)
}

func input(value: UnionOf3<A,B,C>) {
    switch value {
    case let .type0(a):
        print(a)
    case let .type1(b):
        print(b)
    case let .type2(c):
        print(c)
    }
}

let a = A()
input(UnionOf3.type0(a))

For each different number N of types to get union, you should announce UnionOfN for each.
it is more complicated then the union solution.

func input(value: (A | B | C)) {
    switch value {
    case a as A:
        print(a)
    case b as B:
        print(b)
    case let .type2(c):
        print(c)
    }
}

let a= A()
input(a)

And the most bad thing is,
compiler doesn’t know the type relation between generic union with original type,
for example, the type relation between A and A | B and A | B | C.

compare:

enum UnionOf3 <T,U,V>{
    case type0(T)
    case type1(U)
    case type2(V)
}

let a = A()
let union: UnionOf3<A,B,C> = UnionOf3.type0(a) // You should wrap by yourself.

a == union // probably the compiler will refuse to compare.

sub-typing:

var fn0: A->Void = {print($0)}
var fn1: UnionOf2<A,B>->Void = {print($0)}

fn0 = fn1 // This should be allowed. But will be rejected since using enum.

var fn2: UnionOf3<A,B,C>->Void = {print($0)}

fn0 = fn2 // This should be allowed. But will be rejected since using enum.
fn1 = fn2 // This should be allowed. But will be rejected since using enum.

···

---------------------------------------------------------------------------

If using union, this problem can be solved easily.

let a = A()
let union: (A|B|C) = a // Automatically wrap.

a == union // Yes

sub-typing:

var fn0: A->Void = {print(v0)}
var fn1: (A|B)->Void = {print(v0)}

fn0 = fn1 // OK

var fn2: (A|B|C)->Void = {print($0)}

fn0 = fn2 // OK
fn1 = fn2 // OK

下面是被转发的邮件:

发件人: Cao Jiannan via swift-evolution <swift-evolution@swift.org>
主题: [swift-evolution] Fwd: Union instead of Optional
日期: 2016年5月15日 GMT+8 18:34:44
收件人: Adrian Zubarev via swift-evolution <swift-evolution@swift.org>
回复-收件人: Cao Jiannan <frogcjn@163.com>

for example, there is a method input union of 3 types: A, B, C,

This is the three class.

class A {}

class B {}

class C {}

This is how it implemented under Swift 2:

enum UnionABC {
    case classA(A)
    case classB(B)
    case classC(C)
}

func input(value: UnionABC) {
    
}

let a = A()
let b = B()
let c = C()
input(UnionABC.classA(a))

It needs announce all the cases and name each cases and cannot use class names as their case names.

what about using union? It is more easy and rational.

func input(value: (A | B | C)) {
    
}

let a= A()
input(a)

Or you can implement it with protocol and extension, but compiler will not know how many cases it should have.

protocol UnionABC {
    
}

extension A: UnionABC {}
extension B: UnionABC {}
extension C: UnionABC {}

func input(value: UnionABC) {
    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There are other cases? Compiler doesn't know
    }
}

let a = A()
input(a)

下面是被转发的邮件:

发件人: frogcjn@163.com <mailto:frogcjn@163.com>
主题: 回复: [swift-evolution] Union instead of Optional
日期: 2016年5月15日 GMT+8 18:00:55
收件人: Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>>

Enum and Union are two things.

If you use Enum to implement Union, you should announce it with case name.

Another issue using enum instead of union is that, union can combine types as many as possible, you just write ( A | B | C ... | Z), but for enum, you should carefully announce it for each case.

在 2016年5月15日,15:22,Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> 写道:

In addition, not everything in Swift can be modeled in terms of inheritance relationships.

I'm a little curious as to why union types keep on coming up, when enums can do everything they can and much more (methods, constraints on generic types, conformance to protocols).

Austin

swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Austin Zheng) #3

This doesn't explain how I can use 'value' once an A() is passed into the
function:

func input(value: (A | B | C)) {

}

If A, B, and C are not related via protocol or class inheritance, then
there is almost nothing you can do with value. Otherwise you still need to
test against the concrete type using a case statement or a if-else ladder.

The other 'gain' is being able to call 'input(A())', rather than
'input(.caseA(A()))'. I agree that the first form is prettier than the
second one. I also think you could make the language pervasively prettier
and more expressive by allowing for all sorts of implicit conversions. At
some point clarity at the point of use beats conciseness, especially when
code within a complex codebase needs to be maintained.

I understand that this is largely a matter of style - different people
value different things, and that's wonderful. I welcome a formal proposal
submitted to the swift-evolution process, and if one appears I'm happy to
consider it in more detail and argue for or against it based on that.

Austin

···

On Sun, May 15, 2016 at 3:34 AM, Cao Jiannan <frogcjn@163.com> wrote:

for example, there is a method input union of 3 types: A, B, C,

This is the three class.

class A {}

class B {}

class C {}

This is how it implemented under Swift 2:

enum UnionABC {
    case classA(A)
    case classB(B)
    case classC(C)
}

func input(value: UnionABC) {

}

let a = A()
let b = B()
let c = C()
input(UnionABC.classA(a))

It needs announce all the cases and name each cases and cannot use class
names as their case names.

what about using union? It is more easy and rational.

func input(value: (A | B | C)) {

}

let a= A()
input(a)

Or you can implement it with protocol and extension, but compiler will not
know how many cases it should have.

protocol UnionABC {

}

extension A: UnionABC {}
extension B: UnionABC {}
extension C: UnionABC {}

func input(value: UnionABC) {
    if value is A {

    } else if value is B {

    } else if value is C {

    } else {
        // There are other cases? Compiler doesn't know
    }
}

let a = A()
input(a)

下面是被转发的邮件:

*发件人: *frogcjn@163.com
*主题: **回复: [swift-evolution] Union instead of Optional*
*日期: *2016年5月15日 GMT+8 18:00:55
*收件人: *Austin Zheng <austinzheng@gmail.com>

Enum and Union are two things.

If you use Enum to implement Union, you should announce it with case name.

Another issue using enum instead of union is that, union can combine
types as many as possible, you just write ( A | B | C ... | Z), but for
enum, you should carefully announce it for each case.

在 2016年5月15日,15:22,Austin Zheng <austinzheng@gmail.com> 写道:

In addition, not everything in Swift can be modeled in terms of
inheritance relationships.

I'm a little curious as to why union types keep on coming up, when enums
can do everything they can and much more (methods, constraints on generic
types, conformance to protocols).

Austin

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


(Haravikk) #4

I think that a case statement or similar syntax will still be needed, and the case names would just be the types themselves. This would work best with support for type-narrowing, for example:

  func someMethod(value:(A|B|C)) {
    switch (value) {
      case .A:
        value.someMethodForTypeA()
      case .B:
        value.someMethodForTypeB()
      case .C:
        value.someMethodForTypeC()
    }
  }

A union should really just be though of as a lightweight, restricted form of enum that can be declared in a quick ad-hoc fashion, similar to how tuples are a simpler form of struct.

I’m generally a +1 for the feature, but I’d be interested to hear about how well equipped the compiler is for optimising something like this. In most cases an Optional covers what I need, and in more complex cases I’d probably declare overloads for each type (i.e- someMethod(value:A), someMethod(value:B) etc.); unions could make the latter case simpler, but will the compiler produce the same code behind the scenes, i.e- by isolating what’s unique to each type?

···

On 16 May 2016, at 11:17, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

If A, B, and C are not related via protocol or class inheritance, then there is almost nothing you can do with value. Otherwise you still need to test against the concrete type using a case statement or a if-else ladder.


(frogcjn) #5

Consider this case:

class A {
    var someCommonProperty: Int = 0
}
class B {
    var someCommonProperty: Int = 0
}
class C {
    var someCommonProperty: Int = 0
}

protocol UnionABC {
    var someCommonProperty: Int
}

extension A: UnionABC {}
extension B: UnionABC {}
extension C: UnionABC {}

===================== If we using protocol

func input(value: UnionABC) {
    print(value.someCommonProperty) // Compiler will know their common properties automatically.
    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There are other cases? Compiler doesn't know
    }
}

let a = A()
input(a)

===================== If we using union

func input(value: (A | B | C)) {
    print(value.someCommonProperty) // Compiler will know their common properties automatically.

    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There no other cases, so the compiler trigger a warning.
    }
}

let a = A()
input(a)

···

=====================

If using generic enum,
the compiler doesn’t know the type relation between generic union with original type.
class A and UnionOf3<A,B,C> are totally two different types, has no relationship.
But class A and (A | B | C) keeps a relationship.

If using union, these two cases will be allowed:

let a = A()
let union: (A|B|C) = a // Automatically wrap.

a == union // Can be compared, Yes

sub-typing:

var fn0: A->Void = {print(v0)}
var fn1: (A|B)->Void = {print(v0)}

fn0 = fn1 // Original Type and Union Type has a sub-typing relationship, OK

var fn2: (A|B|C)->Void = {print($0)}

fn0 = fn2 // OK
fn1 = fn2 // OK

在 2016年5月16日,18:17,Austin Zheng <austinzheng@gmail.com> 写道:

This doesn't explain how I can use 'value' once an A() is passed into the function:

func input(value: (A | B | C)) {
    
}

If A, B, and C are not related via protocol or class inheritance, then there is almost nothing you can do with value. Otherwise you still need to test against the concrete type using a case statement or a if-else ladder.

The other 'gain' is being able to call 'input(A())', rather than 'input(.caseA(A()))'. I agree that the first form is prettier than the second one. I also think you could make the language pervasively prettier and more expressive by allowing for all sorts of implicit conversions. At some point clarity at the point of use beats conciseness, especially when code within a complex codebase needs to be maintained.

I understand that this is largely a matter of style - different people value different things, and that's wonderful. I welcome a formal proposal submitted to the swift-evolution process, and if one appears I'm happy to consider it in more detail and argue for or against it based on that.

Austin

On Sun, May 15, 2016 at 3:34 AM, Cao Jiannan <frogcjn@163.com <mailto:frogcjn@163.com>> wrote:
for example, there is a method input union of 3 types: A, B, C,

This is the three class.

class A {}

class B {}

class C {}

This is how it implemented under Swift 2:

enum UnionABC {
    case classA(A)
    case classB(B)
    case classC(C)
}

func input(value: UnionABC) {
    
}

let a = A()
let b = B()
let c = C()
input(UnionABC.classA(a))

It needs announce all the cases and name each cases and cannot use class names as their case names.

what about using union? It is more easy and rational.

func input(value: (A | B | C)) {
    
}

let a= A()
input(a)

Or you can implement it with protocol and extension, but compiler will not know how many cases it should have.

protocol UnionABC {
    
}

extension A: UnionABC {}
extension B: UnionABC {}
extension C: UnionABC {}

func input(value: UnionABC) {
    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There are other cases? Compiler doesn't know
    }
}

let a = A()
input(a)

下面是被转发的邮件:

发件人: frogcjn@163.com <mailto:frogcjn@163.com>
主题: 回复: [swift-evolution] Union instead of Optional
日期: 2016年5月15日 GMT+8 18:00:55
收件人: Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>>

Enum and Union are two things.

If you use Enum to implement Union, you should announce it with case name.

Another issue using enum instead of union is that, union can combine types as many as possible, you just write ( A | B | C ... | Z), but for enum, you should carefully announce it for each case.

在 2016年5月15日,15:22,Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> 写道:

In addition, not everything in Swift can be modeled in terms of inheritance relationships.

I'm a little curious as to why union types keep on coming up, when enums can do everything they can and much more (methods, constraints on generic types, conformance to protocols).

Austin

swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Austin Zheng) #6

I'm sorry, but I don't understand the point you are trying to make.

If you pass in a value of type (A | B | C) to a function, what might you want to do with that value?

If you want to do one thing if the value is type A, something else if the value is type B, and something else if the value is type C, then you need to switch or otherwise type check the value at runtime. You can't get around this, no matter whether you use enums, protocols, generics, or union type.

If you want it to do something that A, B, and C all support, use a generic and/or a protocol. In this case limiting the inputs to only those three types is probably a design smell. The whole point of a shared interface is that it only matters that the interface is properly implemented by a type, not what that type is.

If you don't care about doing anything with the value, just make your function generic: func<T>(input: T).

Austin

···

On May 16, 2016, at 3:29 AM, Cao Jiannan <frogcjn@163.com> wrote:

Consider this case:

class A {
    var someCommonProperty: Int = 0
}
class B {
    var someCommonProperty: Int = 0
}
class C {
    var someCommonProperty: Int = 0
}

protocol UnionABC {
    var someCommonProperty: Int
}

extension A: UnionABC {}
extension B: UnionABC {}
extension C: UnionABC {}

===================== If we using protocol

func input(value: UnionABC) {
    print(value.someCommonProperty) // Compiler will know their common properties automatically.
    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There are other cases? Compiler doesn't know
    }
}

let a = A()
input(a)

===================== If we using union

func input(value: (A | B | C)) {
    print(value.someCommonProperty) // Compiler will know their common properties automatically.

    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There no other cases, so the compiler trigger a warning.
    }
}

let a = A()
input(a)

=====================

If using generic enum,
the compiler doesn’t know the type relation between generic union with original type.
class A and UnionOf3<A,B,C> are totally two different types, has no relationship.
But class A and (A | B | C) keeps a relationship.

If using union, these two cases will be allowed:

let a = A()
let union: (A|B|C) = a // Automatically wrap.

a == union // Can be compared, Yes

sub-typing:

var fn0: A->Void = {print(v0)}
var fn1: (A|B)->Void = {print(v0)}

fn0 = fn1 // Original Type and Union Type has a sub-typing relationship, OK

var fn2: (A|B|C)->Void = {print($0)}

fn0 = fn2 // OK
fn1 = fn2 // OK

在 2016年5月16日,18:17,Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> 写道:

This doesn't explain how I can use 'value' once an A() is passed into the function:

func input(value: (A | B | C)) {
    
}

If A, B, and C are not related via protocol or class inheritance, then there is almost nothing you can do with value. Otherwise you still need to test against the concrete type using a case statement or a if-else ladder.

The other 'gain' is being able to call 'input(A())', rather than 'input(.caseA(A()))'. I agree that the first form is prettier than the second one. I also think you could make the language pervasively prettier and more expressive by allowing for all sorts of implicit conversions. At some point clarity at the point of use beats conciseness, especially when code within a complex codebase needs to be maintained.

I understand that this is largely a matter of style - different people value different things, and that's wonderful. I welcome a formal proposal submitted to the swift-evolution process, and if one appears I'm happy to consider it in more detail and argue for or against it based on that.

Austin

On Sun, May 15, 2016 at 3:34 AM, Cao Jiannan <frogcjn@163.com <mailto:frogcjn@163.com>> wrote:
for example, there is a method input union of 3 types: A, B, C,

This is the three class.

class A {}

class B {}

class C {}

This is how it implemented under Swift 2:

enum UnionABC {
    case classA(A)
    case classB(B)
    case classC(C)
}

func input(value: UnionABC) {
    
}

let a = A()
let b = B()
let c = C()
input(UnionABC.classA(a))

It needs announce all the cases and name each cases and cannot use class names as their case names.

what about using union? It is more easy and rational.

func input(value: (A | B | C)) {
    
}

let a= A()
input(a)

Or you can implement it with protocol and extension, but compiler will not know how many cases it should have.

protocol UnionABC {
    
}

extension A: UnionABC {}
extension B: UnionABC {}
extension C: UnionABC {}

func input(value: UnionABC) {
    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There are other cases? Compiler doesn't know
    }
}

let a = A()
input(a)

下面是被转发的邮件:

发件人: frogcjn@163.com <mailto:frogcjn@163.com>
主题: 回复: [swift-evolution] Union instead of Optional
日期: 2016年5月15日 GMT+8 18:00:55
收件人: Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>>

Enum and Union are two things.

If you use Enum to implement Union, you should announce it with case name.

Another issue using enum instead of union is that, union can combine types as many as possible, you just write ( A | B | C ... | Z), but for enum, you should carefully announce it for each case.

在 2016年5月15日,15:22,Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> 写道:

In addition, not everything in Swift can be modeled in terms of inheritance relationships.

I'm a little curious as to why union types keep on coming up, when enums can do everything they can and much more (methods, constraints on generic types, conformance to protocols).

Austin

swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Austin Zheng) #7

I like the justification of unions as lightweight restricted ad-hoc enums. Definitely agreed with you in that the compiler will have to do more work, and should be able to handle the implicit conversion without going bonkers.

Thanks!

Austin

···

On May 16, 2016, at 3:35 AM, Haravikk <swift-evolution@haravikk.me> wrote:

On 16 May 2016, at 11:17, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

If A, B, and C are not related via protocol or class inheritance, then there is almost nothing you can do with value. Otherwise you still need to test against the concrete type using a case statement or a if-else ladder.

I think that a case statement or similar syntax will still be needed, and the case names would just be the types themselves. This would work best with support for type-narrowing, for example:

  func someMethod(value:(A|B|C)) {
    switch (value) {
      case .A:
        value.someMethodForTypeA()
      case .B:
        value.someMethodForTypeB()
      case .C:
        value.someMethodForTypeC()
    }
  }

A union should really just be though of as a lightweight, restricted form of enum that can be declared in a quick ad-hoc fashion, similar to how tuples are a simpler form of struct.

I’m generally a +1 for the feature, but I’d be interested to hear about how well equipped the compiler is for optimising something like this. In most cases an Optional covers what I need, and in more complex cases I’d probably declare overloads for each type (i.e- someMethod(value:A), someMethod(value:B) etc.); unions could make the latter case simpler, but will the compiler produce the same code behind the scenes, i.e- by isolating what’s unique to each type?


(Leonardo Pessoa) #8

I can understand this proposal nite and I don't really think they're related to enums at all. You can today achieve the very same behaviour using an empty protocol.

protocol P { }

class A : P { }
class B : P { }
struct C : P { }

func test(value : P) { }

IMO, the proposed syntax will only create a shortcut for this. And we'd still need to test type and cast the to get anything useful. My question now is: is this such a common practice that justifies the shortcut?

···

-----Original Message-----
From: "Haravikk via swift-evolution" <swift-evolution@swift.org>
Sent: ‎16/‎05/‎2016 07:35 AM
To: "Austin Zheng" <austinzheng@gmail.com>
Cc: "Adrian Zubarev via swift-evolution" <swift-evolution@swift.org>; "Cao Jiannan" <frogcjn@163.com>
Subject: Re: [swift-evolution] Union instead of Optional

On 16 May 2016, at 11:17, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

If A, B, and C are not related via protocol or class inheritance, then there is almost nothing you can do with value. Otherwise you still need to test against the concrete type using a case statement or a if-else ladder.

I think that a case statement or similar syntax will still be needed, and the case names would just be the types themselves. This would work best with support for type-narrowing, for example:

  func someMethod(value:(A|B|C)) {
    switch (value) {
      case .A:
        value.someMethodForTypeA()
      case .B:
        value.someMethodForTypeB()
      case .C:
        value.someMethodForTypeC()
    }
  }

A union should really just be though of as a lightweight, restricted form of enum that can be declared in a quick ad-hoc fashion, similar to how tuples are a simpler form of struct.

I’m generally a +1 for the feature, but I’d be interested to hear about how well equipped the compiler is for optimising something like this. In most cases an Optional covers what I need, and in more complex cases I’d probably declare overloads for each type (i.e- someMethod(value:A), someMethod(value:B) etc.); unions could make the latter case simpler, but will the compiler produce the same code behind the scenes, i.e- by isolating what’s unique to each type?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(frogcjn) #9

Hi Austin,

let me repeat the example so that clarify my point from this example.

protocol cannot do this:

func input(value: ProtocolForABC) {
    print(value.someCommonProperty)

    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There no other cases, but compiler will not trigger a warning.
    }
}

The compiler will not know your protocol is only conformed to these three classes.
So the else block will not trigger a warning.

- Jiannan

···

在 2016年5月16日,18:37,Austin Zheng <austinzheng@gmail.com> 写道:

I'm sorry, but I don't understand the point you are trying to make.

If you pass in a value of type (A | B | C) to a function, what might you want to do with that value?

If you want to do one thing if the value is type A, something else if the value is type B, and something else if the value is type C, then you need to switch or otherwise type check the value at runtime. You can't get around this, no matter whether you use enums, protocols, generics, or union type.

If you want it to do something that A, B, and C all support, use a generic and/or a protocol. In this case limiting the inputs to only those three types is probably a design smell. The whole point of a shared interface is that it only matters that the interface is properly implemented by a type, not what that type is.

If you don't care about doing anything with the value, just make your function generic: func<T>(input: T).

Austin


(frogcjn) #10

Union is far better then generic enum/protocol solution.
        * It can extend the original enum and make it powerful.
    
enum ResultDataType {
    case Music
    case Video
    case File
}

enum FailureType {
    case HTTP404
    case HTTP502
}

enum FailureTypev2 {
    case HTTP451
}

typealias Result = (ResultDataType | FailureType | FailureTypev2)

  * It keeps the code clear and does not need developer to announce some unnecessary protocols or enums.
    like UnionOf3<T,U,V> or ProtocolForABC

        * It is easy to wrap original value into an union type.
    let a = A()
  let union: (A|B|C) = a // Automatically wrap.

  * Compiler might search their common properties, and methods, then mark them as a member of the union type.
    print(value.someCommonProperty) // Compiler will know their common properties automatically.

  * Compiler know the union type exactly composed with which types, better than only know which protocol.
    func input(value: ProtocolForABC) {
    if value is A {
        
        } else if value is B {
        
        } else if value is C {
        
        } else {
            // There are other cases? Compiler doesn't know
        }
  }

        * Original types and union types can have a rational relationship between each other.
               Original type is a sub-type of union types contain it.

  var fn0: A->Void = {print(v0)}
  var fn1: (A|B)->Void = {print(v0)}

  fn0 = fn1 // Original Type and Union Type has a sub-typing relationship, OK

  var fn2: (A|B|C)->Void = {print($0)}

  fn0 = fn2 // OK
  fn1 = fn2 // OK

        * It is also easy to compare with value of original type.
    union == a // Can be compared, Yes for most cases.

  * And the most important part, It can replace Optional<T>.
    let string: String?
  is same to
    let string: (String | None) instead of let string: Optional<String>
    
I really think the union type is a good supplement for Swift. Make the language rational.
And the It is also really good for Reactive Programming.

- Jiannan

···

下面是被转发的邮件:

发件人: Haravikk <swift-evolution@haravikk.me>
主题: 回复: [swift-evolution] Union instead of Optional
日期: 2016年5月16日 GMT+8 18:35:25
收件人: Austin Zheng <austinzheng@gmail.com>
抄送: Cao Jiannan <frogcjn@163.com>, Adrian Zubarev via swift-evolution <swift-evolution@swift.org>

On 16 May 2016, at 11:17, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

If A, B, and C are not related via protocol or class inheritance, then there is almost nothing you can do with value. Otherwise you still need to test against the concrete type using a case statement or a if-else ladder.

I think that a case statement or similar syntax will still be needed, and the case names would just be the types themselves. This would work best with support for type-narrowing, for example:

  func someMethod(value:(A|B|C)) {
    switch (value) {
      case .A:
        value.someMethodForTypeA()
      case .B:
        value.someMethodForTypeB()
      case .C:
        value.someMethodForTypeC()
    }
  }

A union should really just be though of as a lightweight, restricted form of enum that can be declared in a quick ad-hoc fashion, similar to how tuples are a simpler form of struct.

I’m generally a +1 for the feature, but I’d be interested to hear about how well equipped the compiler is for optimising something like this. In most cases an Optional covers what I need, and in more complex cases I’d probably declare overloads for each type (i.e- someMethod(value:A), someMethod(value:B) etc.); unions could make the latter case simpler, but will the compiler produce the same code behind the scenes, i.e- by isolating what’s unique to each type?


(Leonardo Pessoa) #11

I don't really get it why the compiler is to complain about the if block not having an else in this case. That seems like a bug that will push us to write unnecessary code. The compiler is not capable of inferring there are more conditions to evaluate in an if statement opposed to a switch statement. Even with the proposed union the compiler cannot infer the need for an else block here because the programmer might not want to do anything else with whatever doesn't match the first condition.

On the other hand, if you intend to add to this proposal that a switch statement could evaluate the type of the value like the following code, I'd agree with you. I'm just not sure one could do this as of today.

func input(value: (A | B | C)) {
   switch value.type {
      case A:
         ...
      case B:
         ...
      default:
         // here the compiler knows I didn't cover all possible types
         // adding 'case C' here makes 'default' unnecessary
   }
}

I would add to this discussion if this would be the best syntax for the proposed type. I'd think about 'union<A, B, C>' as it matched other existing syntax and is a bit more explicit about what's going on the code.

···

On 16 May 2016, at 7:55 am, Cao Jiannan via swift-evolution <swift-evolution@swift.org> wrote:

Hi Austin,

let me repeat the example so that clarify my point from this example.

protocol cannot do this:

func input(value: ProtocolForABC) {
    print(value.someCommonProperty)

    if value is A {
        
    } else if value is B {
        
    } else if value is C {
        
    } else {
        // There no other cases, but compiler will not trigger a warning.
    }
}

The compiler will not know your protocol is only conformed to these three classes.
So the else block will not trigger a warning.

- Jiannan

在 2016年5月16日,18:37,Austin Zheng <austinzheng@gmail.com> 写道:

I'm sorry, but I don't understand the point you are trying to make.

If you pass in a value of type (A | B | C) to a function, what might you want to do with that value?

If you want to do one thing if the value is type A, something else if the value is type B, and something else if the value is type C, then you need to switch or otherwise type check the value at runtime. You can't get around this, no matter whether you use enums, protocols, generics, or union type.

If you want it to do something that A, B, and C all support, use a generic and/or a protocol. In this case limiting the inputs to only those three types is probably a design smell. The whole point of a shared interface is that it only matters that the interface is properly implemented by a type, not what that type is.

If you don't care about doing anything with the value, just make your function generic: func<T>(input: T).

Austin

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


(Leonardo Pessoa) #12

Jiannan, I agree there is an use for union types, I'm just not really fond
of the syntax (I'd really prefer something like 'union<...>') and with
using it for optionals. To enable this syntax for optionals, None would
have to be a valid type of the language and that would enable one to create
the following constructions:

    func something(value: None) -> None { ... }

It wouldn't be essentially wrong but unnecessary since nil would be the
only possible value of the type None. And I don't really see any need to
change the way optionals are implemented to support unions. And we could
still have optional unions using either syntax with existing optional
syntax (which is very clear to me):

    func something(value: (A | B | C)?) { ... }

or

    func something(value: union<A, B, C>?) { ... }

Just as I said before, I don't really think unions are necessary (did you
mean overloading?) but I don't oppose them. I just don't think we need to
change the way a feature already works to justify the addition of a new
feature.

- Leonardo

···

On 16 May 2016 at 08:26, Cao Jiannan via swift-evolution < swift-evolution@swift.org> wrote:

Union is far better then generic enum/protocol solution.
        * It can extend the original enum and make it powerful.
enum ResultDataType {
    case Music
    case Video
    case File
}

enum FailureType {
    case HTTP404
    case HTTP502
}

enum FailureTypev2 {
    case HTTP451
}

typealias Result = (ResultDataType | FailureType | FailureTypev2)

* It keeps the code clear and does not need developer to announce some
unnecessary protocols or enums.
like UnionOf3<T,U,V> or ProtocolForABC

        * It is easy to wrap original value into an union type.
let a = A()
let union: (A|B|C) = a // Automatically wrap.

* Compiler might search their common properties, and methods, then mark
them as a member of the union type.
print(value.someCommonProperty) // Compiler will know their common
properties automatically.

* Compiler know the union type exactly composed with which types, better
than only know which protocol.
func input(value: ProtocolForABC) {
if value is A {

    } else if value is B {

    } else if value is C {

    } else {
         // There are other cases? Compiler doesn't know
    }
}

        * Original types and union types can have a rational relationship
between each other.
               Original type is a sub-type of union types contain it.

var fn0: A->Void = {print(v0)}
var fn1: (A|B)->Void = {print(v0)}

fn0 = fn1 // Original Type and Union Type has a sub-typing relationship,
OK

var fn2: (A|B|C)->Void = {print($0)}

fn0 = fn2 // OK
fn1 = fn2 // OK

        * It is also easy to compare with value of original type.
union == a // Can be compared, Yes for most cases.

* And the most important part, *It can replace Optional<T>.*
let string: String?
is same to
let string: (String | None) instead of let string: Optional<String>

I really think the union type is a good supplement for Swift. Make the
language rational.
And the It is also really good for Reactive Programming.

- Jiannan

下面是被转发的邮件:

*发件人: *Haravikk <swift-evolution@haravikk.me>
*主题: **回复: [swift-evolution] Union instead of Optional*
*日期: *2016年5月16日 GMT+8 18:35:25
*收件人: *Austin Zheng <austinzheng@gmail.com>
*抄送: *Cao Jiannan <frogcjn@163.com>, Adrian Zubarev via swift-evolution <
swift-evolution@swift.org>

On 16 May 2016, at 11:17, Austin Zheng via swift-evolution < > swift-evolution@swift.org> wrote:

If A, B, and C are not related via protocol or class inheritance, then
there is almost nothing you can do with value. Otherwise you still need to
test against the concrete type using a case statement or a if-else ladder.

I think that a case statement or similar syntax will still be needed, and
the case names would just be the types themselves. This would work best
with support for type-narrowing, for example:

func someMethod(value:(A|B|C)) {
switch (value) {
case .A:
value.someMethodForTypeA()
case .B:
value.someMethodForTypeB()
case .C:
value.someMethodForTypeC()
}
}

A union should really just be though of as a lightweight, restricted form
of enum that can be declared in a quick ad-hoc fashion, similar to how
tuples are a simpler form of struct.

I’m generally a +1 for the feature, but I’d be interested to hear about
how well equipped the compiler is for optimising something like this. In
most cases an Optional covers what I need, and in more complex cases I’d
probably declare overloads for each type (i.e- someMethod(value:A),
someMethod(value:B) etc.); unions could make the latter case simpler, but
will the compiler produce the same code behind the scenes, i.e- by
isolating what’s unique to each type?

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


(Sean Heber) #13

Cavet: I have not been following this, so probably someone has said this somewhere and flaws were pointed out.

I don’t really understand the need for union types when you can have multiple methods of the same name that differ only by the type of their parameter:

func something(value: A) {}
func something(value: B) {}
func something(value: C) {}

Doesn’t this more or less accomplish the same thing without needing a switch/if tree or whatever else inside the method to break the types back apart again in the method body and without even needing any new mechanism in the language at all?

Again, sorry for the (very likely) redundant noise on my part.

l8r
Sean

···

On May 17, 2016, at 6:09 PM, Leonardo Pessoa via swift-evolution <swift-evolution@swift.org> wrote:

Jiannan, I agree there is an use for union types, I'm just not really fond of the syntax (I'd really prefer something like 'union<...>') and with using it for optionals. To enable this syntax for optionals, None would have to be a valid type of the language and that would enable one to create the following constructions:

    func something(value: None) -> None { ... }

It wouldn't be essentially wrong but unnecessary since nil would be the only possible value of the type None. And I don't really see any need to change the way optionals are implemented to support unions. And we could still have optional unions using either syntax with existing optional syntax (which is very clear to me):

    func something(value: (A | B | C)?) { ... }

or

    func something(value: union<A, B, C>?) { ... }

Just as I said before, I don't really think unions are necessary (did you mean overloading?) but I don't oppose them. I just don't think we need to change the way a feature already works to justify the addition of a new feature.

- Leonardo

On 16 May 2016 at 08:26, Cao Jiannan via swift-evolution <swift-evolution@swift.org> wrote:
Union is far better then generic enum/protocol solution.
        * It can extend the original enum and make it powerful.
    
enum ResultDataType {
    case Music
    case Video
    case File
}

enum FailureType {
    case HTTP404
    case HTTP502
}

enum FailureTypev2 {
    case HTTP451
}

typealias Result = (ResultDataType | FailureType | FailureTypev2)

  * It keeps the code clear and does not need developer to announce some unnecessary protocols or enums.
    like UnionOf3<T,U,V> or ProtocolForABC

        * It is easy to wrap original value into an union type.
    let a = A()
  let union: (A|B|C) = a // Automatically wrap.

  * Compiler might search their common properties, and methods, then mark them as a member of the union type.
    print(value.someCommonProperty) // Compiler will know their common properties automatically.

  * Compiler know the union type exactly composed with which types, better than only know which protocol.
    func input(value: ProtocolForABC) {
    if value is A {
        
        } else if value is B {
        
        } else if value is C {
        
        } else {
            // There are other cases? Compiler doesn't know
        }
  }

        * Original types and union types can have a rational relationship between each other.
               Original type is a sub-type of union types contain it.

  var fn0: A->Void = {print(v0)}
  var fn1: (A|B)->Void = {print(v0)}

  fn0 = fn1 // Original Type and Union Type has a sub-typing relationship, OK

  var fn2: (A|B|C)->Void = {print($0)}

  fn0 = fn2 // OK
  fn1 = fn2 // OK

        * It is also easy to compare with value of original type.
    union == a // Can be compared, Yes for most cases.

  * And the most important part, It can replace Optional<T>.
    let string: String?
  is same to
    let string: (String | None) instead of let string: Optional<String>
    
I really think the union type is a good supplement for Swift. Make the language rational.
And the It is also really good for Reactive Programming.

- Jiannan

下面是被转发的邮件:

发件人: Haravikk <swift-evolution@haravikk.me>
主题: 回复: [swift-evolution] Union instead of Optional
日期: 2016年5月16日 GMT+8 18:35:25
收件人: Austin Zheng <austinzheng@gmail.com>
抄送: Cao Jiannan <frogcjn@163.com>, Adrian Zubarev via swift-evolution <swift-evolution@swift.org>

On 16 May 2016, at 11:17, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

If A, B, and C are not related via protocol or class inheritance, then there is almost nothing you can do with value. Otherwise you still need to test against the concrete type using a case statement or a if-else ladder.

I think that a case statement or similar syntax will still be needed, and the case names would just be the types themselves. This would work best with support for type-narrowing, for example:

  func someMethod(value:(A|B|C)) {
    switch (value) {
      case .A:
        value.someMethodForTypeA()
      case .B:
        value.someMethodForTypeB()
      case .C:
        value.someMethodForTypeC()
    }
  }

A union should really just be though of as a lightweight, restricted form of enum that can be declared in a quick ad-hoc fashion, similar to how tuples are a simpler form of struct.

I’m generally a +1 for the feature, but I’d be interested to hear about how well equipped the compiler is for optimising something like this. In most cases an Optional covers what I need, and in more complex cases I’d probably declare overloads for each type (i.e- someMethod(value:A), someMethod(value:B) etc.); unions could make the latter case simpler, but will the compiler produce the same code behind the scenes, i.e- by isolating what’s unique to each type?

_______________________________________________
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


(Leonardo Pessoa) #14

That's exactly my point, Sean.

- Leonardo

···

On 17 May 2016 at 20:31, Sean Heber <sean@fifthace.com> wrote:

Cavet: I have not been following this, so probably someone has said this
somewhere and flaws were pointed out.

I don’t really understand the need for union types when you can have
multiple methods of the same name that differ only by the type of their
parameter:

func something(value: A) {}
func something(value: B) {}
func something(value: C) {}

Doesn’t this more or less accomplish the same thing without needing a
switch/if tree or whatever else inside the method to break the types back
apart again in the method body and without even needing any new mechanism
in the language at all?

Again, sorry for the (very likely) redundant noise on my part.

l8r
Sean

> On May 17, 2016, at 6:09 PM, Leonardo Pessoa via swift-evolution < > swift-evolution@swift.org> wrote:
>
> Jiannan, I agree there is an use for union types, I'm just not really
fond of the syntax (I'd really prefer something like 'union<...>') and with
using it for optionals. To enable this syntax for optionals, None would
have to be a valid type of the language and that would enable one to create
the following constructions:
>
> func something(value: None) -> None { ... }
>
> It wouldn't be essentially wrong but unnecessary since nil would be the
only possible value of the type None. And I don't really see any need to
change the way optionals are implemented to support unions. And we could
still have optional unions using either syntax with existing optional
syntax (which is very clear to me):
>
> func something(value: (A | B | C)?) { ... }
>
> or
>
> func something(value: union<A, B, C>?) { ... }
>
> Just as I said before, I don't really think unions are necessary (did
you mean overloading?) but I don't oppose them. I just don't think we need
to change the way a feature already works to justify the addition of a new
feature.
>
>
> - Leonardo
>
> On 16 May 2016 at 08:26, Cao Jiannan via swift-evolution < > swift-evolution@swift.org> wrote:
> Union is far better then generic enum/protocol solution.
> * It can extend the original enum and make it powerful.
>
> enum ResultDataType {
> case Music
> case Video
> case File
> }
>
> enum FailureType {
> case HTTP404
> case HTTP502
> }
>
> enum FailureTypev2 {
> case HTTP451
> }
>
> typealias Result = (ResultDataType | FailureType | FailureTypev2)
>
> * It keeps the code clear and does not need developer to announce
some unnecessary protocols or enums.
> like UnionOf3<T,U,V> or ProtocolForABC
>
> * It is easy to wrap original value into an union type.
> let a = A()
> let union: (A|B|C) = a // Automatically wrap.
>
> * Compiler might search their common properties, and methods, then
mark them as a member of the union type.
> print(value.someCommonProperty) // Compiler will know
their common properties automatically.
>
>
> * Compiler know the union type exactly composed with which types,
better than only know which protocol.
> func input(value: ProtocolForABC) {
> if value is A {
>
> } else if value is B {
>
> } else if value is C {
>
> } else {
> // There are other cases? Compiler doesn't know
> }
> }
>
> * Original types and union types can have a rational
relationship between each other.
> Original type is a sub-type of union types contain it.
>
> var fn0: A->Void = {print(v0)}
> var fn1: (A|B)->Void = {print(v0)}
>
> fn0 = fn1 // Original Type and Union Type has a sub-typing
relationship, OK
>
> var fn2: (A|B|C)->Void = {print($0)}
>
> fn0 = fn2 // OK
> fn1 = fn2 // OK
>
>
> * It is also easy to compare with value of original type.
> union == a // Can be compared, Yes for most cases.
>
> * And the most important part, It can replace Optional<T>.
> let string: String?
> is same to
> let string: (String | None) instead of let string:
Optional<String>
>
>
> I really think the union type is a good supplement for Swift. Make the
language rational.
> And the It is also really good for Reactive Programming.
>
> - Jiannan
>
>> 下面是被转发的邮件:
>>
>> 发件人: Haravikk <swift-evolution@haravikk.me>
>> 主题: 回复: [swift-evolution] Union instead of Optional
>> 日期: 2016年5月16日 GMT+8 18:35:25
>> 收件人: Austin Zheng <austinzheng@gmail.com>
>> 抄送: Cao Jiannan <frogcjn@163.com>, Adrian Zubarev via swift-evolution <
swift-evolution@swift.org>
>>
>>
>>> On 16 May 2016, at 11:17, Austin Zheng via swift-evolution < > swift-evolution@swift.org> wrote:
>>>
>>> If A, B, and C are not related via protocol or class inheritance, then
there is almost nothing you can do with value. Otherwise you still need to
test against the concrete type using a case statement or a if-else ladder.
>>
>> I think that a case statement or similar syntax will still be needed,
and the case names would just be the types themselves. This would work best
with support for type-narrowing, for example:
>>
>> func someMethod(value:(A|B|C)) {
>> switch (value) {
>> case .A:
>> value.someMethodForTypeA()
>> case .B:
>> value.someMethodForTypeB()
>> case .C:
>> value.someMethodForTypeC()
>> }
>> }
>>
>> A union should really just be though of as a lightweight, restricted
form of enum that can be declared in a quick ad-hoc fashion, similar to how
tuples are a simpler form of struct.
>>
>> I’m generally a +1 for the feature, but I’d be interested to hear about
how well equipped the compiler is for optimising something like this. In
most cases an Optional covers what I need, and in more complex cases I’d
probably declare overloads for each type (i.e- someMethod(value:A),
someMethod(value:B) etc.); unions could make the latter case simpler, but
will the compiler produce the same code behind the scenes, i.e- by
isolating what’s unique to each type?
>
>
> _______________________________________________
> 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