Почему аппликативные функции имеют побочные эффекты, но функторы не могут?
Я чувствую себя довольно глупо, задавая этот вопрос, но это было на мой взгляд на некоторое время, и я не могу найти ответы.
Итак, возникает вопрос: почему аппликативные функторы имеют побочные эффекты, но функторы не могут?
Может быть, они могут, и я просто не заметил...?
Ответы
Ответ 1
Этот ответ немного упрощен, но если мы определяем побочные эффекты, поскольку вычисления влияют на предыдущие вычисления, легко видеть, что класс Functor
недостаточно для побочных эффектов просто потому, что нет способа целые множественные вычисления.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Единственное, что может сделать функтор, - это изменить конечный результат вычисления через некоторую чистую функцию a -> b
.
Однако аппликативный функтор добавляет две новые функции: pure
и <*>
.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Здесь <*>
- ключевое различие, так как оно позволяет нам цепочки двух вычислений:
f (a -> b)
(вычисление, которое создает функцию), и f a
вычисление, которое
предоставляет параметр, к которому применяется функция. Используя pure
и <*>
, это
можно определить, например.
(*>) :: f a -> f b -> f b
Это просто цепочки двух вычислений, отбрасывая конечный результат от первого
(но, возможно, применение "побочных эффектов" ).
Короче говоря, это способность связывать вычисления, которые являются минимальным требованием для таких эффектов, как изменчивое состояние в вычислениях.
Ответ 2
Неверно, что Functor
не имеет эффектов. Каждый Applicative
(и каждый Monad
через WrappedMonad
) является Functor
. Основное отличие состоит в том, что Applicative
и Monad
предоставляют вам инструменты, как работать с этими эффектами, как их сочетать. Грубо
-
Applicative
позволяет вам последовательно выполнять эффекты и комбинировать значения внутри.
-
Monad
дополнительно позволяет вам определить следующий эффект в соответствии с результатом предыдущего.
Однако Functor
позволяет вам изменять значение внутри, оно не дает инструментов для чего-либо с эффектом. Поэтому, если что-то просто Functor
, а не Applicative
, это не значит, что у него нет эффектов. У этого просто нет механизма, чтобы объединить их таким образом.
Обновление: В качестве примера рассмотрим
import Control.Applicative
newtype MyF r a = MyF (IO (r, a))
instance Functor (MyF r) where
fmap f (MyF x) = MyF $ fmap (fmap f) x
Это явно экземпляр Functor
, который несет эффекты. Просто у нас нет способа определить операции с этими эффектами, которые будут соответствовать Applicative
. Если мы не накладываем некоторые дополнительные ограничения на r
, нет способа определить экземпляр Applicative
.
Ответ 3
Другие ответы здесь справедливо показали, что функторы не допускают побочных эффектов, потому что их нельзя комбинировать или секвенировать, что вполне справедливо в целом, но есть один способ последовательности функторов: путем входа внутрь.
Пусть напишите ограниченный функтор Writer.
data Color = R | G | B
data ColorW a = Re a | Gr a | Bl a deriving (Functor)
а затем примените к нему тип Free monad
data Free f a = Pure a | Free (f (Free f a))
liftF :: Functor f => f a -> Free f a
liftF = Free . fmap Pure
type ColorWriter = Free ColorW
red, blue, green :: a -> ColorWriter a
red = liftF . Re
green = liftF . Gr
blue = liftF . Bl
Конечно, по свободному свойству это образует монаду, но эффекты действительно исходят из "слоев" функтора.
interpretColors :: ColorWriter a -> ([Color], a)
interpretColors (Pure a) = ([], a)
interpretColors (Free (Re next)) = let (colors, a) = interpretColors next
in (R : colors, a)
...
Итак, это своего рода трюк. Действительно, "вычисление" вводится свободной монадой, но материал вычисления, скрытый контекст, вводится только функтором. Оказывается, вы можете сделать это с любым типом данных, он даже не должен быть Functor, но Functor обеспечивает четкий способ его создания.
Ответ 4
Позвольте сначала переименовать побочные эффекты в эффекты. Все виды ценностей могут иметь последствия. Функтор - это тип, который позволяет отображать функцию над тем, что создается этим эффектом.
Когда функтор не применяется, он не позволяет использовать определенный стиль композиции для эффектов. Выберем пример (надуманный):
data Contrived :: * -> * where
AnInt :: Int -> Contrived Int
ABool :: Bool -> Contrived Bool
None :: Contrived a
Это просто функтор:
instance Functor Contrived where
fmap f (AnInt x) = AnInt (f x)
fmap f (ABool x) = ABool (f x)
fmap _ None = None
Однако для pure
нет разумной реализации, поэтому этот тип не является прикладным функтором. Он похож на Maybe
тем, что он может иметь значение результата. Но вы не можете составить его с помощью аппликативных комбинаторов.