воскресенье, 4 октября 2009 г.

[comp.prog] Несколько слов о контравариантности в Scala

Года три назад пытался серьезно изучать язык Scala. Такой своеобразный язык оказался – поначалу нравится, потом перестает. Одним из камешков, о которых я чуть не сломал зубы, оказалась контравариантность. Это такая странная лабуда, которую с ходу и на пальцах не объяснишь. А объяснять придется. Вот парочка такие объяснений:

  • совсем свежее объяснение на RSDN от nikov.
  • а вот и две мои попытки почти 2.5 летней давности: общие слова и конкретный пример. Кстати, тот конкретный пример дался мне нелегко и я им до сих пор горжусь, т.к. практически не видел аналогичных простых примеров, демонстрирующих полезность контравариантности.

Про Scala вспомнил потому, что позавчера у себя на компьютере нашел книгу Programming in Scala. Захотелось прочитать, приобщиться, так сказать, к современным веяниям. Думалось: вот разгребусь в ближайшие недели с последним оставшимся безнадежным проектом, возьму отгулов недельку, почитаю спокойно… А тут наткнулся на обсуждение контравариантности в Scala и подумал – а оно мне вообще надо? Имхо, как раз из-за подобных вещей Scala в ближайшее время языком для массового использования не станет. Проще нужно быть, к народу ближе :)

10 комментариев:

zOOn комментирует...

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

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

eao197 комментирует...

Понимать не обязательно -- это сильно :)

Ваше объяснение хорошее. Только сюда бы еще добавить тот факт, что в Scala были (не знаю, есть ли сейчас) ко/контра-вариантные позиции аргументов. Из-за которых какие-то аргументы могут быть контравариантными, а какие-то нет.

zOOn комментирует...

Это не в Scala, как в таковой - это жизнь так устроена. Например, в С++результаты методов могут быть ковариантны (это не сразу там появилось, это добавили - удобно), но нет никакой возможности объявить аргументы методов контрвариантными. В Scala у вас есть возможность декларировать ковариантность/контрвариантность явно. Жизнь(не Scala), диктует правила, например, вы можете сделать контенеры ковариантными (т.е. разрешить использовать List[Button] вместо List[Widget]), но это типобезопасно, только если они неизменяемые.

В методах вы можете обявить аргументы контрвариантными, поскольку это имеет смысл, но можете и не объявлять. Т.е. как пользователь скалы вы можете ничего об этом и не знать - но в стандартной библиотеке все декларации ковариантности/контрвариантности уже расставлены, корректным, типобезопасным образом.

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

В википедии есть неплохая статья с большим количеством примеров на разных языках.
http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

eao197 комментирует...

Ну то, что в стандартной библиотеке уже расставлено -- это не суть важно. Важно, насколько сложно будет программисту использовать возможности языка для достижения своих целей. Допустим, программист захотел написать свой обобщенный код -- исходя из каких соображений ему расставлять ко-/контравариантность для аргументов? Наверное, методом проб и ошибок.

Язык массового потребления (коим предстоит стать Scala, если он пытается занять место Java) должен иметь разумный баланс между сложностью и возможностями. В Scala этот баланс нарушен, имхо, в сторону сложности. Контравариантность -- это только пример, с которым я сам столкнулся.

Хотя до C++ Scala еще пахать и пахать. Но есть люди, которые с удовольствием переходили с C++ на Java, в том числе и из-за сложности C++ных шаблонов. А теперь для них возникнет контравариантность параметров...

zOOn комментирует...

Вы можете вообще ничего не расставлять - и получите в точности то, что в яве.

Если захотите расставить - достаточно пары правил: аргументы функции могут быть контрвариантными; неизменяемые контейнеры, и результаты функций могут быть ковариантными.

zOOn комментирует...

А теперь для них возникнет контравариантность параметров

Я еще хочу повторить, она не в скале возникает, а в _любом_ языке, где есть джинерики и наследование: С++,C#, Java etc.

eao197 комментирует...

Я еще хочу повторить, она не в скале возникает, а в _любом_ языке, где есть джинерики и наследование: С++,C#, Java etc.

Не, ну вот в C++ возможности указывать контравариантность нет -- и ничего, особых жалоб не слышно. А в Scala эта возможность есть. И тут же возникают вопросы: "А что это за зверь?", "А нахрена?", "А как его правильно использовать?".

Я, в общем, чего хочу сказать. Вот есть в Scala такая штука, как контравариантность. В мейнстримовых языках (Java, C#, C++) ее нет. Значит, переходящие на Scala разработчики будут с ней сталкиваться впервые. Если эта фича достаточно полезная, то она должна быть на пальцах с простыми примерами разъяснена во всех материалах по Scala. Но я такого не видел. И, по-моему, это плохо. Поэтому и будут возникать такие вопросы, как недавний на RSDN. И будет много таких объяснений, как дал nikov (формально правильных, но малопонятных). Что не есть хорошо.

zOOn комментирует...

В мейнстримовых языках (Java, C#, C++) ее нет.

В C# контравариантность делегатов есть со 2-ой версии.
http://msdn.microsoft.com/en-us/library/ms173174(VS.80).aspx
В C# 4 они обещают ковариантность контейнеров.
В Скале все это сразу было, поскольку ее писали образованные люди :)

В C# кругах про это очень много пишут, вот Липперт, например:
http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

eao197 комментирует...

В C# контравариантность делегатов есть со 2-ой версии.

Да, блин, пора завязывать говорить о том, в чем не разбираешься...

jazzer комментирует...

в С++ все это делается обычными шаблонами со статик-ассертами.
Причем в статик-ассерт можно сделать проверку какой угодно сложности, не только ко/контравариантности.
Поэтому в плюсах оно и не нужно.