我可以使用自定义赋值运算符初始化变量吗?

时间:2015-01-07 17:42:28

标签: swift operator-overloading variable-assignment

最近我在Swift中尝试了赋值运算符并遇到了一个问题,我无法找到解决方案。

考虑以下架构:

struct Box<T> {

    let value: T

}

struct MyStruct {

    let myProperty: String

    init(boxedProperty: Box<String>) {
        self.myProperty = boxedProperty.value
    }

}

看起来非常简单,但想象MyStruct接受20个盒装属性 - 每个属性都需要通过访问value实例的Box属性来“取消装箱”。现在假设有MyStruct的20个版本。

这将导致数百条.value行,这是非常多的。相反,为了减少代码混乱,我想使用自定义赋值运算符,它隐式地“取消装箱”Box并将其值赋给变量。

考虑这个简单的例子(没有选项支持等):

infix operator <|= {
    associativity right
    precedence 90
    assignment
}

func <|= <T>(inout lhs: T, rhs: Box<T>) {
    lhs = rhs.value
}

理想情况下,我想立即使用<|=运算符,如下所示:

init(boxedProperty: Box<String>) {
    self.myProperty <|= boxedProperty
}

但遗憾的是(而且非常可预测?),这不起作用,因为myProperty变量在用于函数之前未被初始化:

error: variable 'self.myProperty' passed by reference before being initialized
    self.myProperty <|= boxedProperty
                    ^

我可以确保编译器我的assignment运算符函数始终初始化变量(a.k.a。其lhs操作数)吗?此外,如果您对不同的方法有所了解,请随时发表评论。


注意:实际案例更复杂,需要更多的“拆箱”,而不仅仅是访问value结构的Box属性。

4 个答案:

答案 0 :(得分:2)

您不能按照您的描述方式执行此操作,因为赋值运算符使用表达式中的两个操作数,您可以使用未初始化的变量。那么使用返回盒装值的前缀运算符呢?

prefix operator <| { }

prefix func <|<T>(rhs: Box<T>) -> T {
    return rhs.value
}

// ...

init(boxedProperty: Box<String>) {
    self.myProperty = <|boxedProperty
}

答案 1 :(得分:1)

没有理由使用语法来实现您的目标。只需定义一个用“几行”封装“switch语句”的函数。确保该功能与let绑定,以便init()内可以访问该功能:

struct Box<T> { 
  let value: T 
  init (t:T) { 
    self.value = t 
  } 
}    

struct MyStruct { 
  let myProp: String 
  let myUnboxer : Box<String> -> String = { (b:Box<String>) in 
     /* switch, 5-10 lines */
     return b.value 
  } 

  init (bp:Box<String>) { 
    self.myProp = myUnboxer(bp) 
  } 
} 

行动中:

 15> var ms = MyStruct(bp:Box(t:"abc")) 
ms: MyStruct = {
  myProp = "abc"
  myUnboxer =
}
 16> ms.myProp
$R0: String = "abc"

“费用”是myUnboxer的“额外”实例变量,但当拆箱的详细信息变为特定结构时,这很快就会成为优势 - 此时您将初始化MyStruct与unboxer一样。

对我来说,在一种具有闭包和一流功能的语言中,发明语法通常是错误的 - 除非有人主动定义子语言。使用特殊评估规则(又称“语法”)的语句会让人感到困惑。

答案 2 :(得分:0)

我使用枚举来存储对象的属性,如下所示:

enum TextFieldProps {
    case StringValue(String)
    case Editable(Bool)
}

然后我对NSTextField有一个扩展,其中包含一个名为assignProps的函数,该函数采用TextFieldProps数组:

func assignProps(tfp: [TextFieldProps]) {
    tfp.reduce(self) { prop in
        switch prop {
            case .StringValue(let value):
                self.stringValue = value
            case .Editable(let value):
                self.editable = value
        }
        return self
    }
}

当我需要设置或更改NSTextField的属性时,允许我将TextFieldProps的数组传递给assign,使用reduce一次更新所有内容

我认为你试图使用类似的东西,只有一个通用的Box来保存它里面的值。也许有一种方法可以修改上面的技术以适应您的目的。如果不了解更多关于枚举结构和您正在使用的Box ed值,很难给出明确的答案。

我可以说的是,如果您尝试设置20个属性的值,那么无论您是否使用自定义运算符,都会有20个分配调用。

当我使用Box类型的对象(我有时会这样做)时,我有几个不同的自定义函数。其中一个叫做o,用于&#34;打开&#34;:

func o(b: Box<T>) -> T {
    return b.value
}

// Now you can use it like this:

init(b: Box<String>) {
    self.property = o(b)
}

我知道它并没有直接回答您询问的有关自定义赋值运算符的问题,但是如果您将init标签缩短为b并使用名为o的函数,那么您就是&#39 ;我把整个事情归结为4个字符,o(b)。对于我而言,o(b)对我来说简直有效,直观地看起来像是opened box的简写,所以代码不会太混乱。

您可能希望限制o的范围,以便框架的后续用户可以根据需要使用o作为变量名称。您实际上可以在o MyStruct函数中创建init,如下所示:

struct MyStruct {
    let myProperty: String

    init(b: Box<String>) {
        let o: Box<String> -> String = { $0.value }
        self.myProperty = o(b)
    }
}

o现在是一个闭包,在init函数内部定义,并且范围仅限于该函数。您仍然可以在代码中的任何其他位置使用o作为变量名,而不会发生任何冲突。

答案 3 :(得分:0)

我认为这可以通过使用默认属性值来解决。例如。类似的东西:

struct Box<T> {

    private let value: T? = nil

    init(value: T) {
        self.value = value
    }

}

struct MyStruct {

    let myProperty: String = ""

    init(boxedProperty: Box<String>) {
        self.myProperty <|= boxedProperty
    }

}

infix operator <|= { associativity right precedence 90 assignment }

func <|= <T>(inout lhs: T, rhs: Box<T>) {
    lhs = rhs.value!
}

var box = Box<String>(value: "foo")

var myStruct = MyStruct(boxedProperty: box)