【问题标题】:Applicative transformer classes适用变压器类
【发布时间】:2014-09-12 00:54:07
【问题描述】:

Applicative 转换器类在哪里?我想为applicative transformer stack in a previous answer 使用转换器类,但它们似乎不存在。

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)

只有状态和交替的转换器(ExceptTMaybeT)需要 Applicative 实例的底层 monad。

(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 的实例都应该自动成为ApTransMonadTrans 的实例,而任何ApTrans 的实例都应该自动成为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 约束的目的是什么?它使上述许多转换器的MonadReaderMonadReader 实例与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

如果没有Monad 实例,local 可能很难使用。没有Monad 约束的更有用的界面类似于

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

是否存在没有Monad 约束的现有转换器类,或者是否实际需要另一个转换器类库?

【问题讨论】:

  • This 值得一读以各种方式组合应用程序。
  • @AndrewC 谢谢,我希望我在尝试回答链接的问题之前已经阅读过。
  • 我认为有些应用转换器不是来自组合应用程序。例如,定义一元流data MStream m a = MStream (a, MStream m a)。那么MStream Identity是一个Applicative,对于任何一个Applicative mMStream m都是一个Applicative,通过无限重复有一个明显的lift :: m a -&gt; MStream m a。然而,MStream m不是MStreamm 的组合! (运动,是什么?)
  • @Turion MStream m aApplicative,无论 m 是什么; m 是一种幻象类型。与data Stream a = Stream a (Stream a) 相同。如果您的意思是将as 包裹在m 中,那么它正是Applicatives StreammCompose Stream m 的组合。这就是编写Applicatives 所做的;它将每次出现的参数都用内部函子包装到外部函子。
  • @Cirdec,我的评论中有一个根本性的错字。我的意思是MStream m (a, MStream m a)!!

标签: haskell monad-transformers applicative


【解决方案1】:

Applicative 与 Monad 不同,在产品和组合下是封闭的,因此不需要像“转换器”这样的特殊类别。这是一个小型图书馆:

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)

此外,所有的 monad 都是 Applicative,所以我们应该能够重用该代码。可悲的是,Applicative-Monad 子类型的缺乏迫使 monadic 代码比需要的更具排他性,因此禁止此类代码。如果所有这些库都要求(Applicative m, Monad m) 约束,它可能会被纠正,但他们没有。此外,考虑到您可能不得不写的频率

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

Monad 超类约束很方便。不过我不确定这是否完全有必要。

【讨论】:

  • 我没有意识到 Applicative 在 composition 下的关闭也意味着对于大多数变压器 TT f a 等价于 T Identity (f a)。我不确定这是否涵盖产品,因为Functor data (*) f x1 = P (f x1) (x1) x1 ~ g x 似乎与您的(*) f g x 不同。这仍然留下了巧妙地穿透组合堆栈的问题,这可能比为Monads 所做的更容易。我要尝试的第一件事是 Generics 样式类,它要求类似 *-&gt;* 的事物描述它们的结构,并查看基于此可以完成的工作。
  • @Cirdec 另见transformers 库,它定义了仿函数的compositionproductsum 以及它们各自的Applicative 实例。
  • @PetrPudlák 值得注意的是,除非我们有从“一侧到另一侧”的环境自然转换,否则应用程序不会关闭总和。它们也是非规范的,因为它们需要偏见。
  • AMP 没有修复最后一段中的那部分吗?
  • 太棒了,我在链接答案中定义的 'LetT' 转换器只是 'Compose (Ap Identity)'
【解决方案2】:

正如 J. Abrahamson 所说,应用程序在产品和组合下是封闭的,因此不需要专用的变压器版本。但是,也无需推出您自己的 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 工具集中的另一个有用工具是免费的 applicative functor。我通常使用Edward Kmett's free library's version,但如果您想要更少的依赖项,也可以轻松滚动。

这些定义也很有用(尽管我欢迎有关命名方案的建议,尤其是“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

【讨论】:

  • “隐藏使用 compose 并让你保持清醒的一堆实用功能”是 mtl 类为 monad 提供的东西,也是我正在寻找的应用程序。
  • 还有mapI :: Functor f =&gt; (g a -&gt; h b) -&gt; (f :. g) a -&gt; (f :. h) bmapI f = Compose . fmap f . getCompose。它可以让你提升类似箭头的功能。 mapI2 :: Applicative f =&gt; (g a -&gt; h b -&gt; i c) -&gt; (f :. g) a -&gt; (f :. h) b -&gt; (f :. i) c 其中mapI2 f (Compose fga) (Compose fhb) = Compose (fmap f fga &lt;*&gt; fhb) 允许您提升一般的二元运算符和函数。
  • 如果我理解正确,你是在说Data.Fuctor.ComposeData.Functor.ProductData.Functor.ConstantData.Functor.IdentityControl.Applicative.Lift,是编写单子转换器的通用方法(如状态读取器和写入器) ,这样你就可以使用GeneralizedNewtypeDeriving?
猜你喜欢
  • 2020-10-31
  • 1970-01-01
  • 2019-10-08
  • 2017-11-14
  • 2018-09-10
  • 1970-01-01
  • 2022-08-14
  • 1970-01-01
  • 2021-01-14
相关资源
最近更新 更多