Почему в Scala невозможно заменить var на def?

Хотя я понимаю, почему var не может переопределить val в подклассе и наоборот, я не могу понять, почему Scala не позволяет def в подклассе переопределять var в суперклассе.

class Car {
  var age = 32
}

class SedanCar extends Car {
  override def age = 54
}

Поскольку var является изменяемым, почему бы не позволить def переопределить его? Может ли кто-нибудь помочь мне понять это?


person JavaMan    schedule 23.12.2014    source источник
comment
Потому что defs — это не изменяемые переменные, а определения методов. Что вы хотите, чтобы произошло, если вы сделаете age = 32 в SedanCar?   -  person The Archetypal Paul    schedule 23.12.2014
comment
Я думаю, что вопрос ОП вполне разумен. Scala часто представляют как придерживающуюся принципа унифицированного доступа, и на то есть веские причины. Тогда было бы разумно рассматривать определение age var как не что иное, как определение пары методов получения и установки (age и age_=). В этом случае я определенно должен иметь возможность переопределить age, что фактически переопределит только геттер и оставит сеттер как есть.   -  person Régis Jean-Gilles    schedule 23.12.2014


Ответы (2)


Это связано с принципом подстановки Лисков: вы не можете назначать более слабые права доступа в подклассе (даже для Java). Преобразование var в def делает сеттер def x_= (y: T ): Unit закрытым (как сказал @Staix). Таким образом, в случае, когда Seadan Car имеет формальный тип Car - к нему не следует обращаться, но компилятор вообще не может найти такие случаи (во время компиляции известны только формальные типы), поэтому такое поведение отключено, как и любая более слабая привилегия:

 val car2 = new SeadanCar

 car.age = 422 //compiler error, can't mutate "def age"

 val car: Car = new SeadanCar

 car.age = 42 //now you had mutated immutable

Суть принципа заменяемости заключается в том, что поведение не должно изменяться после приведения к супертипу.

С другой стороны, scala может переопределить только геттерную часть переменной, как сказал @Régis Jean-Gilles, но это не столь очевидное решение, потому что интуитивно пользователь ожидает, что var станет неизменным после override def. И это на самом деле нарушает принцип единого доступа, поскольку вы должны рассматривать var как две службы (читатель и писатель) вместо одного, а UAP-совместимость требует обратного: и читатель, и писатель должны быть представлены одной унифицированной нотацией.

P.S. На самом деле ваш вопрос указывает на неполноту совместимости scala с UAP. Как я уже сказал, переопределение только считывателя var и оставление записи как есть будет несовместимо с UAP — это блокирует возможность переопределения самого var (так что вы не можете переопределить var обоими способами: вычислительным и подобным хранилищу), даже несмотря на то, что вы уже не может переопределить его из-за LSP. Но текущее решение scala также проблематично. Вы можете обнаружить, что можете:

 trait Car { def age: Int = 7; def age_=(a: Int) = {}}

 class SeadanCar extends Car { override def age: Int = 5}

Но не могу

 // just repeating your example

 trait Car { var age: Int = 7 } 

 class SeadanCar extends Car { override def age: Int = 5}

Таким образом, наследование scala несовместимо с UAP. ИМХО, большая проблема в том, что читатель и сам var имеют одинаковые имена - поэтому их не различить (при определении, а не доступе). Я бы решил это с чем-то вроде:

 trait Car { def age_: Int = 7; def age_=(a: Int) = {}}
 class SeadanCarReadOnly extends Car { override def age: Int = 5} //can't compile as reader is closed
 class SeadanCarVar extends Car { override var age: Int = 5} 
 class SeadanCarReadOnly extends Car { override def age_: Int = 5}

 trait Car2 { var age = 100500 }
 class SeadanCarReadOnly extends Car2 { override def age_: Int = 5}

Обратите внимание, что переопределение age_ в моем предложенном примере должно привести к:

scalaxx> (new SeadanCarReadOnly).age //call age_ here
resxx: Int = 5

scalaxx> (new SeadanCarReadOnly).age_
resxx: Int = 5

Не как:

 trait Car2 { @BeanProperty var age = 100500 }
 class SeadanCarReadOnly extends Car2 { override def getAge: Int = 5}

 //which leads to inconsistency:

 scala> (new SedanCar()).age
 res6: Int = 30

 scala> (new SedanCar()).getAge
 res7: Int = 54

Конечно, такой подход должен отключить одновременное переопределение var age и def age_; def age_=:

 trait Car2 { var age = 100500 }
 class SeadanCarReadOnly extends Car2 { 
    override var age = 17; 
    override def age_: Int = 5 //should be compile error here
 }

но это сложно быстро реализовать на языке Scala из-за обратной совместимости

PS/2 Просто отметим, что в части изменчивости/неизменяемости вопроса вы определенно не можете этого сделать (из-за LSP):

 trait Car { var age: Int = 32 } //or without initial value
 class SedanCar extends Car { override val age = 42 }

И, из-за LSP + UAP, не должно быть возможности сделать это:

 trait Car { def age: Int = 7; def age_=(a: Int) = {}}
 class SedanCar extends Car { override val age = 42 }

несмотря на то, что ты можешь :)

person dk14    schedule 23.12.2014
comment
Я могу купить часть того, что ожидают пользователи, но я с уважением не согласен с тем, что переопределение только геттера нарушит UAP. В конце концов, при этом вы все еще можете читать или писать age, ссылаясь только на age. Я не понимаю, как это может быть более единообразным обозначением. Я могу предположить причины этого ограничения, но нарушение UAP не является одной из них, а совсем наоборот. - person Régis Jean-Gilles; 24.12.2014
comment
В качестве примечания мне на самом деле все равно, что нельзя переопределить геттер в одиночку. Но на самом деле вы вообще не можете переопределить переменную, и это облом. - person Régis Jean-Gilles; 24.12.2014
comment
1) var является унифицированной записью, get + set - нет. когда вы говорите, давайте переопределить только получить! - вы явно предпочитаете второе и подталкиваете пользователя к размышлениям о геттерах/сеттерах. 2) я думаю, что мой пример с формальным типом достаточно репрезентативен, чтобы понять, почему вы вообще не можете переопределить var. - person dk14; 24.12.2014
comment
1*) другими словами, если я хочу забыть о том, как реализован мой сервис - переопределение всего var поможет, поскольку это не зависит от того, что такое мой сервис: вычисление (я переопределяю методы установки и получения , так что не нужно думать о них) или хранилище (переменная - это, естественно, хранилище). Переопределение только читателя напоминает мне, что это фактически реализовано посредством вычислений (поэтому это только метод). - person dk14; 24.12.2014
comment
2*) переопределение целой переменной с помощью def делает ее доступной только для чтения, что определенно нарушает LSP: если car.age = 42 не работает, то и (car: Car).age = 42 не должно - иначе просто случайная передача вашего SeadanCar какой-либо функции, работающей с Car, сделает ее изменяемой. - person dk14; 24.12.2014
comment
var — это унифицированная запись, а get + set — нет: как так? В любом случае, я мог бы ответить по пунктам, но я попытаюсь обобщить свою точку зрения как таковую: скажем, я определяю пару age/age_=. Я получу доступ к age, как и к любой другой переменной, и в этом смысл UAP. Теперь, если я переопределяю age, на самом деле я переопределяю геттер. Все нормально и ЛСП не сломан. В спецификации сказано, что var x: T эквивалентно def x: T; def x_= (y: T ): Unit, но когда дело доходит до переопределения, это не так. Это все, что я говорю. Что касается переопределения целой переменной, ваш ответ точно правильный. - person Régis Jean-Gilles; 25.12.2014
comment
я понял вашу точку зрения о переопределении get (и оставлении set как есть) с самого начала :), но моя точка зрения такова: переопределение только читателя (как вы сказали) напоминает мне, что оно фактически реализовано посредством вычислений (так что это только метод) даже если вы не настаиваете на том, чтобы сеттер был закрытым. Вы не можете переопределить только средство чтения для службы, реализованной как состояние (это просто сплошное поле). UAP означает, что пользователь не должен думать о читателе или писателе: не выдавать, реализованы ли они через хранилище или через вычисления (википедия). Так что да, с LSP все в порядке, но не с UAP. - person dk14; 25.12.2014
comment
С чем я категорически не согласен (предполагаемая связь с UAP), но я могу получить аргумент в пользу того, чтобы рассматривать var как нечто большее, чем геттер и сеттер (хотя это может быть неудобно во многих практических случаях). Я даже не выступаю за то, чтобы этот выбор дизайна был изменен, кстати, я просто указываю, что здесь есть действительно интересная проблема и что вопрос ОП был более актуальным (в зависимости от того, как вы подходите к проблеме), чем многие, казалось, полагали (особенно учитывая все минусы) - person Régis Jean-Gilles; 25.12.2014
comment
это может немного сбивать с толку, потому что здесь я применил UAP к наследованию (поскольку это также форма доступа). Например, вы сказали о покупке той части, которую ожидают пользователи :). Так почему же пользователь ожидает, что def переопределит все var, а не только чтение? Я хочу сказать, что это потому, что пользователь не хочет знать о том, как реализовано var: использование хранилища или некоторых читателей/писателей; он просто хочет работать с var как с var (что естественно). - person dk14; 25.12.2014
comment
К сожалению, реализация UAP в scala несовместима, насколько это возможно: trait Ttt { def a: Int = 7; def a_=(a: Int) = {}}; class Aaa2 extends Ttt { override def a: Int = 5}. Я бы решил эту проблему со специальным именем для читателя, например def a_ =, в этом случае вы можете работать (наследовать) a как состояние (используя override def a), так и как вычисление (используя override def a_; override def a_=) - person dk14; 25.12.2014
comment
И да, это хороший вопрос - на самом деле я один из 3 парней, проголосовавших за +1. Единственной проблемой с вопросом была его первоначальная формулировка (слово scala в верхнем регистре и т.д.) - person dk14; 25.12.2014
comment
Я понимаю, почему он разработан так, как сейчас. Вот вам и УАП. Хотя нужно иметь возможность переопределять с помощью аннотации @beanproperty, сгенерированные по умолчанию геттеры и сеттеры не работают по причинам, хорошо объясненным в ваших ответах.import scala.beans.BeanProperty class Car { @BeanProperty var age : Int = 30 } class SedanCar extends Car{ println ("age is "+super.getAge) override def getAge : Int = { 32 } } - person JavaMan; 26.12.2014

Я думаю, у вас проблемы с концепцией. Из ссылок Scala:

Объявление переменной var x: T эквивалентно объявлениям функции-получателя x и функции-установщика x_=, определенным следующим образом:

def x: T
def x_= (y: T ): Unit

Итак, вы пытаетесь переопределить геттер на «возраст = 54». Но теперь вам не нужен сеттер.

Надеюсь, вы поняли, что я имею в виду. Я думаю, почему люди «минусуют» ваш вопрос, потому что вы здесь не думаете в духе Scala.

person nate-k    schedule 23.12.2014
comment
Ваш ответ точно указывает на суть проблемы: var x: T должен быть эквивалентен def x: T; def x_= (y: T ): Unit, но это не так. С последним определением я имею полное право переопределить только геттер, оставив сеттер как есть. Это неверно с определением var. - person Régis Jean-Gilles; 23.12.2014
comment
Ха-ха-ха-ха, еще одна ошибка спецификации Scala. Аккуратный! - person gzm0; 26.12.2014
comment
@ RégisJean-Gilles объявление не является определением. - person som-snytt; 28.07.2015
comment
Верно, но различие здесь вряд ли уместно. И вообще, что такое объявление, если это не абстрактное определение? Технически абстрактное определение можно считать оксюмороном, но оно широко используется и принимается даже самой спецификацией scala. Так что извините меня, если я просто скажу, что определение при указании, является ли оно конкретным или абстрактным, не имеет значения. - person Régis Jean-Gilles; 28.07.2015