2

编辑:提示此问题的错误现已修复


在 Scala Reference 中,我可以阅读(第 86 页):

对简单变量 x = e 赋值的解释取决于 x 的定义。如果 x 表示可变变量,则赋值将 x 的当前值更改为计算表达式 e 的结果。e 的类型应该符合 x 的类型。如果 x 是在某个模板中定义的无参数函数,并且同一模板包含一个 setter 函数 x_= 作为成员,则赋值 x = e 被解释为该 setter 函数的调用 x_=(e)。类似地,对无参数函数 x 的赋值 f.x = e 被解释为调用 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 方法以允许使用 setter 的语法糖?

更新

这就是我真正想做的事情。它相当长,抱歉,我本打算避免使用它,但我意识到省略它会更令人困惑。

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

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) })
  )
)

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

最后,我希望能够按照这些思路写一些东西:

val fieldEditable = new WritableValue // observable value

composite(
  textField(
    editable <=> fieldEditable,
    editable = false
  ),
  checkbox(
    caption = "Editable",
    selection <=> fieldEditable
  )
)

这将通过 WritableValue 变量将文本字段的可编辑属性绑定到复选框的选择。

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

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

...但这仅在吸气剂也存在时才有效。太好了:无论如何我都需要 getter 来实现与 <=> 的数据绑定。像这样的东西:

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

如果这行得通,生活会很好,因为我可以在 BindingMaker 中定义 <=> 并且我可以使用这个很好的语法。但可惜的是,getter 上的类型参数破坏了 setter。因此我最初的问题是:为什么这个简单的类型参数会影响编译器是否决定继续使用语法糖来调用 setter?

我希望这使它现在更清楚一点。感谢阅读……</p>

4

1 回答 1

4

更新根据新信息删除了整个先前的答案。

这里发生了很多非常奇怪的事情,所以我将尝试解释我对您到目前为止所拥有的理解:

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

这是一个 setter 方法,它的存在纯粹是为了让它看起来像是 DSL 中的命名参数。它什么都不设置,实际上返回一个函数。

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

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

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

编写 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))
于 2011-02-11T09:55:42.403 回答