Почему аппликативные функции имеют побочные эффекты, но функторы не могут?

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

Итак, возникает вопрос: почему аппликативные функторы имеют побочные эффекты, но функторы не могут?

Может быть, они могут, и я просто не заметил...?

Ответ 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 тем, что он может иметь значение результата. Но вы не можете составить его с помощью аппликативных комбинаторов.