关于Scala的赋值和setter方法

时间:2011-02-11 09:38:41

标签: scala assignment-operator syntactic-sugar type-parameter

编辑:提示此问题的错误has now been fixed


在Scala参考资料中,我可以阅读(第86页):

  

对作业的解释   一个简单的变量x = e取决于   x的定义。如果x表示a   可变变量,然后是赋值   将x的当前值更改为   评估结果   表达e的类型是   期望符合x的类型。   如果x是无参数函数   在某些模板中定义,并且相同   模板包含一个setter函数   x_ =作为成员,然后赋值x =   e被解释为调用   x _ =(e)该setter函数。   类似地,赋值f .x = e到   无参数函数x是   解释为调用f.x _ =(e)。

所以,例如,这样的事情很好:

class A {
  private var _a = 0
  def a = _a
  def a_=(a: Int) = _a = a
}
然后我可以写

val a = new A
a.a = 10

但是如果我像这样定义类,则在方法a:

中添加一个类型参数
class A {
  private var _a = 0
  def a[T] = _a
  def a_=(a: Int) = _a = a
}

然后它不再起作用了;如果我写error: reassignment to val,我会得a.a = 10。有趣的是,它仍然可以使用没有类型参数和隐式参数列表,例如。

可以说,在这个例子中,类型参数不是很有用,但是在DSL的设计中,即使getter具有类型参数(也就是说,添加类型参数),调用setter方法会很棒。在setter上是允许的并且工作正常。)

所以我有三个问题:

  1. 有解决方法吗?
  2. 当前行为应该被视为错误吗?
  3. 为什么编译器会强制使用getter方法来允许使用setteric糖作为setter?
  4. 更新

    这就是我真正想做的事情。这是相当长的,对不起,我打算避免它,但我意识到省略它会更加困惑。

    我正在使用Scala中的SWT设计GUI,并使用Dave Orme的XScalaWT获得巨大的乐趣,这极大地减少了所需代码的数量。以下是他的博客文章中有关如何创建将°C转换为°F度的SWT Composite的示例:

    var fahrenheit: Text = null
    var celsius: Text = null
    
    composite(
      _.setLayout(new GridLayout(2, true)),
    
      label("Fahrenheit"),
      label("Celsius"),
    
      text(fahrenheit = _),
      text(celsius = _),
    
      button(
        "Fahrenheit => Celsius",
        {e : SelectionEvent => celcius.setText((5.0/9.0) * (fahrenheit - 32)) }
      ),
      button(
        "Celsius -> Fahrenheit",
        {e : SelectionEvent => fahrenheit.setText((9.0/5.0) * celsius + 32) })
      )
    )
    

    每个窗口小部件构造方法的参数都是(WidgetType => Any)*类型,带有一些有用的隐式转换,例如,它允许直接为具有setText()方法的窗口小部件指定字符串。所有构造函数都是从单个对象导入的。

    最后,我希望能够按照这些方式写出一些内容:

    val fieldEditable = new WritableValue // observable value
    
    composite(
      textField(
        editable <=> fieldEditable,
        editable = false
      ),
      checkbox(
        caption = "Editable",
        selection <=> fieldEditable
      )
    )
    

    这会将textfield的editable属性绑定到通过WritableValue变量选中复选框。

    首先:命名参数在这里不适用,所以行editable = false必须来自某个地方。因此,沿着单例对象中的小部件构造方法,我可以在概念上编写

    def editable_=[T <: HasEditable](value: Boolean) = (subject: T) => subject.setEditable(value)
    

    ...但仅当吸气剂也存在时才有效。太棒了:无论如何我都需要getter才能用&lt; =&gt;实现数据绑定。像这样:

    def editable[T <: HasEditable] = new BindingMaker((widget: T) => SWTObservables.observeEditable(widget))
    

    如果这样做有效,生活会很好,因为我可以定义&lt; =&gt;在BindingMaker中,我可以使用这个很好的语法。但是,getter上的type参数打破了setter。因此我的原始问题是:为什么这个简单的类型参数会影响编译器是否决定继续使用语法糖来调用setter?

    我希望现在让它更清晰一些。谢谢你的阅读...

1 个答案:

答案 0 :(得分:4)

更新根据新信息删除了之前的全部答案。

这里有很多非常奇怪的东西,所以我试着尝试解释一下我对你到目前为止的理解:

def editable_=[T <: HasEditable](value: Boolean) = (subject: T) => subject.setEditable(value)

这是一个setter方法,并且纯粹,因此它可以给出一个命名参数的外观 在您的DSL中。它什么也没设置,实际上返回一个函数。

textField(
  editable <=> fieldEditable,
  editable = false
)

这是调用textField工厂方法,其中看起来像一个命名的参数,但实际上是先前定义的setter方法。

令人惊讶的是,尽管我最初担心编译器会将此识别为命名参数并产生语法错误,但该方法似乎仍然有效。我用简单的单态(非泛型)方法对它进行了测试,虽然它确实需要为setter定义getter方法才能看到 - 这是你已经注意到的事实。

在编写DSL时经常需要一些“聪明”(否则它将被完全禁止),因此您的原始意图不清楚也就不足为奇了。这可能是Scala之前从未见过的全新技术。 setter和getter定义的规则是基于将它们用作getter和setter,所以当你在这样的边界上推进时,如果事情有点破裂,不要感到惊讶。

这里真正的问题似乎是你使用类型参数的方式。在这个表达式中:

def editable_=[T <: HasEditable](value: Boolean) = (subject: T) => subject.setEditable(value)

编译器无法从提供的参数中推断出特定的T,因此它将采用允许的最通用类型(在这种情况下为HasEditable)。您可以通过在使用该方法时显式提供类型参数来更改此行为,但这似乎会打败您要实现的所有目标。

鉴于函数不能是通用的(只有方法可以),我怀疑你甚至想要类型边界。所以你可以尝试的一种方法是放弃它们:

def editable_=(value: Boolean) = (subject: HasEditable) => subject.setEditable(value)
def editable = new BindingMaker((widget: HasEditable) => SWTObservables.observeEditable(widget))