通用函数中的Swift可选绑定

时间:2015-01-28 13:37:38

标签: swift binding functor optional applicative

可爱的越野车迅速让我再次惊讶。 在我的新项目中,我使用了<*>运算符的可选应用仿函数,描述了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
}

这真的很酷,直到我发现一个奇怪的崩溃。我将以简化版本展示它。

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

那么,输出会是什么?什么都没有,你会期待什么? 如果我们传递变量怎么办?

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

你告诉我这个,但我的控制台说:

lol, wat?

是的,没错,如果我们传递var,它会愉快地通过可选的绑定检查。 我为NSObject和一些Swift结构/类尝试了这个,如果我传递常量一切正常,如果它是一个变量,一些魔法正在进行。

更新

描述魔法,var<*>函数嵌套Optionallet不嵌套。

这种隐含的行为有没有合理的解释?

解决方法

对于变通方法,您可以将变量包装在某些函数中:

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

或者在计算属性中:

var variable: AnyObject? = nil
var getVariable: AnyObject? {
    return variable
}
someFnc <*> getVariable

2 个答案:

答案 0 :(得分:4)

以下是发生的事情:

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

结果是:

nil
Optional(nil)
lol, wat?

因此,我们的var variable: AnyObject?一旦进入AnyObject??函数,就会成为嵌套的可选<*>。它的值是Optional(nil),它不是nil,因此在它上面调用包装函数。另一种解决方法可能是展平嵌套的可选项:

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

但我们这里不需要解决方法。这不是一个错误,代码中有一个微妙的错误。问题是someFnc签名错误。您使用AnyObject!,它是隐式展开的可选项,因此函数的签名为AnyObject! -> ()。但<*>运算符需要(A -> B)?,而不是(A! -> B)?。只需将func someFnc(obj: AnyObject!)更改为func someFnc(obj: AnyObject)即可。我不确定为什么只在使用变量时出现问题,而不是使用常量。

那就是说,我不认为应用运算符应该与变量和副作用一起使用,就像在这种情况下一样。另外,尽可能避免隐式解包的选项。我们看到他们有多讨厌。他们允许我们在没有任何警告的情况下使用错误的功能。

修改

我做了很少的测试,肯定有一些奇怪的事情发生了:

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

如果我们使用map方法而不是全局函数,则在所有情况下都是nil,并且不调用任何upperCase[N]函数。常规和隐式展开的选项之间的行为没有区别。变量和常量表现不同的事实并不是最糟糕的事情。最糟糕的是编译器允许我们调用upperCase1upperCase2。在这种情况下应该允许的唯一两个函数是upperCase3upperCase4,好消息是这两个函数可以正常使用变量。

答案 1 :(得分:1)

问题可以简化为:

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)

输出:

nil
Optional(nil)

所以,我的假设是:

  • foo(constant, someFunc)被解释为
    foo(constant as Int?, someFunc as (Int) -> Void)
  • foo(variable, someFunc)被解释为
    foo(variable as Int!?, someFunc as (Int!) -> Void)

这里有一些含糊不清的地方:

// `(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!?

在这种情况下,我认为

  • 常数比参数强。
  • 参数比变量强。

我不确定这可以被视为一个错误。它至少令人困惑。