Размышляю тут вторые сутки над вопросом, что делает контекст
Foldable в определении класса типов
Traversable
class (Functor t, Foldable t) => Traversable t where
...
Технически он не нужен: никакая функциональность
Foldable не используется ни при выражении законов
Traversable, ни для реализации по умолчанию его методов.
Контекст
Functor, кстати, не вызывает вопросов в техническом отношении: он нужен и для формулировки законов (Naturality и Composition для
sequenceA и Composition для
traverse) и для реализации по умолчанию
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
Соображения, которыми руководствовались разработчики класса типов
Traversable, включая контекст
Foldable, заключаются в довольно разумном соображении: любой
Traversable является
Foldable в том смысле, что имея
traverse можно гарантированно и универсально реализовать
Foldable с помощью нехитрой машинерии:
foldMap :: (Traversable t, Monoid m) => (a -> m) -> t a -> m
foldMap f = getConst . traverse (Const . f)
(эта функция присутствует в
Data.Traversable под именем
foldMapDefault).
Тем не менее решение о "наследовании"
Traversable от
Foldable имеет свою цену. Теперь, объявляя некоторый тип
MyType представителем
Traversable, я должен сначала сделать его представителем
Foldable. Даже если я совсем не собираюсь пользоваться
Foldable-интерфейсом, я должен написать затычку
instance Foldable MyType where
foldMap = foldMapDefault
Это не очень большая цена, но представим, что через пару лет ресерчеры обнаружат, что есть замечательный класс типов
SomethingInterestingAndTrendyable и что с помощью
traverse можно реализовать его единственную ключевую функцию
somethingInterestingAndTrendyImpl. То есть
Traversable является (is a :)
SomethingInterestingAndTrendyable и поэтому нужно сделать
class (Functor t, Foldable t, SomethingInterestingAndTrendyable t) => Traversable t where
...
а для "удобства" добавить
somethingInterestingAndTrendyImplDefault. Не думаю, что мы будем этим очень довольны, хотя технически в каждый наш инстанс
Traversable потребуется добавить лишь
instance SomethingInterestingAndTrendyable MyType where
somethingInterestingAndTrendyImpl = somethingInterestingAndTrendyImplDefault
Но раз уж мы двинулись по этому пути "проявленных наследований" (а мы таки двинулись, см.
Monad и
Applicative), почему бы не переложить эту деятельность на компилятор? Вроде есть напрашивающееся решение: разрешить в Хаскелле в классах-наследниках задавать методы по умолчанию для классов-родителей. То есть вместо внешней по отношению к классу
foldMapDefault и ей подобных разрешить писать
class (Functor t, Foldable t, SomethingInterestingAndTrendyable t) => Traversable t where
-- default impls for Traversable methods
traverse = ...
-- default impls for parents methods
fmap f = getId . traverse (Id . f)
foldMap f = getConst . traverse (Const . f)
somethingInterestingAndTrendyImpl = ...
В этом случае словари для родительских классов могут при необходимости генерироваться автоматически (если у нас достаточно реализаций по умолчанию), и разработчики будут освобождены от написания фейковых родительских инстансов.
Discuss.
UPD. В комментах сказали, что proposal такой имеется. Нашел,
https://wiki.haskell.org/Class_system_extension_proposal, почитал. Мрак. C++ как мы его знаем, практически все прелести разрешения коллизий при множественном наследовании реализации. Нам такого не надо. Вывод: наследование - зло, его надо избегать, долой контекст
Foldable из
Traversable (и, как мне тут подсказывают,
Applicative из
Monad).