О назначениях Scala и методах установки - PullRequest
2 голосов
/ 11 февраля 2011

Редактировать: ошибка, вызвавшая этот вопрос , была исправлена ​​.


В справочнике Scala я могу прочитать (стр. 86):

Интерпретация присваивания простой переменной x = e зависит от определения x.Если x обозначает непостоянную переменную, то присваивание изменяет текущее значение x на результат вычисления выражения e.Ожидается, что тип e будет соответствовать типу x.Если x - это функция без параметров, определенная в некотором шаблоне, и тот же шаблон содержит функцию-установщик 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
}

, он не будетработать больше;Я получаю error: reassignment to val, если я пишу a.a = 10.Как ни странно, он по-прежнему работает без параметра типа и списка неявных параметров, например.

Возможно, в этом примере параметр типа не очень полезен, но при проектировании DSL было бы здоровочтобы метод метода вызывался, даже если метод имеет параметры типа (и, между прочим, добавление параметров типа в метод разрешено и работает нормально).

Итак, у меня три вопроса:

  1. Есть ли обходной путь?
  2. Следует ли считать текущее поведение ошибкой?
  3. Почему компилятор принудительно применяет метод получения, позволяющий использовать синтаксический сахар для метода установки?

ОБНОВЛЕНИЕ

Вот что я действительно пытаюсь сделать.Это довольно долго, извините, я хотел избежать этого, но понял, что опускать его было бы более запутанно.

Я проектирую GUI с SWT в Scala и получаю огромное удовольствие от использования XScalaWT Дэйва Орма , что значительно уменьшает объем необходимого кода.Вот пример из его сообщения в блоге о том, как создать SWT Composite, который конвертирует ° C в ° F градусов:

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)

... но это работает, только если есть и геттер.Отлично: мне все равно нужен геттер для реализации привязки данных с помощью <=>.Примерно так:

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

Если бы это сработало, жизнь была бы хорошей, потому что я могу определить <=> в BindingMaker и использовать этот приятный синтаксис.Но, увы, параметр типа на получателе нарушает установщик.Отсюда мой первоначальный вопрос: почему этот простой параметр типа влияет на то, решит ли компилятор продолжить использование синтаксического сахара для вызова метода установки?

Надеюсь, теперь это станет понятнее.Спасибо за чтение…

1 Ответ

4 голосов
/ 11 февраля 2011

ОБНОВЛЕНИЕ Удалил весь предыдущий ответ в свете новой информации.

Здесь происходит много очень странных вещей, поэтому я попытаюсь попытаться объяснить мое понимание того, что у вас есть до сих пор:

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

Это метод установки и существует чисто , так что он может создавать видимость именованного параметра. в вашем DSL. Он ничего не устанавливает и фактически возвращает функцию.

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

Это вызов фабричного метода textField, с которым выглядит как именованный параметр, но на самом деле это метод сеттера, определенный ранее.

Удивительно, но этот подход, похоже, работает, несмотря на мое первоначальное опасение, что компилятор распознает это как именованный параметр и выдаст синтаксическую ошибку. Я протестировал его с помощью простых мономорфных (неуниверсальных) методов, хотя для этого нужно, чтобы метод getter был определен так, чтобы сеттер рассматривался как таковой - факт, который вы уже отметили.

При написании DSL часто требуется определенное количество «умов» (в противном случае это было бы совершенно запрещено), поэтому неудивительно, что ваше первоначальное намерение было неясным. Возможно, это совершенно новая техника, никогда не встречавшаяся в Scala. Правила для определения сеттеров и геттеров основывались на их использовании в качестве геттеров и сеттеров, поэтому не удивляйтесь, если что-то треснет, когда вы продвигаетесь по таким границам.

Кажется, настоящая проблема здесь в том, как вы используете параметры типа. В этом выражении:

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