2

Lovely buggy swift amazed me once again. In my new project I used optional applicative functor with <*> operator, described here.

infix operator <*> { associativity left precedence 150 }
func <*><A, B>(lhs: (A -> B)?, rhs: A?) -> B? {
    if let lhs1 = lhs {
        if let rhs1 = rhs {
            return lhs1(rhs1)
        }
    }
    return nil
}

And it was really cool, till I caught a weird crash. I'll show it in a simplified version.

let constant: AnyObject? = nil
func someFnc(obj: AnyObject!) {
    println("lol, wat?")
}
someFnc <*> constant

So, what will be the output? Of cause nothing, what would you expect? What if we pass a variable?

var variable: AnyObject? = nil
someFnc <*> variable

Same for this one you tell me, but my console says:

lol, wat?

Yep, thats right, if we pass var, it happily pass through optional binding check. I tried this for NSObject and some Swift structs/classes, if I pass constant everything works fine, if it is a variable, some magic is going on.

update

Describing the magic going on, the var inside <*> function becomes nested Optional and let do not.

Is there any sensible explanation for this implicit behaviour?

workaround

For workaround you can wrap your variable in some function like:

var variable: AnyObject? = nil
func getVar() -> AnyObject? {
    return variable
}
someFnc <*> getVar()

Or in a computed property:

var variable: AnyObject? = nil
var getVariable: AnyObject? {
    return variable
}
someFnc <*> getVariable
Aleksey G.
  • 171
  • 8
  • I tried a bit of the above. The `someFnc <*> constant` (not videoInput) returns nil and `someFnc <*> variable` returns {(0 elements)} which is strange enough already. My best guess: drop it and wait for half a year :-/ – qwerty_so Jan 28 '15 at 14:18

2 Answers2

4

Here is what happens:

infix operator <*> { associativity left precedence 150 }

func <*> <A, B>(wrappedFunction: (A -> B)?, optional: A?) -> B? {
    println(optional)
    if let f = wrappedFunction {
        if let value = optional {
            return f(value)
        }
    }
    return nil
}

func someFnc(obj: AnyObject!) {
    println("lol, wat?")
}

let constant: AnyObject? = nil
let result1: ()? = someFnc <*> constant
var variable: AnyObject? = nil
let result2: ()? = someFnc <*> variable

The result is:

nil
Optional(nil)
lol, wat?

So, our var variable: AnyObject? becomes a nested optional AnyObject?? once it enters the <*> function. Its value is then Optional(nil), which is not nil, so the wrapped function is called on it. Yet another workaround could be to flatten the nested optional:

let result2: ()? = someFnc <*> variable?

But we don't need a workaround here. It's not a bug, there is a subtle error in the code. The problem is that someFnc has wrong signature. You use AnyObject!, which is implicitly unwrapped optional, so the signature of the function is AnyObject! -> (). But the <*> operator requires (A -> B)?, not (A! -> B)?. Just change func someFnc(obj: AnyObject!) to func someFnc(obj: AnyObject) and it will work. I'm not really sure why the problem appeared only while using variable, and not with the constant.

That said, I don't think that the applicative operator should be used with variables and for side effects, as in this case. Also, avoid implicitly unwrapped optionals as much as possible. We saw how nasty they are. They allowed us to use wrong function without any warning.

EDIT:

I did few tests and there is definitely some weirdness going on:

func upperCase1(string: String?) -> String {
    println("calling upperCase1...")
    return string?.uppercaseString ?? ""
}

func upperCase2(string: String?) -> String? {
    println("calling upperCase2...")
    return string?.uppercaseString
}

func upperCase3(string: String) -> String? {
    println("calling upperCase3...")
    return string.uppercaseString
}

func upperCase4(string: String) -> String {
    println("calling upperCase4...")
    return string.uppercaseString
}

let constant: String? = nil
var variable: String? = nil

let mappedConstant1 = map(constant, upperCase1) // nil
let mappedVariable1 = map(variable, upperCase1) // Some("")

let mappedConstant2 = map(constant, upperCase2) // nil
let mappedVariable2 = map(variable, upperCase2) // Some(nil)

let mappedConstant3 = map(constant, upperCase3) // nil
let mappedVariable3 = map(variable, upperCase3) // nil

let mappedConstant4 = map(constant, upperCase4) // nil
let mappedVariable4 = map(variable, upperCase4) // nil

If we use map method instead of the global function, everything is nil in all the cases and none of the upperCase[N] functions is called. There is no difference in behaviour between regular and implicitly unwrapped optionals. The fact that variables and constants behave differently is not the worst thing here. The worst thing is that the compiler allows us to call upperCase1 and upperCase2. The only two functions that should be allowed in this case are upperCase3 and upperCase4, and the good news is that those two functions work correctly with variables.

Ivica M.
  • 4,763
  • 1
  • 23
  • 16
1

The problem can be simplified as:

func foo<A>(v: A?, f:(A) -> Void) {
    println(v)
}

let constant: Int? = nil
var variable: Int? = nil
func someFunc(x: Int!) {}

foo(constant, someFunc)
foo(variable, someFunc)

output:

nil
Optional(nil)

So, with my assumption:

  • foo(constant, someFunc) is interpreted as
    foo(constant as Int?, someFunc as (Int) -> Void)
  • foo(variable, someFunc) is interpreted as
    foo(variable as Int!?, someFunc as (Int!) -> Void)

There is some ambiguousness here:

// `(Int!) -> Void` can be cast to `(Int) -> Void`
func f(Int!) {}
let f2 = f as (Int) -> ()

// `Int?` can be cast to `Int!?`
let i:Int? = 1
let i2 = i as Int!?

In this case, I think

  • constant is stronger than parameter.
  • parameter is stronger than variable.

I'm not sure this can be considered as a bug. It's very confusing at least.

rintaro
  • 51,423
  • 14
  • 131
  • 139