首页 文章

有人可以向我解释为什么ArrowApply的应用功能使它们像monad一样强大吗?

提问于
浏览
25

所以我将我的问题分成4个部分,但首先是一些背景:

我对Monads感觉相对舒服,但对Arrows不太满意 . 我想我遇到的主要问题是,我看不出它们对它们有用 . 无论形式是否正确,我都理解Monads是一种工具,可以让我们从计算中引入副作用 . 因为它们将程序片段从纯值推广到用其他动作装箱的值 . 从我的霰弹枪“阅读所有论文”的方法来学习箭头,我遇到了两个相互矛盾的观点:

答:箭头比Monads更强大/是Monads的概括 . haskell wiki的开头是“他们可以做monad所能做的一切,甚至更多 . 它们与具有静态组件的monad大致相当 . ”

B.箭头是Monads的子集使用ArrowApply我们可以定义monad

  • 观点A有什么道理吗?

  • 箭头没有什么功能,我've read that the difference has to do with composition, so what does the >>> operator allow us to do that >>= doesn't?

  • app究竟做了什么?它's type doesn'甚至有一个( - >)

  • 为什么我们想要在monad上使用适用的箭头?

3 回答

  • 40

    可解释的语句提醒:

    “A比B更强大”......“C是D的概括”......“E可以做F所能做的一切,而且更多”......“G是H的子集”......

    首先,我们需要 grab 强大的等等我们的意思 . 假设我们有一个类 GripHandle 用于具有握柄的东西,另一个类 Screwdriver 用于螺丝刀 . 哪个更强大?

    • 很明显,如果你只有一个握柄,那就是一把螺丝刀;单独使用一个手柄并没有多大用处,所以很明显,因为你可以使用螺丝刀而不是手柄来做更多的事情,所以螺丝刀更强大 .

    • 很明显,除了螺丝刀之外,更多东西都有抓握手柄 - 钻头,刀叉,所有类型的东西都有抓握手柄,因此抓握手柄更强大,更灵活 .

    • 很明显,如果你有一把螺丝刀,你不仅可以握住它,你可以转动它,并且有能力转动而不仅仅是握住使得螺丝刀比握柄更强大和灵活 .

    好吧,这是一个愚蠢的论点,但它提出了一个很好的观点,即“你可以用_____做更多”这句话是多么含糊不清 .

    如果你坚持使用单独的界面, a 螺丝刀比 a 手柄更有用,但是 all 带有把手的东西比 all 螺丝刀更有用,如果你使用的功能多于接口 .

    层次结构的工作原理

    AB 更通用
    = A 的仅接口功能是 B 的子集=您可以使用 B 实例(单独)执行更多操作
    =所有 B 的类是所有类的子集 A s =还有 A s比 B s =你可以用 A 类做更多

    更一般
    =更多可能的实例
    =能够更广泛地使用
    =可以在幕后做额外的事情
    =界面中指定的功能较少
    =可以通过界面做更少的事情

    箭头和单子之间的层次结构是什么?

    • ArrowMonad 更通用 .

    • ArrowApplyMonad 完全一样 .

    PetrPudlák在其评论中所述的论文中详细证明了这两个陈述:Idioms are oblivious, arrows are meticulous, monads are promiscuous .

    A和B中的断言

    • "Arrows can do everything monads can do, and more."
      这是营销 . 这是真的,但你必须在语义上跳一点才能使它成为现实 . ArrowApply 实例本身允许您自己完成 Monad 实例的所有操作 . 使用 ArrowApply 比使用 Monad 做的更多 . 您可以使用 Arrows 做更多事情 . 索赔"everything monads can do"可能是指 ArrowApply ,而索赔"and more"可能是指 Arrow !由于界面表现力的提高,Monad营销委员会可以说"With Monads, you can do everything Arrows can do, and more" . 这些陈述含糊不清,因此具有很少的正式含义 .

    • "They are roughly comparable to monads with a static component."
      严格来说,不,这只是你可以用 Arrow 做的事情,你不能直接使用 Monad ,而不是关于Arrow界面的数学事实 . 这是一种帮助你掌握我们可能用箭头做什么的方式,类似于Monad是一个有 Value 的盒子的类比 . 不是所有的monad都很容易可以解释为具有值的盒子,但它可能在早期阶段对您有所帮助 .

    • "Arrows are a subset of Monads"
      这可能会产生误导 . 确实,Arrows的仅接口功能是Monads的仅接口功能的一个子集,但更公平地说所有Monads的类是所有Arrows的类的子集,因为 Arrow 更通用 .

    • "With ArrowApply we can define a monad"
      是 . 稍后看一下,使用Monad,我们可以定义一个ArrowApply .

    你的四个问题

    • Is there any truth to viewpoint A?
      一些 . 往上看 . B也有一些道理 . 两者都以某种方式误导 .

    • What kind of functionality do arrows not have, I've read that the difference has to do with composition, so what does the >>> operator allow us to do that >>= doesn't?
      实际上 >>= 允许您执行超过 >>> (更多接口提供的功能) . 它允许您进行上下文切换 . 这是因为 Monad m => a -> m b 是一个函数,所以你可以在决定运行哪个monadic之前在输入 a 上执行任意纯代码,而 Arrow m => m a b isn 't a function, and you' ve决定在检查输入 a 之前运行哪个箭头 .

    monadSwitch :: Monad m => m a -> m a -> (Bool -> m a)
    monadSwitch computation1 computation2 test 
          = if test then computation1 else computation2
    

    使用 Arrow 无法使用 ArrowApplyArrowApply 模拟此情况

    • What does app exactly do? it's type doesn't even have an (->)
      它允许您使用箭头的输出作为箭头 . 我们来看看类型 .
    app :: ArrowApply m => m (m b c, b) c
    

    我更喜欢使用 ma ,因为 m 感觉更像是一个计算而 a 感觉就像一个值 . 有些人喜欢使用类型操作符(中缀类型构造函数),所以你得到了

    app :: ArrowApply (~>) => (b ~> c, b) ~> c
    

    我们认为 b ~> c 是一个箭头,我们认为箭头是一个需要 b 的东西,做了什么并且给了 c s . 所以这意味着 app 是一个带箭头和值的箭头,可以产生第一个箭头在该输入上产生的值 .

    它在类型签名中没有 -> ,因为当使用箭头编程时,我们可以使用 arr :: Arrow (~>) => (b -> c) -> b ~> c 将任何函数转换为箭头,但是您无法将每个箭头转换为函数,因此 (b ~> c, b) ~> c 可用于 (b ~> c, b) -> c(b -> c, b) ~> c 不会 .

    即使没有ArrowApply,我们也可以轻松制作一个产生箭头甚至多个箭头的箭头,只需使用 produceArrow a = arr (const a) 定义 produceArrow :: Arrow (~>) => (b ~> c) -> (any ~> (b ~> c)) 即可 . 困难在于让箭头做任何箭头工作 - 如何让你生成的箭头成为下一个箭头?你无法使用 >>> 作为下一次计算弹出它,就像你可以用monadic function Monad m => a -> m b (只做 id :: m a -> m a !),因为,至关重要的是,箭头不是函数,但是使用 app ,我们可以使下一个箭头做无论上一个箭头产生的箭头是什么 .

    因此,ArrowApply为您提供了Monad的运行时生成的计算运行性 .

    • Why would we ever want to use applicative arrows over monads?
      呃,你的意思是箭头或应用函数? Applicative Functors很棒 . 它们比Monad或Arrow更常见(参见论文),因此具有较少的界面指定功能,但更广泛适用(得到它?适用/适用chortle chortle lol rofl类别理论幽默hahahaha) .

    Applicative Functors具有可爱的语法,看起来非常像纯函数应用程序 . f <$> ma <*> mb <*> mc 运行 ma 然后 mb 然后 mc 并将纯函数 f 应用于三个结果 . 例如 . (+) <$> readLn <*> readLn 从用户读取两个整数并添加它们 .

    您可以使用Applicative来获得通用性,并且可以使用Monads来获取接口功能,因此您可以认为理论上我们不喜欢使用表示法,并且您确实可以使用 Arrow 来实现具有静态组件的解析器,因此应用编译时优化 . 我相信你可以用Applicative做到这一点,但是先用Arrow完成 .

    A note about Applicative being "less powerful":
    本文指出 ApplicativeMonad 更通用,但你可以通过提供一个允许你运行生成计算的函数 run :: Applicative f => f (f b) -> f b 或者允许你将生成的计算提升到计算的函数来使Applicative函数具有相同的能力 . 如果我们定义 join = rununit = (<$>) ,我们得到两个函数,为Monads提供一个理论基础,如果我们定义 (>>=) = flip (use.pure)return = unit ,我们得到另一个's used in Haskell. There isn' t ApplicativeRun 类,只是因为如果你可以做到,你可以做一个monad,类型签名几乎相同 . 我们 ArrowApply 而不是重用 Monad 的唯一原因是类型不相同; ~> 被抽象(通用)到接口中在ArrowApply中,但函数应用程序 -> 直接在Monad中使用 . 尽管ArrowApply和Monad等价,但这种区别使得使用Arrows进行编程在monad中进行编程的方式有所不同 .

    • <咳嗽> Why would we ever want to use Arrows/ArrowApply over Monad?
      好吧,我承认我知道这就是你的意思,但是想谈谈Applicative functors并让我们忘掉了,我忘了回答!

    能力原因:是的,如果你有一些无法制作成monad的东西,你会想要使用Arrow over Monad . 首先给我们带来Arrows的激励示例是解析器 - 您可以使用Arrow编写一个解析器库,在组合器中进行静态分析,从而提高解析器的效率 . 以前的Monadic解析器不能这样做,因为它们将解析器表示为一个函数,它可以对输入执行任意操作而不会静态记录它们,因此您无法在编译时/组合时分析它们 .

    句法原因:不,我个人不想使用基于箭头的解析器,因为我不喜欢箭头 proc / do 符号 - 我发现它比monadic符号更糟糕 . 我首选的解析器符号是Applicative,你可能会编写一个Applicative解析器库,它可以完成Arrow实现的有效静态分析,虽然我可以自由地承认我常用的解析器库没有,可能是因为他们想要提供Monadic接口 .

    • Monad:
    parseTerm = do
             x <- parseSubterm
             o <- parseOperator
             y <- parseSubterm
             return $ Term x o y
    
    • 箭头:
    parseTerm = proc _ -> do
             x <- parseSubterm -< ()
             o <- parseOperator -< ()
             y <- parseSubterm -< ()
             returnA -< Term x o y
    
    • 适用:
    parseTerm = Term <$> parseSubterm <*> parseOperator <*> parseSubterm
    

    这看起来像使用 $ 几次的函数应用程序 . MMMMM . 整齐 . 明确 . 语法低 . 提醒我为什么我更喜欢Haskell任何命令式编程语言 .

    为什么ArrowApply中的应用会成为Monad?

    Control.Arrow模块的ArrowApply部分中有一个Monad实例,为了清晰起见,我将在 (~>) 而不是 a 进行编辑 . (我已经离开了 Functor ,因为无论如何定义Monad而没有Functor是愚蠢的 - 你应该定义 fmap f xs = xs >>= return . f . ):

    newtype ArrowMonad (~>) b = ArrowMonad (() ~> b)
    
    instance Arrow (~>) => Functor (ArrowMonad (~>)) where
        fmap f (ArrowMonad m) = ArrowMonad $ m >>> arr f
    
    instance ArrowApply (~>) => Monad (ArrowMonad (~>)) where
        return x = ArrowMonad (arr (\_ -> x))
        ArrowMonad m >>= f = ArrowMonad $
            m >>> arr (\x -> let ArrowMonad h = f x in (h, ())) >>> app
    

    那是做什么的?好吧,首先, ArrowMonad 是一个 newtype 而不是一个类型的同义词,所以我们可以使实例没有各种令人讨厌的类型系统问题,但是让我们忽略这一点,通过替换为可编译性来概念清晰,就好像它是 type ArrowMonad (~>) b = () ~> b

    instance Arrow (~>) => Functor (() ~>) where
        fmap f m = m >>> arr f
    

    (使用不可编译的类型运算符部分 (()~>) 作为类型构造函数)

    instance ArrowApply (~>) => Monad (() ~>) where
     -- return :: b -> (() ~> b)
        return x = arr (\_ -> x)
     -- (>>=) ::   ()~>a   ->    (a  ->  ()~>b )   ->   ()~>b 
        m >>= f = 
            m >>> arr (\x ->  (f x, ()) ) >>> app
    

    好的,_3041731正在继续 . 首先注意箭头和monad之间的对应关系在 Monad m => b -> m cArrow (~>) => b ~> c 之间,但monad类不涉及声明中的 b . 这就是为什么我们需要在 () ~> b 中提供虚拟值 () 以使零输入开始并复制 m b 类型的东西 .

    • 将函数应用于输出的 fmap 的等价物只是产生输出,然后以箭头形式运行函数: fmap f m = m >>> arr f

    • 返回的等价物(仅生成指定值 x )只是以箭头形式运行函数 const x ,因此 return x = arr (\_ -> x) .

    • 运行计算的绑定 >>= 的等价物然后使用输出作为函数 f 的输入,然后可以计算下次运行的计算是:首先 m >>> 运行第一次计算 m ,然后输出 arr (\x -> (f x, .... ,应用函数 f ,然后使用该箭头作为 app 的输入,其行为就像往常一样作用于提供的输入 () 的输出箭头 . 整齐!

  • 1

    观点A有点奇怪 - 一般来说,抽象不会比其他抽象更强大,更通用;这两者是不一致的 . 拥有"more power"意味着更多地了解您正在使用的结构 . 这非常强大;你可以应用任何有效的功能 . 另一方面,它至少也不是通用的:用这个假设编写的代码只适用于那种类型!在另一个极端,你可以对你的类型一无所知(例如,有一个类型变量 a ) . 这是非常通用的,适用于所有类型,但也没有强大,因为你没有足够的信息可以做任何事情!

    根植于实际代码的一个例子是 FunctorApplicative 之间的区别 . 这里, Functor 更通用 - 严格来说 Functor s比 Applicative 更多,因为每个 Applicative 也是 Functor 但是反之亦然 . 但是,由于 Applicative 具有更多结构,因此它的功能更加强大 . 使用 Functor ,您只能在您的类型上映射单参数函数;使用 Applicative ,您可以映射任意数量参数的函数 . 再一次:一个更通用,另一个更强大 .

    那是哪个呢?箭头比monad更强大还是更通用?这比比较函子,应用函子和monad更难,因为箭是一种非常不同的野兽 . 他们甚至有一种不同的类型:monads et al有点 * -> * ,其中箭头有点 * -> * -> * . 令人高兴的是,事实证明我们可以使用应用函子/ monad来识别箭头,因此我们实际上可以有意义地回答这个问题:箭头比monad更通用,因此效率更低 . 给定任何monad,我们可以构造一个箭头,但是我们不能为每个箭头构造一个monad .

    基本思路如下:

    instance Monad m => Category (a -> m b) where
      id = return
      (f . g) x = g x >>= f
    
    instance Monad m => Arrow (a -> m b) where
      arr f = return . f
      first f (x, y) = f x >>= \ x' -> return (x', y)
    

    但是,由于我们有一个 a -> b 的箭头实例,我们必须将 a -> m b 包装成实际代码中的 newtype . newtype 被称为 Klesli (因为Klesli categories) .

    但是,我们不能走另一条路 - 没有任何建设可以从_3041774中获得 Monad . 发生这种情况是因为_3141775_计算不能根据流经它的值来改变其结构,而monad可以 . 解决这个问题的唯一方法是通过一些额外的原始函数为箭头抽象添加能量;这正是 ArrowApply 所做的 .

    箭头的 >>> 运算符是函数的 . 的推广,因此具有相同的一般限制 . 另一方面, >>= 更像是功能应用的概括 . 注意类型:对于 >>> ,两边都是箭头;对于 >>= ,第一个参数是一个值( m a ),第二个参数是一个函数 . 此外, >>> 的结果是另一个箭头,其中 >>= 的结果是一个值 . 由于箭头只有 >>> 但没有相当于 >>= 的概念,所以你通常不能将它们作为参数 - 你只能构造箭头管道 . 实际的应用/运行功能必须特定于任何给定的箭头 . 另一方面,Monads是根据 >>= 定义的,因此默认情况下会有一些应用概念 .

    ArrowApply 只是使用 app 扩展箭头,这是应用程序的一般概念 . 让我们想象一下正常的功能应用:

    apply :: (b -> c) -> b -> c
    apply f x = f x
    

    我们可以解决这个问题:

    apply :: ((b -> c), b) -> c
    

    箭头概括函数的方法基本上是用变量( a )替换 -> . 让我们为 apply 执行此操作,将 -> 的两次出现替换为中缀 a

    apply :: (b `a` c, b) `a` c
    

    我们仍然可以看到与 apply 的第一个版本相同的结构,只是未经验证且使用 a 而不是 -> . 现在,如果我们只是摆脱反引号并生成 a 前缀,我们将获得 app 的签名:

    app :: a (a b c, b) c
    

    所以我们看到 ArrowApply 如何向箭头添加一些应用概念 . 这与 >>= 平行,这是monad的应用概念(或者,特别是形状 a -> m b 的函数) . 这是从箭头构建monad的足够的额外结构,因此 ArrowApplyMonad 是同构的 .

    我们为什么要用这些?老实说,我认为我们不会 . 箭头被高估了,所以坚持使用monad和applicative functor .

  • 10

    Monad 是一种乐器,它允许我们以命令式的方式写作(一步一步) .

    Arrow 是一种工具,它允许我们以程序框图的形式编写 .

    因此,箭头的monad看起来像线性框图 .

    http://www.soi.city.ac.uk/~ross/talks/fop.pdf

    http://www.haskell.org/arrows/syntax.html

相关问题