Почему Складные и Functor отдельные классы?
В разделе wiki говорится, что оба класса имеют дело с контейнерными операциями, а Foldable - это класс контейнеров с foldr
, определенный над ними, и для Functor it fmap
.
Однако каково принципиальное различие между типами Foldable и типами, которые являются функторами?
Вики намекают на это различие:
Класс [Foldable] не требует суперкласса Functor, чтобы разрешить контейнеры, такие как Set или StorableVector
Но я все еще не уверен, почему Set не удалось сопоставить, чтобы получить другой набор, если я правильно его интерпретирую.
Ответы
Ответ 1
Foldable
и Functor
предлагают две отдельные абстракции для типов со структурами, которые могут складываться (или уменьшаться) и отображаться соответственно.
A Складные значения, которые могут быть перечислены и объединены вместе 1. Можно думать о Складном как о чем-то, что можно превратить в список (toList :: Foldable f => f a -> [a]
). В качестве альтернативы можно думать о Foldables как о структурах, значения которых могут быть объединены моноидально: (Foldable t, Monoid m) => (a -> m) -> t a -> m
(конечно, для этого требуется возможность их перечислить).
Функторы, с другой стороны, являются структурами, которые позволяют "поднять" функцию (a -> b)
для применения к a
, удерживаемому структурой (fmap :: (a -> b) -> (f a -> f b)
). fmap
должен сохранять отображаемую структуру: дерево должно иметь одинаковую форму до и после, список должен иметь одинаковое количество элементов в том же порядке, Nothing
нельзя превратить в что-то и т.д., С другой стороны, Foldables не должны сохранять эту структуру; все дело в том, чтобы отбросить структуру и создать новую.
Wiki ссылается на тот факт, что для ограничения типа typeclass нет fmap
. fmap :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
не объединяется с типом, определенным классом, fmap :: (a -> b) -> f a -> f b
, который не имеет ограничений. Это делает невозможным запись экземпляра для Set
.
Однако это просто проблема языковой реализации, а не более глубокое математическое утверждение о наборах. Настоящая причина, по которой Foldable не имеет суперкласса Functor, просто состоит в том, что есть Foldable экземпляры, которые не являются экземплярами Functor.
- "Hold" немного ослаблен и предназначен для интерпретации в "Функторы являются контейнерами" , где
Proxy s a
имеет нулевое значение a, Identity a
содержит один a, Maybe a
имеет нуль или один a, b -> a
содержит |b|
a и т.д.
Ответ 2
Предположим, что у меня есть xs :: Set Int
, и я хочу отобразить над ним функцию putStrLn
. Это будет проблемой, потому что IO ()
не имеет экземпляра Ord
, поэтому нет способа выяснить, как вставить эти действия в набор результатов. Тип fmap
не оставляет места для ограничений для аргумента типа Functor
.
IO
также дает пример того, что a Functor
, но нет никакого значимого способа foldMap
.
Стоит отметить, что Foldable
и Functor
объединяются в класс Traversable
, который дает значительно большую мощность, чем либо.
Ответ 3
Set
и StorableVector
являются функторами, вы действительно можете отображать над ними функции.
Но не какая-либо функция. Например, вы не можете сопоставить (+)
над StorableArray
чисел: это даст массив функций, и они не будут сохраняться.
Итак, эти функторы не являются (endo-) функторами на всех Hask, но только в подкатегории, включающей типы определенного класса. Это не может быть выражено в Haskell98, на самом деле это стало возможным совсем недавно с появлением видов ограничений. См. этот пример:
instance Functor Set Ranking Ranking where
fmap = constrainedFmap Set.map
На самом деле, наборы также образуют монаду в Hask, если вы используете некоторые умные трюки GADT. Но это невозможно для всех контейнеров Foldable
, поэтому стандартной библиотеке не требуется Functor f => Foldable f
.
Ответ 4
Functor
typeclass не позволяет вам fmap
над вещами, которые имеют ограничения на типы их элементов. Set
требует Ord
и StorableVector
требует Storable
.
Можно выразить "ограниченный функтор" с использованием расширения GHC ConstraintKinds
, что-то вроде:
{-# LANGUAGE ConstraintKinds,
FunctionalDependencies,
MultiParamTypeClasses #-}
import GHC.Exts (Constraint)
import Data.Set (Set)
import qualified Data.Set as Set
class ConstrainedFunctor c f | f -> c where
cfmap :: (c a, c b) => (a -> b) -> f a -> f b
instance ConstrainedFunctor Ord Set where
cfmap = Set.map
Но этот механизм не был вокруг, когда появился Functor
.
Кроме того, Functor
должен быть "сохраняющим форму", но, например, отображение через Set
может изменить его размер.