Аппликативные классы трансформаторов

Где классы трансформатора Applicative? Я хотел использовать классы преобразователей для стека прикладных преобразователей в предыдущем ответе, но, похоже, их не существует.

Пакет transformers и многие другие полны преобразователей, которые сохраняют Applicative структуру, даже если базовая структура не Monad.

Беглый взгляд на transformers содержит Applicative экземпляров для большинства трансформаторов.

Applicative f => Applicative (Backwards f)
Applicative f => Applicative (Lift f)
Applicative (ContT r m)
Applicative m => Applicative (IdentityT m)
Applicative m => Applicative (ReaderT r m)
(Monoid w, Applicative m) => Applicative (WriterT w m)
(Applicative f, Applicative g) => Applicative (Compose f g)
(Applicative f, Applicative g) => Applicative (Product f g)

Только преобразователи состояния и чередования (ExceptT и MaybeT) требуют базовой монады для экземпляра Applicative.

(Functor m, Monad m) => Applicative (ExceptT e m)
(Functor m, Monad m) => Applicative (MaybeT m)
(Monoid w, Functor m, Monad m) => Applicative (RWST r w s m)
(Functor m, Monad m) => Applicative (StateT s m)

Есть класс для Monad трансформаторов. Я понимаю, как что-то может потребовать этого Monad ограничения, поскольку его нельзя ввести в другом месте.

class MonadTrans t where
    lift :: (Monad m) => m a -> t m a

Где класс для Applicative трансформаторов?

class ApTrans t where
    liftAp :: (Applicative f) => f a -> t f a

Или просто старые добрые трансформаторы (хотя я не могу себе представить для этого никаких законов)?

class Trans t where
    liftAny :: f a -> t f a

Из-за разницы только в полиморфных ограничениях эти классы типов имеют странный паттерн дисперсии. За исключением их законов, которые должны учитывать невыразимые ограничения, все, что является экземпляром Trans, должно автоматически быть экземпляром ApTrans и MonadTrans, а все, что является экземпляром ApTrans, должно автоматически быть экземпляром MonadTrans.

Если мы перейдем к библиотеке mtl, классы там также несовместимы со стеком преобразователя Applicative. . Все классы mtl, с которыми я знаком, имеют ограничение Monad. Например, вот MonadReader

class Monad m => MonadReader r m | m -> r where
    -- | Retrieves the monad environment.
    ask   :: m r
    ask = reader id

    -- | Executes a computation in a modified environment.
    local :: (r -> r) -- ^ The function to modify the environment.
          -> m a      -- ^ @Reader@ to run in the modified environment.
          -> m a

    -- | Retrieves a function of the current environment.
    reader :: (r -> a) -- ^ The selector function to apply to the environment.
           -> m a
    reader f = do
      r <- ask
      return (f r)

Какова цель ограничения Monad? Это делает экземпляры MonadReader и MonadReader для многих из вышеуказанных трансформаторов несовместимыми со стеками Applicative трансформаторов.

Я бы наивно написал что-нибудь вроде

class Reader r m | m -> r where
    ask :: m r
    local :: (r -> r) -> m a -> m a

или даже разделить local на отдельный класс.

class Reader r m | m -> r where
    ask :: m r

class (Reader r m) => Local r m | m -> r where
    local :: (r -> r) -> m a -> m a

local было бы довольно сложно использовать без Monad экземпляра. Более полезный интерфейс без ограничения Monad выглядел бы примерно так:

class (Reader r m) => Local r m | m -> r where
    local :: m (r -> r) -> m a -> m a

Существуют ли где-то существующие классы трансформаторов, которые не имеют ограничения Monad, или существует реальная потребность в еще одной библиотеке классов трансформаторов?


person Cirdec    schedule 12.09.2014    source источник
comment
Это стоит прочесть о различных способах комбинирования заявителей.   -  person AndrewC    schedule 13.09.2014
comment
@AndrewC Спасибо, мне жаль, что я не прочитал это, прежде чем пытаться ответить на связанный вопрос.   -  person Cirdec    schedule 13.09.2014
comment
Я думаю, что есть аппликативные преобразователи, которые не возникают при составлении аппликативов. Например, определите монадические потоки data MStream m a = MStream (a, MStream m a). Тогда MStream Identity - это Applicative, и для любого Applicative m, MStream m это Applicative, и есть очевидное lift :: m a -> MStream m a бесконечным повторением. Тем не менее, MStream m не состав MStream и m! (Упражнение, что это такое?)   -  person Turion    schedule 10.06.2016
comment
@Turion MStream m a - это Applicative независимо от того, что такое m; m - фантомный тип. Это то же самое, что и data Stream a = Stream a (Stream a). Если вы имели в виду, что as должны быть обернуты m, то это точно композиция Applicatives Stream и m, Compose Stream m. Вот что делает композиция Applicatives; он оборачивает каждое вхождение аргумента во внешний функтор с внутренним функтором.   -  person Cirdec    schedule 10.06.2016
comment
@Cirdec, у меня в комментарии есть фундаментальная опечатка. Я имел ввиду MStream m (a, MStream m a) !!   -  person Turion    schedule 11.06.2016
comment
... и это не то же самое, что Mstream Identity (m a) = (Compose Stream m) a.   -  person Turion    schedule 11.06.2016
comment
@Turion Если у вас есть еще один вопрос о существовании Applicative с определенной структурой, я предлагаю вам задать отдельный вопрос. Ваше исправление все еще содержит опечатки, и хотя я мог догадаться, что вы имеете в виду, и отсылаю вас к базовым функторам и фиксированным точкам, это превышает то, что я готов развлечь в комментариях.   -  person Cirdec    schedule 11.06.2016
comment
@Cirdec, ты неправильно понял. У меня нет вопросов, у меня есть комментарий. Мой второй комментарий - это фактически рабочий код из библиотеки, которую мы с соавтором отправляем на симпозиум Haskell. Но вы правы, мой третий комментарий все еще полон опечаток (да, было поздно и слишком много вина ...), и мне больше не разрешено его редактировать. Я воспользуюсь вашим советом сейчас и задам этот вопрос, и посмотрю, есть ли у кого-нибудь что-нибудь сказать по этому поводу.   -  person Turion    schedule 11.06.2016
comment
@Cirdec, вот и все: stackoverflow.com/questions/ 37761078 / Код компилируется на моей машине.   -  person Turion    schedule 11.06.2016


Ответы (2)


Как сказал Дж. Абрахамсон, заявители закрыты по продуктам и составу, поэтому нет необходимости в специальных версиях трансформаторов. Однако также нет необходимости создавать собственные типы продуктов / композиций Applicative, потому что на платформе они уже есть:

Я обнаружил, что их проще использовать с расширением GeneralizedNewtypeDeriving, потому что тогда вы можете просто определять такие типы:

newtype MyType m a = MyType (Compose (Const m) (Reader m) a)
    deriving (Functor, Applicative)

-- Plus a bunch of utility definitions to hide the use of Compose and generally
-- keep you sane...

Еще один полезный инструмент в наборе инструментов Applicative - это бесплатный аппликативный функтор. Обычно я использую версию free библиотеки Эдварда Кметта, но если вам нужно меньше зависимостей, то легко создать свой собственный.

Эти определения также могут быть полезны (хотя я бы приветствовал предложения по схеме именования, особенно бит «I / O»):

{-# LANGUAGE Rank2Types, TypeOperators #-}

import Control.Applicative
import Data.Functor.Compose

-- | A handy infix type synonym for 'Compose', which allows us to
-- stack 'Applicative's with less syntactic noise:
-- 
-- > type CalculationT s p f = Reader (Frame s p) :. Reader (Cell s p) :. f
-- > type Calculation s p = Calculation s p Identity
--
-- Note that 'Identity' and ':.' form something a type-level monoid
-- modulo @newtype@ equivalence.  The following isomorphisms hold:
--
-- > f :. Identity  ~=  Identity :. f  ~=  f
-- > f :. g :. h  ~=  (f :. g) :. h 
--
type f :. g = Compose f g
infixr :.

-- | Lift an action from the outer functor into the composite.
-- Alternative reading: append an 'Applicative' to the right of @f@.
liftO :: (Functor f, Applicative g) => f a -> (f :. g) a
liftO = Compose . fmap pure

-- | Lift an action from the inner functor into the composite.
-- Alternative reading: prepend an 'Applicative' to the left of @g@.
liftI :: Applicative f => g a -> (f :. g) a
liftI = Compose . pure

-- | Lift a natural transformation from @g@ to @h@ into a morphism
-- from @f :. g@ to @h :. g@.
hoistO :: (forall x. f x -> h x) -> (f :. g) a -> (h :. g) a
hoistO eta = Compose . eta . getCompose

-- | Lift a natural transformation from @g@ to @h@ into a morphism
-- from @f :. g@ to @f :. h@.
hoistI :: Functor f => (forall x. g x -> h x) -> (f :. g) a -> (f :. h) a
hoistI eta = Compose . fmap eta . getCompose
person Luis Casillas    schedule 12.09.2014
comment
Куча служебных функций, позволяющих скрыть использование compose и сохранять рассудок, - это то, что классы mtl предоставляют для монад, и то, что я ищу для аппликаций. - person Cirdec; 13.09.2014
comment
Там также mapI :: Functor f => (g a -> h b) -> (f :. g) a -> (f :. h) b где mapI f = Compose . fmap f . getCompose. что позволяет вам поднимать стрелочные функции. mapI2 :: Applicative f => (g a -> h b -> i c) -> (f :. g) a -> (f :. h) b -> (f :. i) c где mapI2 f (Compose fga) (Compose fhb) = Compose (fmap f fga <*> fhb) позволяет поднимать бинарные операторы и функции в целом. - person Cirdec; 21.09.2014
comment
Если я правильно понимаю, вы говорите, что Data.Fuctor.Compose, Data.Functor.Product, Data.Functor.Constant, Data.Functor.Identity & Control.Applicative.Lift, являются обобщенными средствами написания преобразователя монад (например, чтения и записи состояний), который позволяет вам использовать GeneralizedNewtypeDeriving? - person akst; 06.08.2016

Аппликативы, в отличие от Монад, закрыты по продуктам и составу и, следовательно, не нуждаются в особом классе вещей, таких как «трансформеры». Вот небольшая библиотека:

data (*) f g x = P (f x) (g x)     deriving Functor
data C   f g x = C (f (g x))       deriving Functor

instance (Applicative f, Applicative g) => Applicative (f * g) where
  pure a = P (pure a) (pure a)
  P ff gf <*> P fx gx = P (ff <*> fx) (gf <*> gx)

instance (Applicative f, Applicative g) => Applicative (C f g) where
  pure = C . pure . pure
  C fgf <*> C fgx = C (liftA2 (<*>) fgf fgx)

Более того, все монады являются аппликаторами, поэтому мы должны иметь возможность повторно использовать этот код. К сожалению, отсутствие подтипов Applicative-Monad вынуждает монадический код быть более исключающим, чем необходимо, и, таким образом, запрещает такой код. Это можно было бы исправить, если бы все эти библиотеки запрашивали ограничение (Applicative m, Monad m), но они этого не делают. Кроме того, учитывая, как часто вам, возможно, придется писать

(MonadReader m, Monad m) => ...

ограничение суперкласса Monad удобно. Однако я не уверен, что это совершенно необходимо.

person J. Abrahamson    schedule 12.09.2014
comment
Я не понимал, что закрытие Applicative по композиции также означало, что для большинства трансформаторов T, T f a эквивалентно T Identity (f a). Я не уверен, что это относится к продуктам, поскольку Functor data (*) f x1 = P (f x1) (x1), когда x1 ~ g x не похоже на ваш (*) f g x. Это по-прежнему оставляет проблему умного проникновения в стек композиции, что может быть проще, чем то, что было сделано для Monads. Первое, что я попробую, - это класс стиля Generics, который просит какие-то вещи *->* описать их структуру и посмотреть, чего можно достичь на основе этого. - person Cirdec; 12.09.2014
comment
@Cirdec См. Также библиотеку преобразователей, которая определяет композиция, product и сумма функторов, а также их соответствующих Applicative экземпляров. - person Petr; 12.09.2014
comment
@ PetrPudlák Примечательно, что заявители не закрывают слишком большие суммы, если у нас нет естественного преобразования из одной стороны в другую. Они также неканоничны, поскольку нуждаются в предвзятости. - person J. Abrahamson; 12.09.2014
comment
Разве AMP не исправляет ту часть в последнем абзаце? - person David Young; 12.09.2014
comment
Замечательно, преобразователь LetT, который я определил в связанном ответе, - это просто Compose (Ap Identity). - person Cirdec; 13.09.2014
comment
В Haskell теория категорий - обычное дело, поэтому я хотел бы перефразировать комментарий, сделанный @ J.Abrahamson: Sums of Applicatives работают как Either в том смысле, что у вас есть предпочтительный Applicative, и вы используете нетривиальную полиморфную функцию для преобразовать из предпочтительного Applicative в альтернативный, чтобы его можно было использовать для моделирования сложных или сложных успехов / неудач. В частности, неудача не должна останавливать мир, как это должно быть в Монаде. (Практически любая нетривиальная полиморфная функция, которую вы склонны писать между двумя экземплярами Functor, является естественным преобразованием, поэтому теоремы бесплатны.) - person AndrewC; 13.09.2014