Can someone explain to me why the function of the ArrowApply app makes them as powerful as monads?

So, I will break my question into 4 parts, but first a bit of background:

I feel relatively comfortable with Monads, but not very comfortable with Arrows. I believe that the main problem I am facing is that I do not understand what they are useful for. Whether it is formally correct or not, I understand that Monads is a tool that allows us to introduce side effects when calculating. Because they summarize program fragments from pure values ​​to values ​​that are boxed with other actions. From my shotgun “read all documents” to find out about the arrows, I came across two conflicting points of view:

but. Arrows more powerful than Monads / are generalizations of Monads. The haskell wiki begins 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 Monads. With ArrowApply we can define a monad

  • Is there any truth to point A?
  • What functionality do arrows have, I read that the difference is related to composition, therefore, that the → → operator allows us to do this → = not?
  • What does the application do for sure? this type doesn't even have (->)
  • Why would we like to use applicative arrows over monads?
+24
functional-programming haskell monads arrows
Jul 16 '13 at 5:10
source share
3 answers

Warnings with multiple interpreters:

"A is more powerful than B" ... "C is a generalization of D" ... "E can do anything that F can do, and more than" ... "G is a subset of H" ..

First we need to understand what we mean by powerful, etc. Suppose we had a GripHandle class for things with a grip handle and another Screwdriver class for screwdrivers. Which is more powerful?

  • Well, if you just have enough handles, it is not as useful as if you were a screwdriver; the handle for the handle itself is not very convenient, therefore, obviously, because you can do more with a screwdriver than the grip handle, so screwdrivers are more powerful.
  • It is good that more things have a grip handle, and not just screwdrivers - drills, knives and forks, all types have handles, so the handles are more powerful and flexible.
  • Well, if you have a screwdriver, you can not only hold it, you can rotate it, but be able to rotate, and not just keep the screwdrivers much more powerful and flexible than the clutch handles.

Well, this is a stupid argument, but it raises a good point about how the phrase “You can do more with _____” is rather ambiguous.

If you stick only to the interface, a screwdriver is more useful than a , but all things with handles together are useful than all screwdrivers if you use more functions than just the interface.

How the hierarchy works

A is more general than B
= A for interface only - this is a subset of B = you can do more with instance B (alone)
= The class of all B is a subset of the class of all A = more than A than B = you can do more with class A

more general
= more possible cases
= ability to be more widely used
= can do extra things backstage
= less features are specified in the interface
= can do the least through the interface

What is the hierarchy between arrows and monads?

  • Arrow more general than Monad .
  • ArrowApply exactly as generic as Monad .

These two statements are described in detail in an article by Peter Pudlak, which is mentioned in his commentary: Idioms do not pay attention, arrows are meticulous, monads are illegible .

Statements A and B

  • "Arrows can do everything the monads can do, and more."
    This is marketing. It's true, but you have to jump a little semantically to make it true. ArrowApply instance on its own allows you to do everything that a Monad instance does on its own. You cannot do more with ArrowApply than with Monad . You can do more with Arrows stuff. The requirement "all monads can do" probably refers to ArrowApply , while the requirement "and more" probably refers to Arrow ! Monad's bulletin board may say, “With Monads, you can do everything Arrows can do, and much more,” due to the increased expressiveness of the interface. Because of this, these statements are ambiguous and have little formal significance.
  • "They are roughly comparable to monads with a static component."
    Strictly speaking, no, this is just what you can do with Arrow , which you cannot do directly with Monad , and not a mathematical fact about the Arrow interface. This is a way to help you deal with what we can do with Arrow, similar to how the Monad analog is a value box. Not all monads are easily interpreted as a box with a value of in, but this may help you a bit in the early stages.
  • "Arrows are a subset of Monads"
    Perhaps this is misleading. It’s true that only arrows for the interface are a subset of the Monads interface capabilities, but it’s fair to say that the class of all Monads is a subset of the class of all Arrows, because Arrow more General.
  • "With ArrowApply we can define a monad"
    Yes. See Later, and with Monad we can define ArrowApply.

Your four questions

  • Is there any truth to point A?
    Some. See above. There, too, the truth is in B. Both are more or less misleading.
  • What functionality arrows do not have, I read that the difference is related to composition, therefore the operator →> allows us to do this → = does not
    In fact, >>= allows you to do more than >>> (more features provided by the interface). This allows you to switch context. This is because Monad m => a -> mb is a function, so you can execute arbitrary clean code at input A before deciding which monadic thing to do, while Arrow m => mab not a function, and you decided before you check input A

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

    It is not possible to simulate this using Arrow without using the app from ArrowApply

  • What does the application do for sure? this type doesn't even have (->)
    It allows you to use the arrow output in the form of an arrow. Let's look at the type.

     app :: ArrowApply m => m (mbc, b) c 

    I prefer to use m for A , because m more like a calculation, and A feels like a value. Some people like to use a type operator (constructor like infix), so you get

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

    We think of b ~> c as an arrow, and we think of an arrow as a thing that takes B s, does something and gives c s. Thus, this means that app is an arrow that takes an arrow and a value, and can generate the value that the first arrow would produce at this input.

    It does not have a -> 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 don’t you can turn each arrow into a function, so (b ~> c, b) ~> c can be used if (b ~> c, b) -> c or (b -> c, b) ~> c will not.

    We can easily create an arrow that creates an arrow or even several arrows, even without ArrowApply, simply by doing produceArrow :: Arrow (~>) => (b ~> c) -> (any ~> (b ~> c)) , defined using produceArrow a = arr (const a) . The difficulty is for the arrow to make any arrow - how do you get the arrow you created to become the next arrow? You cannot put it as the next calculation using >>> , as you can do with a monadic function Monad m => a -> mb (just do id :: ma -> ma !), Because, in essence, the arrows are not functions, but using the app , we can do the next arrow, doing what the arrow would have created previous arrow.

    In this way, ArrowApply provides you with an executable runtime version created 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 are more general than Monad or Arrow (see Article), therefore they have less functionality specified in the interface, but are more widely applicable (apply / apply / apply chortle chortle lol rofl category theory humor hahahaha).

    Applicative functors have excellent syntax that is very similar to a pure function application. f <$> ma <*> mb <*> mc starts 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 functionality of the interface, so you can say that theoretically we don't need them, but some people like the arrow symbol because it looks like a symbol, and you can really use Arrow to implement parsers that have a static component, so use compile-time optimization. I believe that you can do this with Applicative, but first it was done with an arrow.

    Note that the Applicative is "less powerful":
    The document states that Applicative more general than Monad , but you can make Applicative functors have the same capabilities by providing the function run :: Applicative f => f (fb) -> fb , which allows you to start the calculation or use :: Applicative f => f (a -> fb) -> fa -> fb , which allows you to push the resulting calculation to the calculation. If we define join = run and unit = (<$>) , we get two functions that make up the same theoretical basis for Monads, and if we define (>>=) = flip (use.pure) and return = unit , we we get another one that was used in Haskell. There is no ApplicativeRun class, because if you can do this, you can make a monad, and the type labels are almost identical. The only reason we have ArrowApply instead of reusing Monad is because the types are not identical; ~> abstracted (generalized) to the interface in ArrowApply, but the application-application -> used directly in Monad. This difference is what makes programming with Arrows much better for programming in monads, despite the equivalence of ArrowApply and Monad.

  • <cough> Why would we ever want to use Arrows / ArrowApply over the Monad?
    Well, I admit that I knew this, what you meant, but I wanted to talk about applicative functors and was so passionate that I forgot to answer!

    Possible reasons: Yes, you would like to use Arrow over Monad if you had something that cannot be made into a monad. The motivational example that led us to Arrows was primarily parsers - you can use Arrow to write a parser library that does static analysis in combinators, thereby making more efficient parsers. Previous Monadic parsers cannot do this because they are a parser as a function that can do arbitrary things on the input without writing them statically, so you cannot parse them during compilation / combination.

    Syntax reasons: No, I personally would not want to use arrow-based parsers because I don’t like the proc / do arrow note - I find it even worse than monadic notation. My preferred notation for parsers is applicative, and you can write an applicative parser library that does the efficient static analysis that Arrow does, but I freely admit that the parser libraries that I usually use do not, perhaps because they want to provide the Monadic interface.

    • Monad:

        parseTerm = do x <- parseSubterm o <- parseOperator y <- parseSubterm return $ Term xoy 
    • Arrow:

        parseTerm = proc _ -> do x <- parseSubterm -< () o <- parseOperator -< () y <- parseSubterm -< () returnA -< Term xoy 
    • Application:

        parseTerm = Term <$> parseSubterm <*> parseOperator <*> parseSubterm 

      It looks like a functional application using $ several times. Mmmmm. Well maintained. To clear. Low syntax. Reminds me why I prefer Haskell to any imperative programming language.

Why is the app in ArrowApply creating Monad?

There is a Monad instance in the ArrowApply section of the Control.Arrow module, and I will edit in (~>) instead of A for my clarity of thought. (I left Functor because it’s stupid to define Monad without Functor anyway - you have to define 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 = fx in (h, ())) >>> app 

What does it do? Firstly, ArrowMonad is a newtype instead of a type synonym, so we can make an instance without any unpleasant system type problems, but we will not ignore it to go for conceptual clarity over compilation, replacing it as if it were type ArrowMonad (~>) b = () ~> b

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

(using an incompatible type operator section (()~>) as a type constructor)

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

OK, this is a little clearer what is happening. First of all, note that the correspondence between arrows and monads is between Monad m => b -> mc and Arrow (~>) => b ~> c , but the monad class does not include B in the declaration. Therefore, we need to specify the dummy value () in () ~> b in order to start working with zero input and play something like mb .

  • The fmap equivalent, in which you apply the function to your output, simply displays the result, and then runs the function in the form of an arrow: fmap fm = m >>> arr f
  • The equivalent of return (which simply creates the specified x value) is simply to run the const x function in the form of an arrow, therefore return x = arr (\_ -> x) .
  • Equivalent to bind >>= , which starts the calculation, then uses the output as an input to the function f , which can then calculate the following calculation to start: First m >>> start the first calculation m , then arr (\x -> (fx, .... with the exit, use the f function, then use this arrow as the input to the app , which behaves as if it were a drawn arrow acting on the incoming input () , as usual.
+37
Jul 16 '13 at 10:06 on
source share

A's point of view is a bit odd - generally speaking, an abstraction will not be stronger and more general than any other abstraction; these two contradict each other. Having “more power” means knowing more about the structure of what you are working with, which means more limitations. On the one hand, you know exactly what type you are working with. It is extremely powerful; you can apply any valid function to it. On the other hand, it is also not general in general: the code written with this assumption is applicable only to this type! On the other hand, you know nothing about your type (for example, with a variable of type a ). This is very general, applicable to each type, but also not very powerful, because you do not have enough information to do anything at all!

An example of a larger root in real code is the difference between Functor and Applicative . Here Functor is more general - strictly more Functor types than Applicative s, since each Applicative also a Functor , but not vice versa. However, since Applicative has a larger structure, it is strictly more powerful. With Functor you can only display functions with one argument over your type; with Applicative you can display functions of any number of arguments. Again: one is more general, the other is more powerful.

So what is this? Are arrows more powerful or more general than monads? This is a more complicated question than comparing functors, applicative functors and monads, because arrows are a completely different beast. They even have a different look: monads and others. There is a look * -> * , where the arrows look like * -> * -> * . Fortunately, it turns out that we can identify arrows with applicative functors / monads, so we can really answer this question: arrows are more general than monads, and therefore less powerful. Given any monad, we can build an arrow out of it, but we cannot build a monad for each arrow.

The basic idea is as follows:

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

However, since we have an arrow instance for a -> b , we have to wrap a -> mb in newtype in real code. This newtype is called Klesli (due to the Klesli categories ).

However, we cannot go the other way - there is no construction to get Monad from any Arrow . This is because Arrow computation cannot change its structure based on the values ​​flowing through it, while monads can. The only way around this is to add strength to your arrow abstraction with some extra primitive function; this is exactly what ArrowApply does.

The >>> operator for arrows is a generalization of a function . for functions and, therefore, has the same general limitations. >>= , on the other hand, is more like a generalization of a function application. Pay attention to the types: for >>> , both sides are arrows; for >>= first argument is the value ( ma ), and the second is the function. Moreover, the result >>> is another arrow, the result of which is the result >>= . Since arrows have only >>> , but are not equivalent to >>= , you cannot "apply" them to arguments at all - you can only create pipelines for pipelines. The actual apply / run function must be specific to any given arrow. Monads, on the other hand, are defined in terms of >>= , and therefore come with some default app concept.

ArrowApply simply extends the arrows with the app , which is the general concept of the application. Imagine a normal application:

 apply :: (b -> c) -> b -> c apply fx = fx 

we can parse this:

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

The way to generalize arrow functions is basically to replace -> with variable ( a ). Let's do it for apply , replacing both occurrences -> infix a :

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

We can still see the same structure as the first version of apply , just uncurried and `a` instead of -> . Now, if we just get rid of backticks and prefix a , we get a signature for the app :

 app :: a (abc, b) c 

So we see how ArrowApply just adds some concept of the application to the arrows. This is a parallel with >>= , which is the concept of an application for monads (or, in particular, functions of the form a -> mb ). This is quite an additional structure for building a monad from an arrow, so ArrowApply is isomorphic to Monad .

Why will we ever want to use them? Honestly, I don’t think we will do it. The arrows are quite overpriced, so stick with monads and applicative functors.

+10
Jul 16 '13 at 10:21
source share

Monad is a tool that allows us to write in an imperative style (step by step).

- , -.

, -.

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

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

+1
01 . '13 22:37
source share



All Articles