Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing "Either" struct in Swift

Tags:

swift

Can someone explain what is happening in the code below? It creates a struct Either which takes any two optional types and (attempts to) return whichever one is not nil, or the first if both are not nil. However, it behaves strangely when passing literal nil as opposed to a nil variable in. I can't see why b4 in the example behaves as it does...

struct Either <T1, T2> {
    let first: T1?
    let second: T2?

    init(first: T1?, second: T2?) {
        self.first = first
        self.second = second
    }

    func either() -> Bool {
        return (self.first != nil) || (self.second != nil)
    }

    func which() -> Any? {
        if self.first != nil {
            return self.first
        } else if self.second != nil {
            return self.second
        }
        return nil
    }
}

var s1: String? = nil
var s2: Int? = nil

let b1 = Either(first: s1, second: s2)
b1.either() // false
b1.which() // {nil}

s1 = "Hello"
let b2 = Either(first: s1, second: s2)
b2.either() // true
b2.which()  // {Some Hello}

s1 = nil
s2 = 7
let b3 = Either(first: s1, second: s2)
b3.either() // true
b3.which()  // {Some 7}

// all as expected, however
let b4 = Either(first: nil, second: nil)
b4.either() // true !!! <<<<<<<<<<<<<<<<<<
b4.which()  // {nil}

I think it has to do with "Optional Optionals", but I'm not sure it's behaving as expected.

like image 875
Grimxn Avatar asked Jul 04 '14 11:07

Grimxn


1 Answers

This has been fixed in Beta 3, the last case now correctly triggers a compiler error forcing you to specify the generic type explicitely

The problem is in types - when creating Either from literals:

let b4 = Either(first: nil, second: nil)

then the compiler can't infer the type for the nil parameters. If you check the debugger, you will see that the inferred type is a type called _Nil.

(lldb) p b4
(SwiftConsole.Either<_Nil, _Nil>) $R1 = {
  first = Some
  second = Some
}

Now, this is a type which accepts nil but it is not an optional type itself.

let x: _Nil = nil //this is not an optional

The initialiser is making an optional of it, thus making a value Optional.Some(nil) and because it's a .Some(...) value, comparing it with nil will be false.

I don't see any easy generic workarounds but in this case it would help to specify generic types explicitly:

let b4 = Either<String, Int>(first: nil, second: nil)

I suggest you report a bug because this is just silly. Instead of inferring some special types which lead to undefined behaviour, the compiler should instead trigger an error.

The same problem would arise with type Any because it can also accept nil without being an optional.

let x: Any = nil //this is not an optional

Either<Any, Any>(first: nil, second: nil).which() //true !!!

Edit: This will be fixed in Beta 3. nil will be made a literal, _Nil type will be removed and _Any won't accept nil any more.

Confirmed here: https://devforums.apple.com/thread/234463?tstart=0

like image 126
Sulthan Avatar answered Sep 22 '22 01:09

Sulthan