关于 Scala 的赋值和 setter 方法

发布于 2024-10-17 02:41:54 字数 3083 浏览 2 评论 0原文

编辑:引发此问题的错误现已修复。< /strong>


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

赋值给的解释 一个简单的变量 x = e 取决于 x 的定义。如果 x 表示 可变变量,然后赋值 将 x 的当前值更改为 评估的结果 表达式 e. e 的类型是 期望符合 x 的类型。 如果 x 是无参数函数 在某些模板中定义,并且相同 模板包含一个 setter 函数 x_= 作为成员,则赋值 x = e 被解释为调用 该设置函数的 x_=(e) 。 类似地,赋值 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
}

那么它就不再工作了;如果我写入 aa = 10,我会收到错误:重新分配给 val。有趣的是,例如,它仍然可以在没有类型参数和隐式参数列表的情况下工作。

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

所以我有三个问题:

  1. 有解决方法吗?
  2. 当前的行为是否应该被视为错误?
  3. 为什么编译器强制使用 getter 方法来允许使用 setter 的语法糖?

更新

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

我正在 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
  )
)

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

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

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

......但这仅在 getter 也存在时才有效。太棒了:无论如何我都需要 getter 来实现使用 <=> 的数据绑定。像这样的事情:

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

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

我希望这现在能让事情变得更清楚一些。感谢您的阅读...

Edit: The bug which prompted this question has now been fixed.


In the Scala Reference, I can read (p. 86):

The interpretation of an assignment to
a simple variable x = e depends on the
definition of x. If x denotes a
mutable variable, then the assignment
changes the current value of x to be
the result of evaluating the
expression e. The type of e is
expected to conform to the type of x.
If x is a parameterless function
defined in some template, and the same
template contains a setter function
x_= as member, then the assignment x =
e is interpreted as the invocation
x_=(e) of that setter function.
Analogously, an assignment f .x = e to
a parameterless function x is
interpreted as the invocation f.x_=(e).

So, for instance, something like this works fine:

class A {
  private var _a = 0
  def a = _a
  def a_=(a: Int) = _a = a
}

I can then write

val a = new A
a.a = 10

But if I define the class like this, adding a type parameter to method a:

class A {
  private var _a = 0
  def a[T] = _a
  def a_=(a: Int) = _a = a
}

then it doesn't work any more; I get an error: reassignment to val if I write a.a = 10. Funny enough, it still works with no type parameter and an implicit parameter list, for instance.

Arguably, in this example, the type parameter is not very useful, but in the design of DSLs, it would be great to have the setter method called even if the getter has type parameters (and by the way, adding type parameters on the setter is allowed and works fine).

So I have three questions:

  1. Is there a workaround?
  2. Should the current behavior be considered a bug?
  3. Why does the compiler enforce a getter method to allow using the syntactic sugar for the setter?

UPDATE

Here's what I'm really trying to do. It's rather long, sorry, I meant to avoid it but I realized it was more confusing to omit it.

I'm designing GUIs with SWT in Scala, and having huge fun using Dave Orme's XScalaWT, which immensely reduces the amount of needed code. Here's an example from his blog post on how to create an SWT Composite that converts °C to °F degrees:

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

The argument to each of the widget-constructing methods is of type (WidgetType => Any)*, with a few useful implicit conversions, which for instance allow to directly specify a string for widgets that have a setText() method. All constructor functions are imported from a singleton object.

In the end, I'd like to be able to write something along these lines:

val fieldEditable = new WritableValue // observable value

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

This would bind the editable property of the textfield to the selection of the checkbox through the WritableValue variable.

First: named arguments are not applicable here, so the line editable = false has to come from somewhere. So, along the widget-constructing methods in the singleton object, I could write, conceptually,

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

... but this works only if the getter is also present. Great: I'd need the getter anyway in order to implement databinding with <=>. Something like this:

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

If this worked, life would be good because I can then define <=> in BindingMaker and I can use this nice syntax. But alas, the type parameter on the getter breaks the setter. Hence my original question: why would this simple type parameter affect whether the compiler decides to go ahead with the syntactic sugar for calling the setter?

I hope this makes it a bit clearer now. Thanks for reading…

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

伴随着你 2024-10-24 02:41:54

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

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

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

UPDATE Deleted the entire previous answer in light of new information.

There's a lot of very odd stuff going on here, so I'm going try try and explain my understanding of what you have so far:

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

This is a setter method, and exists purely so that it can give the appearance of beinng a named parameter
in your DSL. It sets nothing and actually returns a Function.

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

This is calling the textField factory method, with what looks like a named param, but is actually the setter method defined previously.

Amazingly, the approach seems to work, despite my initial concern that the compiler would recognize this as a named parameter and produce a syntax error. I tested it with simple monomorphic (non-generic) methods, though it does require the getter method to be defined for the setter to be seen as such - a fact that you've already noted.

Some amount of "cleverness" is often required in writing a DSL (where it would otherwise be totally forbidden), so it's no surprise that your original intent was unclear. This is perhaps a completely new technique never before seen in Scala. The rules for setter and getter definitions were based on using them as getters and setters, so don't be surprised if things crack a little when you push at the boundaries like this.

It seems the real problem here is the way you're using type params. In this expression:

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

The compiler has no way of inferring a particular T from the supplied argument, so it will take the most general type allowed (HasEditable in this case). You could change this behaviour by explicitly supplying a type param when using the method, but that would seem to defeat the entire point of what you're seeking to achieve.

Given that functions can't be generic (only methods can), I doubt that you even want type bounds at all. So one approach you could try is to just drop them:

def editable_=(value: Boolean) = (subject: HasEditable) => subject.setEditable(value)
def editable = new BindingMaker((widget: HasEditable) => SWTObservables.observeEditable(widget))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文