Здравствуйте, Klapaucius, Вы писали:
K>Кстати, "наследование" тайпклассов в хаскеле — фича сомнительной полезности, да еще и, в некоторых случаях, достаточно проблемная.
K>Почему сомнительной полезности? И почему проблемная?
K>Члены тайпкласса "наследника" редко могут быть реализованы в терминах базового класса (тут наследование могло бы помочь), но зато очень часто базовый класс может быть реализован в терминах наследника (тут наследование ничем помочь не может). Возможно, полезность наследованию могли бы придать так называемые дефолтные реализации для суперклассов:
K>K>class Functor f => Applicative f where
K> return :: x -> f x
K> (<*>) :: f (s -> t) -> f s -> f t
K> (>>) :: f s -> f t -> f t
K> fs >> ft = return (flip const) <*> fs <*> ft
K> -- описываем дефолтный инстанс для суперкласса.
K> instance Functor f where
K> fmap = (<*>) . return
K>class Applicative f => Monad f where
K> (>>=) :: f a -> (a -> f b) -> f b
K> instance Applicative f where
K> ff <*> fs = ff >>= \f -> fs >>= \s -> return (f s)
K>
Здесь, собственно, какой момент. Вот "большевики" долго говорят о том, что монада должна быть апликативным функтором. Вернее, даже не так. Монада *является* апликативным функтором. Возникает естественное желание это самое "является" как-то зафиксировать. Наследование — как раз способ подобной фиксации. И теперь, собственно, вопрос. А что мы теряем, если на уровне абстракции подобной фиксации не будет? Т.е. понятно, что сейчас ее и так нет. Но все же — есть ли какие-то *реальные* проблемы из-за этого?
Одна проблема мне понятна (кстати, не понимаю, как тут помогут те же default-ы) — реализуя Монаду, я не могу получить реализацию того же функтора "из коробки", мне придется ее-таки описать (да, я могу описать ее через готовые функции, но это-таки бойлерплейт). Эту проблему могли бы решить как раз "дефолтные реализации для суперклассов". Или даже без них — сама монада (ну при наличии немного другой, более "православной" иерархии классов) могла бы быть описана в терминах map/join — таким образом, я бы реализовал map/join, а реализацию монады получил бы за бесплатно. Без наследования же — только бойлерплейт (?)
А что еще?
K>Наследование, в принципе, позволяет избавиться от огромных контекстов. Но на практике для этого используется редко (городить такую сильную связь ради некоторого уменьшения писанины — спорное решение). Тем более, что в современном хаскеле есть средства куда луше:
K>K>type NumAndEq a = (Num a, Eq a)
K>foo :: NumAndEq a => a -> a -> Bool
K>foo a b = a * b == a
K>
K>Т.е. в хаскеле сейчас тенденция такая: отказ от наследования в стандартных библиотеках, добавление более адекватных (это, впрочем, еще время покажет — или не покажет) средств решения проблем без всякого наследования.
А нет каких-либо тредов или тикетов на тему "а давайте выпилим наследование"? Было бы интересно почитать.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здесь, собственно, какой момент. Вот "большевики" долго говорят о том, что монада должна быть апликативным функтором. Вернее, даже не так. Монада *является* апликативным функтором. Возникает естественное желание это самое "является" как-то зафиксировать. Наследование — как раз способ подобной фиксации.
Да, тут фиксация безвредна, потому что достоверно известно, что для всего, для чего можно написать инстанс монады — можно и интсанс аппликативного функтора написать. Это, однако, скорее исключение: в большинстве случаев никакой уверенности в том, что при возможности реализовать класс C можно реализовать и суперкласс S нет. А это порождает проблемы аналогичные всяким интерфейсным методам, выбрасывающим NotImplementedException. К примеру, имплементация Num для функций из чисел в числа вполне имеет смысл и может быть написана, но реализации Show и Eq — нет. "Наследование" вынуждает их писать.
ВВ>И теперь, собственно, вопрос. А что мы теряем, если на уровне абстракции подобной фиксации не будет? Т.е. понятно, что сейчас ее и так нет. Но все же — есть ли какие-то *реальные* проблемы из-за этого?
Проблемы вполне были, но не особенно серьезные. Дело в том, что если что-то можно не написать — его и не напишут. Например, долгое время не было имплементации Applicative для ST. Это вызывало некоторые неудобства. Есть, конечно, обертки, превращающие любой инстанс Monad в инстанс Applicative, но они замусоривают код. Кроме того, теоретически можно реализовать Applicative и Monad по разному — а это уже хитрый баг. Наследование тут, правда, не сильно поможет, разве что тем, что сократит время между реализациями (в нашем случае инстанс Monad писали, допустим, в 1995, a Applicative — в 2005).
Вообще, несуществующая в Prelude иерархия Functor -> Applicative -> Monad сама по себе является демонстрацией проблем "наследования". Суперкласс трудно добавить позднее. Даже если такую иерархию поправить — рано или поздно появится какой-нибудь Pointed (между Functor и Applicative). Особая ирония в том, что иерархия эта фундаментальна, известна была за годы до появления хаскеля и достоверно свободна от проблем типа наследования Num от Eq. Но даже в этих тепличных условиях "наследование" подвело. С подавляющим большинством других иерархий проблем будет только больше.
ВВ>Одна проблема мне понятна (кстати, не понимаю, как тут помогут те же default-ы) — реализуя Монаду, я не могу получить реализацию того же функтора "из коробки", мне придется ее-таки описать (да, я могу описать ее через готовые функции, но это-таки бойлерплейт). Эту проблему могли бы решить как раз "дефолтные реализации для суперклассов". Или даже без них — сама монада (ну при наличии немного другой, более "православной" иерархии классов) могла бы быть описана в терминах map/join — таким образом, я бы реализовал map/join, а реализацию монады получил бы за бесплатно. Без наследования же — только бойлерплейт (?)
Ну так "наследование" тут как раз не особенно помогает, о чем я и писал. А default-реализации помогают. Сравните сами:
-- нет "наследования" и default-методов:
instance Functor F where
fmap = ...
instance Applicative F where
pure = ...
(<*>) = ...
instance Monad F where
return = ...
(>>=) = ...
-- реализуем пять функций.
-- теперь исправленная иерархия с "наследованием":
instance Functor F where
fmap = ...
instance Applicative F where
pure = ...
(<*>) = ...
instance Monad F where
join = ...
-- реализуем четыре функции. При этом join не сильно легче bind, так что
-- наследование спасло нас только от повторной реализации pure/return
-- самой простой из четырех.
-- А теперь варианты с default-ами:
instance Functor F -- эти инстансы не обязательны.
instance Applicative F -- в отличие от случая с "наследованием".
instance Monad F where
return = ...
(>>=) = ... -- либо join, зависит от того, как класс определили.
-- реализуем две функции.
-- если же у нас уже есть готовые инстансы Functor и Applicative
-- что, само по себе, явление редкое (Functor можно вывести автоматически, Applicative - нет)
-- то все будет как при "идеальной иерархии"
-- return повторно реализовывать не нужно.
ВВ>А нет каких-либо тредов или тикетов на тему "а давайте выпилим наследование"? Было бы интересно почитать.
"Наследование" как фичу? Нет, никто его "выпиливать" не собирается. Все-таки это наследование в кавычках — аналогом наследования является в самом простом случае. На самом деле это контексты, аналог констрейнтов дженериков, в более интересных случаях наследованию не эквивалентны, и претензии к "сомнительной полезности" к контекстам в общем случае не применимы. Например:
class (Monad m, Monad (t m)) => Trans t m where
-- или там
class (FunDep a ~ b) => C a b where type FunDep a
Тут уже с наследованием мало общего.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, PSV100, Вы писали:
PSV>В этой веткеАвтор: Temoto
Дата: 06.07.12
начали говорить о Go, но не договорили. Интерфейсы у него, фактически, аналог классов типов в Хаскеле или протоколов и типов в Кложуре и т.п.:
PSV>PSV>type Reader interface {
PSV> Read(b []byte) (n int, err error)
PSV>}
PSV>type Writer interface {
PSV> Write(b []byte) (n int, err error)
PSV>}
PSV>type ReadWriter interface {
PSV> Reader
PSV> Writer
PSV>}
PSV>
PSV>Фактически, это аналог хаскелевских деклараций вида:
PSV>class (Eq a, Show a) => C a where ...
И близко не аналог. Принципиальное отличие выделено жирным шрифтом. Как, например, на го-интерфейсах делается упомянутый вами класс Eq?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll