本文介绍了有人能向我解释为什么ArrowApply的应用程序功能使它们像单子一样强大?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 所以我会把我的问题分成4个部分,但首先是一些背景: 我觉得Monads比较舒服,但对Arrows不太舒服。我想我遇到的主要问题是,我没有看到他们有什么用处。不管形式上是否正确,我都明白Monads是一种让我们从计算中引入副作用的工具。因为它们将程序片段从纯粹的值推广到用其他操作装箱的值。从我的霰弹枪阅读所有论文的方法来学习箭头,我遇到了两个相互冲突的观点: A。箭头比单子更强大/是单子的普遍化。 haskell wiki的开头是它们可以完成monad可以做的所有事情,而且更多,它们与具有静态组件的monads大致相当。 B 。箭头是Monads的一个子集借助ArrowApply,我们可以定义一个monad 视图A有任何事实吗? 箭头没有什么类型的功能,我已经看到差别与组合有关,那么>>>运算符允许我们做什么>> =不是? 应用程序到底做了什么?它的类型甚至没有( - >) 为什么我们会想要使用适用于箭头的箭头? $ b $ 首先,我们需要掌握强大的等等。假设我们有一个类 GripHandle 用于有把手的物品,另一个用于螺丝刀的类螺丝刀。哪个更强大? 很明显,如果你只是有一把手,那不如你是一把螺丝刀;一个单独的手段并没有多大用处,所以很明显,因为你可以用螺丝刀做更多的事情而不是抓手,所以螺丝刀更强大。 很明显,更多的东西比起螺丝刀有更多的抓握手柄 - 钻头,刀叉和各种各样的东西都具有格格不入,所以抓手更加强大和灵活。 b $ b 很明显,如果你有一把螺丝刀,你不仅可以拿着它,而且可以转动它,并且有能力转动而不是只握住螺丝刀,这使得螺丝刀比griphandles更加强大和灵活。 > 好的,这是一个愚蠢的论点,但它提出了一个关于你可以用一个_____做得更多这个词的含义很模糊的观点。 如果您仅仅依靠界面, a 螺丝刀比 a 手柄更有用,但如果您使用的功能多于接口,所有使用griphandles的东西都比 all 螺丝刀更有用。 层次结构的工作原理 A 比 B 更普遍 = A 的仅接口功能是 B 的子集= s =您可以做更多使用 B 实例(单独) =所有 B s是一个子集所有 A s =的类的类别比 B s c $ c> s =您可以通过 A 类来做更多的事情 更普遍 =更多可能的实例 =可以更广泛地使用 =可以在幕后执行额外的操作 =界面中指定的功能更少 =可以做更少的事情通过接口 箭头和monad之间的层次结构如何? 箭头比 Monad 更普遍。 ArrowApply 与 Monad 完全一样。 PetrPudlák在他的评论中提到:习语不经意,箭头一丝不苟,monads杂乱无章。 A和B中的断言 箭头可以完成monad可以做的所有事情,等等。 这是市场营销。这是真的,但你必须有点语义上的跳跃才能实现它。一个 ArrowApply 实例可以让它自己完成一切 Monad 实例。对于 ArrowApply ,您不能使用 Monad 做更多事情。您 可以对箭头做更多的事情。 monad可以做的一切的声明可能指的是 ArrowApply ,而声明和更多可能指的是 Arrow ! Monad市场营销委员会可以说:随着Monad,你可以做所有的箭头可以做的事情,更多,因为界面的表现力增强。这些语句含糊不清,因此没有什么正式含义。 它们与具有静态组件的monads大致相当。 严格地说,不,这只是你可以用 Arrow 做的事情,你不能直接用 Monad ,而不是关于Arrow界面的数学事实。这是一种帮助你掌握我们可以用Arrow来做什么的方式,类似于Monad的类比方式,它是一个有价值的盒子。并不是所有的单子都可以被理解为一个有值的盒子,但在早期阶段它可能会有所帮助。 箭头是Monads的子集 这可能会引起误解。它 是真的,Arrows的仅接口功能是Monad的仅接口功能的一个子集,但更公平地说,所有Monad的类都是所有箭头类的子集,因为箭头更一般。 借助ArrowApply,我们可以定义monad 是。请参阅后面的内容,使用Monad,我们可以定义一个ArrowApply。 您的四个问题 观点A有任何事实吗? 有些。往上看。 B也有一些事实。这两种方式都会以某种方式误导人。 箭头没有什么样的功能,我已经读过,区别与组合有关,所以>>>运算符是否允许我们这样做>> =否? 事实上>> = 允许你可以做到超过>>> (更多接口提供的功能)。它允许你进行上下文切换。这是因为 Monad m => a - > mb 是一个函数,因此您可以在决定运行哪个单一事物之前在输入 a 上执行任意纯代码,而 Arrow m => mab 不是一个函数,并且在检查输入 a 之前,您已经决定要运行哪个箭头。 monadSwitch :: Monad m => m a - > m a - > (Bool - > ma) monadSwitch计算1计算2测试 =如果测试然后计算1其他计算2 无法使用 Arrow 而不使用 app 从> ArrowApply app究竟做了什么?它的类型甚至没有( - >) 它可以让您将箭头的输出用作箭头。我们来看看这个类型。 app :: ArrowApply m => m(mbc,b)c 我更喜欢使用 m 到 a ,因为 m 感觉更像是一个计算,而 a 感觉就像一个值。有些人喜欢使用类型运算符(中缀类型构造函数),所以你可以得到 app :: ArrowApply(〜>) => (b〜> c,b)〜> c 我们认为 b〜> c 作为一个箭头,我们将箭头看作一个需要 b s的东西,做一些事情并给出 c 秒。所以这意味着 app 是一个箭头,它带有一个箭头和一个值,并且可以产生第一个箭头在该输入上产生的值。 在类型签名中没有 - > ,因为使用箭头编程时,我们可以将任何使用 arr :: Arrow(〜>)=> (b→c)→> b〜> c ,但不能将每个箭头转换成一个函数,因此(b〜> c,b)〜> c 可用,其中(b〜> c,b) - > c 或(b - > c,b)〜>我们可以很容易地生成一个箭头,即使没有ArrowApply,也可以生成一个箭头甚至是多个箭头,只需要执行 produceArrow :: Arrow(〜>)=> (b→c)→> (任何〜>(b〜> c))用定义的productsArrow a = arr(const a)。难度在于让箭头执行任何箭头工作 - 如何获得您制作的箭头作为下一个箭头?使用>>> ,您不能像使用monadic 功能 Monad m => a - > mb (只是做 id :: ma - > ma !),因为关键的是,箭头不是函数,而是使用 app ,我们可以使下一个箭头执行上一个箭头产生的箭头。\\ b $ b 因此ArrowApply为您提供了您从Monad获得的运行时生成的计算运行能力。 为什么我们会想要使用适用于箭头的箭头? 呃,你的意思是箭头还是应用函数? Applicative Functors非常棒。它们比Monad或Arrow更普遍(见论文),因此具有较少的界面指定功能,但更广泛适用(可以应用/应用chortle chortle lol rofl类别理论幽默hahahaha)。 Applicative Functors有一个非常像纯函数应用程序的可爱语法。 f 运行 ma 然后 mb 然后 mc 并将纯函数 f 应用于三个结果。例如。 (+)< $> readLn * readLn 从用户读取两个整数并添加它们。 您可以使用Applicative来获得一般性,并且您可以使用Monads来获得界面功能,因此您可以争辩说理论上我们不需要它们,但有些人喜欢箭头符号,因为它就像符号一样,而且确实可以使用 Arrow 来实现具有静态组件的解析器,从而应用编译时优化。我相信你可以用Applicative做到这一点,但是它先用Arrow完成。 关于Applicative的说明不够强大: 本文指出 Applicative 比 Monad 更普遍,但是您可以 可以通过提供函数 run :: Applicative f =>来使应用函数具有相同的能力。 f(f b) - > f b ,它允许您运行生成的计算,或 use :: Applicative f => f(a - > f b) - > f a - > f b ,它允许您将生成的计算推广到计算。如果我们定义 join = run 和 unit =(< $>),我们得到两个函数, Monads的理论基础,如果我们定义(>> =)= flip(use.pure)并且 return = unit 我们得到了Haskell中使用的另一个。没有 ApplicativeRun 类,只是因为如果你可以做到这一点,你可以创建一个monad,并且类型签名几乎是相同的。我们有 ArrowApply 而不是重复使用 Monad 的唯一原因是类型不相同; 〜> 被抽象(概括)到ArrowApply的接口中,但函数应用程序 - > 直接用于Monad 。尽管ArrowApply和Monad是等价的,但这种区别使得使用箭头编程在许多方面与单体编程有所不同。 咳嗽> 为什么我们想要在Monad上使用Arrow / ArrowApply? 好的,我承认我知道你的意思,但是想要谈论Applicative仿函数,带走了我忘了回答! 能力的原因:是的,如果你有一些不能做成monad的东西,你会希望使用Arrow over Monad。首先为我们带来箭头的激励性示例是解析器 - 您可以使用箭头编写一个解析器库,在组合器中执行静态分析,从而制作更高效的解析器。以前的Monadic解析器无法做到这一点,因为它们将解析器表示为函数,它可以对输入执行任意操作,而无需静态记录它们,因此无法在编译时/合并时分析它们。 句法原因:不,我个人不希望使用基于箭头的解析器,因为我不喜欢箭头 proc / do 符号 - 我发现它比单子符号更差。我对解析器的首选符号是Applicative,您可能可以编写一个Applicative解析器库来执行Arrow所做的有效静态分析,尽管我毫不犹豫地承认我通常使用的解析器库不可用,因为他们想提供一个Monadic界面。 Monad: parseTerm = do x< - parseSubterm o< - parseOperator y< - parseSubterm return $ Term xoy 箭头: parseTerm = proc _ - > do x< - parseSubterm - < () o< - parseOperator - < () y< - parseSubterm - < () returnA - <期限xoy 应用: parseTerm =期限< $> parseSubterm< *> parseOperator< *> parseSubterm 使用 $ 几次。 MMMMM。整齐。明确。语法低。 为什么ArrowApply中的应用程序创建Monad? Control.Arrow 模块,我将在( 〜>),而不是 a ,这是为了我的清晰思想。 (我已经在 Functor 中留下了,因为无论如何定义Monad而没有Functor是愚蠢的 - 您应该定义 fmap f xs = xs>> = return 。): newtype ArrowMonad(〜>)b = ArrowMonad(()〜f > b) 实例箭头(〜>)=> Functor(ArrowMonad(〜>))其中 fmap f(ArrowMonad m)= ArrowMonad $ m>>> arr f 实例ArrowApply(〜>)=> Monad(ArrowMonad(〜>))其中 return x = ArrowMonad(arr(\_-> x)) ArrowMonad m>> = f = ArrowMonad $ m> ;>> arr(\ x - >令ArrowMonad h = f x in(h,()))>>>> app 这是干什么的?那么,首先, ArrowMonad 是一个 newtype ,而不是类型同义词,所以我们可以让这个实例没有各种各样的讨厌的类型系统问题,但让我们忽略这一点,为了概念上的清晰度而不是编译性,只需将它们替换为类型ArrowMonad(〜>)b =()〜> b 实例箭头(〜>)=> Functor(()〜>)其中 fmap f m = m>>> (使用不可编译的类型运算符部分(()〜> b) )作为类型构造函数) 实例ArrowApply(〜>)=> Monad(()〜>)其中 - return :: b - > (()〜> b) return x = arr(\_-> x) - (>> =)::()〜> a - > (a - >() - > b) - > ()〜> b m>> = f = m>>> arr(\ x - >(f x,()))>>> app 好的,这会更清楚一点。首先请注意,箭头和monad之间的对应关系介于 Monad m =>>之间。 b - > m c 和 Arrow(〜>)=> b〜> c ,但monad类不涉及声明中的 b 。这就是为什么我们需要在()〜>中提供虚拟值() b 可以从零输入开始,并复制 mb 类型的内容。 相当于 fmap ,您可以在其中为输出应用函数,只需生成输出,然后以箭头形式运行该函数: fmap fm = m>>> arr f 返回值(它只是产生指定值 x )只是运行 const x 以箭头形式显示,因此 return x = arr(\_ - > x)。 运行计算的bind >> = 等价于使用输出作为函数 f 然后可以计算下一个要运行的计算:First m>>> 运行第一次计算 m ,然后 arr(\ x - >(fx,.... )与输出相关,应用函数 f ,然后使用该箭头作为 app 的输入,其行为就像输出的箭头作用于提供的输入()和往常一样!整洁! So I'll break my question into 4 parts, but first some background:I feel relatively comfortable with Monads, but not very comfortable with Arrows. I suppose the main problem I have with them is, I don't see what they are useful for. Whether formally correct or not, I understand Monads to be a tool that allows us to introduce side effects from computation. As they generalize program fragments from pure values to values boxed with other actions. From my shotgun "read all the papers" approach to learning about arrows, I've come across two conflicting viewpoints:A. Arrows are more powerful than Monads/ are generalizations of Monads.The haskell wiki starts off with "They can do everything monads can do, and more. They are roughly comparable to monads with a static component."B. Arrows are a subset of MonadsWith ArrowApply we can define a monadIs there any truth to viewpoint A?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?What does app exactly do? it's type doesn't even have an (->)Why would we ever want to use applicative arrows over monads? 解决方案 Multiply-interpretable statements alert:First we need to get a grip on what we mean by powerful etc. Suppose we had a class GripHandle for things which had a grip handle, and another class Screwdriver for screwdrivers. Which is more powerful? Well clearly, if you just have a griphandle, that's not as useful as if you're a screwdriver; a griphandle on its own isn't much use, so obviously since you can do more with a screwdriver than a griphandle, so screwdrivers are more powerful. Well clearly, more things have a grip handle than just screwdrivers - drills, knives and forks, all sort of things have griphandles, so griphandles are more powerful and flexible.Well clearly, if you have a screwdriver, you can not only hold it, you can turn it, and having the ability to turn rather than just hold makes screwdrivers much more powerful and flexible than griphandles.OK, that's a silly argument, but it raises a good point about how the phrase "You can do more with a _____" is rather ambiguous. If you stick to the interface alone, a screwdriver is more useful than a handle, but all things with griphandles are together more useful than all screwdrivers if you use more functions than just the interface.How hierarchy worksA is more general than B= A's interface-only capabilities are a subset of B's = you can do more with a B instance (alone)= The class of all Bs is a subset of the class of all As = there are more As than Bs = you can do more with the A class more general= more possible instances= able to be more widely used= can do extra things behind the scenes= fewer capabilities are specified in the interface= can do fewer things via the interfaceWhat's the hierarchy between arrows and monads?Arrow is more general than Monad. ArrowApply is exactly as general as Monad.These two statements are proved in full detail in the paper Petr Pudlák linked to in his comment: Idioms are oblivious, arrows are meticulous, monads are promiscuous.The assertions in A and B"Arrows can do everything monads can do, and more."This is marketing. It's true, but you have to jump around a bit semantically to make it true. An ArrowApply instance on its own allows you to do everything a Monad instance on its own does. You can't do more with an ArrowApply than with a Monad. You can do more with things that are Arrows. The claim "everything monads can do" probably refers to ArrowApply while the claim "and more" probably refers to Arrow! The Monad marketing board could say "With Monads, you can do everything Arrows can do, and more" because of the increased expressiveness of the interface. These statements are ambiguous and have little formal meaning because of that."They are roughly comparable to monads with a static component."Strictly speaking, no, that's just something you can do with Arrow that you can't do directly with a Monad, not a mathematical fact about the Arrow interface. It's a way of helping you get to grips with what we might do with an Arrow, in a similar way to the analogy of a Monad being a box with a value in. Not all monads are readily interpretable as a box with a value in, but it might help you a bit in the early stages."Arrows are a subset of Monads"This is perhaps misleading. It is true that the interface-only capabilities of Arrows are a subset of the interface-only capabilities of Monads, but it's fairer to say that the class of all Monads is a subset of the class of all Arrows, because Arrow is more general. "With ArrowApply we can define a monad"Yes. See later, and with a Monad, we can define an ArrowApply.Your four questionsIs there any truth to viewpoint A? Some. See above. There's some truth in B too. Both are misleading in some way or other.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? In fact >>= allows you do to more than >>> (more interface-supplied capability). It allows you to context-switch. This is because Monad m => a -> m b is a function, so you can execute arbitrary pure code on the input a before deciding which monadic thing to run, whereas Arrow m => m a b isn't a function, and you've decided which arrow thing is going to run before you examined the input a.monadSwitch :: Monad m => m a -> m a -> (Bool -> m a)monadSwitch computation1 computation2 test = if test then computation1 else computation2It's not possible to simulate this using Arrow without using app from ArrowApplyWhat does app exactly do? it's type doesn't even have an (->) It lets you use the output of an arrow as an arrow. Let's look at the type. app :: ArrowApply m => m (m b c, b) cI prefer to use m to a because m feels more like a computation and a feels like a value. Some people like to use a type operator (infix type constructor), so you get app :: ArrowApply (~>) => (b ~> c, b) ~> cWe think of b ~> c as an arrow, and we think of an arrow as a thing which takes bs, does something and gives cs. So this means app is an arrow that takes an arrow and a value, and can produce the value that the first arrow would have produced on that input. It doesn't have -> in the type signature because when programming with arrows, we can turn any function into an arrow using arr :: Arrow (~>) => (b -> c) -> b ~> c, but you can't turn every arrow into a function, thus (b ~> c, b) ~> c is usable where (b ~> c, b) -> c or (b -> c, b) ~> c would not be.We can easily make an arrow that produces an arrow or even multiple arrows, even without ArrowApply, just by doing produceArrow :: Arrow (~>) => (b ~> c) -> (any ~> (b ~> c)) defined with produceArrow a = arr (const a). The difficulty is in making that arrow do any arrow work - how to you get an arrow that you produced to be the next arrow? You can't pop it in as the next computation using >>> like you can do with a monadic function Monad m => a -> m b (just do id :: m a -> m a!), because, crucially, arrows aren't functions, but using app, we can make the next arrow do whatever the arrow produced by the previous arrow would have done.Thus ArrowApply gives you the runtime-generated computation runnability that you have from Monad.Why would we ever want to use applicative arrows over monads? Er, do you mean Arrows or Applicative Functors? Applicative Functors are great. They're more general than either Monad or Arrow (see the paper) so have less interface-specified functionality, but are more widely applicable (get it? applicable/applicative chortle chortle lol rofl category theory humor hahahaha). Applicative Functors have a lovely syntax that looks very like pure function application. f <$> ma <*> mb <*> mc runs ma then mb then mc and applies the pure function f to the three results. For example. (+) <$> readLn <*> readLn reads two integers from the user and adds them. You can use Applicative to get the generality, and you can use Monads to get the interface-functionality, so you could argue that theoretically we don't need them, but some people like the notation for arrows because it's like do notation, and you can indeed use Arrow to implement parsers that have a static component, thus apply compile-time optimisations. I believe you can do that with Applicative, but it was done with Arrow first.A note about Applicative being "less powerful": The paper points out that Applicative is more general than Monad, but you could make Applicative functors have the same abilities by providing a function run :: Applicative f => f (f b) -> f b that lets you run a produced computation, or use :: Applicative f => f (a -> f b) -> f a -> f b that allows you to promote a produced computation to a computation. If we define join = run and unit = (<$>) we get the two functions that make one theoretical basis for Monads, and if we define (>>=) = flip (use.pure) and return = unit we get the other one that's used in Haskell. There isn't an ApplicativeRun class, simply because if you can make that, you can make a monad, and the type signatures are almost identical. The only reason we have ArrowApply instead of reusing Monad is that the types aren't identical; ~> is abstracted (generalised) into the interface in ArrowApply but function application -> is used directly in Monad. This distinction is what makes programming with Arrows feel different in many ways to programming in monads, despite the equivalence of ArrowApply and Monad.< cough > Why would we ever want to use Arrows/ArrowApply over Monad? OK, I admit I knew that's what you meant, but wanted to talk about Applicative functors and got so carried away I forgot to answer!Capability reasons: Yes, you would want to use Arrow over Monad if you had something that can't be made into a monad. The motivating example that brought us Arrows in the first place was parsers - you can use Arrow to write a parser library that does static analysis in the combinators, thus making more efficient parsers. The previous Monadic parsers can't do this because they represent a parser as a function, which can do arbitrary things to the input without recording them statically, so you can't analyse them at compile time/combine time.Syntactic reasons: No, I personally wouldn't want to use Arrow based parsers, because I dislike the arrow proc/do notation - I find it even worse than the monadic notation. My preferred notation for parsers is Applicative, and you might be able to write an Applicative parser library that does the efficient static analysis that the Arrow one does, although I freely admit that the parser libraries I commonly use don't, possibly because they want to supply a Monadic interface.Monad: parseTerm = do x <- parseSubterm o <- parseOperator y <- parseSubterm return $ Term x o yArrow: parseTerm = proc _ -> do x <- parseSubterm -< () o <- parseOperator -< () y <- parseSubterm -< () returnA -< Term x o yApplicative: parseTerm = Term <$> parseSubterm <*> parseOperator <*> parseSubtermThat just looks like function application using $ a few times. Mmmmm. Neat. Clear. Low syntax. Reminds me why I prefer Haskell to any imperative programming language.Why does app in ArrowApply make a Monad?There's a Monad instance in the ArrowApply section of the Control.Arrow module, and I'll edit in (~>) instead of a for my clarity of thought. (I've left Functor in because it's silly to define Monad without Functor anyway - you should define fmap f xs = xs >>= return . f.):newtype ArrowMonad (~>) b = ArrowMonad (() ~> b)instance Arrow (~>) => Functor (ArrowMonad (~>)) where fmap f (ArrowMonad m) = ArrowMonad $ m >>> arr finstance ArrowApply (~>) => Monad (ArrowMonad (~>)) where return x = ArrowMonad (arr (\_ -> x)) ArrowMonad m >>= f = ArrowMonad $ m >>> arr (\x -> let ArrowMonad h = f x in (h, ())) >>> appWhat does that do? Well, first, ArrowMonad is a newtype instead of a type synonym just so we can make the instance without all sorts of nasty type system problems, but lets ignore that to go for conceptual clarity over compilability by substituting in as if it were type ArrowMonad (~>) b = () ~> binstance Arrow (~>) => Functor (() ~>) where fmap f m = m >>> arr f(using an uncompilable type operator section (()~>) as a type constructor)instance ArrowApply (~>) => Monad (() ~>) where -- return :: b -> (() ~> b) return x = arr (\_ -> x) -- (>>=) :: ()~>a -> (a -> ()~>b ) -> ()~>b m >>= f = m >>> arr (\x -> (f x, ()) ) >>> appOK, that's a bit clearer what's going on. Notice first that the correspondence between arrows and monads is between Monad m => b -> m c and Arrow (~>) => b ~> c, but the monad class doesn't involve the b in the declaration. That's why we need to supply the dummy value () in () ~> b to get things started on zero input and replicate something of type m b.The equivalent of fmap where you apply a function to your ouput, is just produce the output, then run the function in arrow form: fmap f m = m >>> arr fThe equivalent of return (which just produces the specified value x) is just to run the function const x in arrow form, hence return x = arr (\_ -> x).The equivalent of bind >>=, which runs a computation then uses the output as the input to a function f which can then calculate the next computation to run is: First m >>> run the first computation m, then arr (\x -> (f x, .... with the output, apply the function f , then use that arrow as the input to app which behaves as if it were the outputted arrow acting on the supplied input () as usual. Neat! 这篇关于有人能向我解释为什么ArrowApply的应用程序功能使它们像单子一样强大?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 10-16 23:30