Re[4]: Kotlin - новый язык для JVM
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 26.07.11 14:14
Оценка: 87 (7)
Cyberax,

C>>>5) Множественное наследование.

A>>можно сказать, что они в Скале есть через трэйты
C>Они кривые. Скажем, для них нет конструкторов и вообще их инициализация неудобна. Diamond-проблему trait'ы в Scala никак не решают, а просто задают детерминированный механизм разрешения конфликта.

C>Так что в Kotlin поступили правильно — взяли обычное множественное наследование, убрав разницу между интерфейсом и классом.


Это как раз случай простого и неправильного решения для сложной проблемы. Вообще, я понимаю, об окончательном решении сейчас рано говорить — язык ещё даже не оформлен, не то что не реализован. Но если эта залепуха (а по другому я это "решение" назвать не могу) останется в релизе, то это будет полнейший фэйспалм. Можно понять Страуструпа — он лепил из того, что было, и опыта хождения по граблям множественного наследования не было никакого. Гослинг (а вслед и Хейлсберг) посмотрели на это, и сказали — нафиг надо и ввели ограничения, так что выразительность упала значительно, но и грабли были убраны. А что мы видим здесь? возврат к старому и злому С++.

Diamond-проблему как раз трейты решают, и это решение модульное, типобезопасное и непротиворечивое (см. статью Scalable component abstractions). Соглашусь, что несколько громоздкое из-за правил линеаризации. И ограничения есть — нетривиальные конструкторы недопустимы. Впрочем, если мы хотим позволить себе конструкторы с параметрами, тогда придётся навернуть систему типов и отношений между классами.

Самый продвинутый известный мне вариант описан в статье CZ:MultipleInheritance Without Diamonds — а именно ввести 2 вида отношений: extends и requires, и разделить понятия subtype и subclass.
Вкратце, любые ромбы запрещены, но чтобы сохранить выразительность и переиспользовать функционал, можно ввести отношение requires: C requires A. Означает это, что C становится абстрактным, будет подтипом A (то есть можно приводить ссылки), и конкретные наследники C должны будут также наследовать и от A.
Таким образом, ромб типа

заменяется на иерархию

Никаких неоднозначностей не осталось, и вместе с тем, конкретные классы заиспользовали все нужные им куски из функционала предков.

А вот то что я вижу в Котлине вызывает ряд вопросов:
open class A(virtual var v : int)
open class B(v : Int) : A(v)
open class C(v : Int) : A(v)

class D(v : Int) : B(v), C(v) {
  override var v : Int
    get() = this<B>.v
    set(value) { this<B>.v = value }
}

1. Требуется ли всегда вручную явно указать как разрешать противоречия? (Если да, то печаль.)
2. Сколько подобъектов A содержится в D (если 2, то это грабли, и тогда эти сопли не нужны, лучше использовать композицию, если 1, то см ниже.)
3. Как будем инициализировать подобъект А если напишем
open class A(virtual var v : int)
open class B(v : Int) : A(100)
open class C(v : Int) : A(200)

class D(v : Int) : B(v), C(v) { ... }

Сколько раз будем вызывать конструктор для A? Я надеюсь, один раз, но тогда с каким аргументом? (Для Скалы пофиг, у всех трейтов одинаковые то бишь дефолтные конструкторы, поэтому выбор очевиден — вызываем один раз единственно имеющийся конструктор).

Плюс, в Скале есть мембер-типы и селфтипы, что позволяет элегантно расправиться с Expression Problem. А Котлин?

Короче, будем смотреть
(Думаю, черкануть пару строчек кому-нибудь из JetBrains, авось заинтересуются идеей из CZ).
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
множественное наследование kotlin
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.