4

I tried writing down joinArr :: ??? a => a r (a r b) -> a r b. I came up with a solution which uses app, therefore narrowing the a down to ArrowApply's:

joinArr :: ArrowApply a => a r (a r b) -> a r b
joinArr g = g &&& Control.Category.id >>> app

Is it possible to have this function written down for arrows?

My guess is no.

Control.Monad.join could have been a good stand-in for >>= in the definition of the Monad type class: m >>= k = join $ k <$> m.

Having joinArr :: Arrow a => a r (a r b) (a r b) on our hands, it would be possible to write down instance Arrow a => Monad (ArrowMonad a):

m >>= k = joinArr (k <$> m)

Please note that joinArr should be slightly tweaked to be able to deal with the wrapper. If we speak of ArrowApply:

joinArr :: ArrowApply a => ArrowMonad a (ArrowMonad a b) -> ArrowMonad a b
joinArr (ArrowMonad m) = ArrowMonad $
   m &&& Control.Category.id >>>
   first (arr (\x -> let ArrowMonad h = x in h)) >>> 
   app

instance ArrowApply a => Monad (ArrowMonad a) is already implemented in the source file.

I reckon this argument not to be the best one (if it is right).

Am I right? What is the more formal way to back this up (or disprove it)?

Zhiltsoff Igor
  • 1,812
  • 8
  • 24
  • 4
    Hint: not every `Arrow` is a `Monad`. – Joseph Sible-Reinstate Monica Jun 23 '20 at 20:40
  • @JosephSible-ReinstateMonica true, that’s what I’m basing my argument on - we can be sure that an `Arrow` does not give rise to a `Monad` generally. Yet I reckon `joinArr :: Arrow a => a r (a r b) -> a r b` to enable us to write this down. Or am I using this fact wrongly? – Zhiltsoff Igor Jun 23 '20 at 20:45
  • 2
    If you had `joinArr`, then you could write `instance Monad SomeArrowThatIsntAMonad where m >>= f = joinArr (f <$> m)`. – Joseph Sible-Reinstate Monica Jun 23 '20 at 20:47
  • @JosephSible-ReinstateMonica yes, that was my point. At least I think so. How does it differ from my argument? – Zhiltsoff Igor Jun 23 '20 at 20:49
  • I think you're right. To make this formal, the only missing piece is a proof that there is some `Arrow` that doesn't admit a lawful `Monad` instance. – Joseph Sible-Reinstate Monica Jun 23 '20 at 20:51
  • 3
    (1) Looking at it from another direction: between `(.)`, `id`, `arr` and `first`, there isn't anything that allows collapsing two `a r` layers into one. (2) @JosephSible-ReinstateMonica It's not a particularly enlightening example, perhaps, but [`Static`](https://hackage.haskell.org/package/semigroupoids-5.3.4/docs/Data-Semigroupoid-Static.html) for any applicative which isn't a monad should suffice. [`Scan`](http://hackage.haskell.org/package/foldl-1.4.5/docs/Control-Scanl.html) from the *foldl* library might be a more interesting illustration. – duplode Jun 23 '20 at 21:00
  • @JosephSible-ReinstateMonica I wish people stopped saying it like that. **No** arrow is a monad, they're abstractions on a fundamentally different approach. Many (not all) arrows _give rise_ to a monad, but it's missing the point to always discuss them only in that light. And probably a reason why arrows / cartesian monoidal categories haven't found much widespread applications yet; though the more important reason is certainly the fact that the unconstrained, mono-kinded, **Hask**-specific `Arrow` class as it stands doesn't really allow exploiting any of those beyond-monadish capabilities. – leftaroundabout Jun 24 '20 at 08:50

1 Answers1

6

I think the formal reason that you can’t implement a x (a x y) -> a x y using only Arrow is that this requires a notion of either application (as you tried) or currying, or rather uncurrying in this case:

uncurry :: a x (a y z) -> a (x, y) z

With that, joinArr is simply:

joinArr :: a x (a x y) -> a x y
joinArr f = dup >>> uncurry f
  where dup = id &&& id

But if we can’t implement this without apply, curry, or uncurry, that means that a must be a Cartesian closed category (CCC) because we need some notion of “exponential” or higher-order arrow, which ArrowApply gives us, but Arrow only gives us a Cartesian category. (And I believe ArrowApply is equivalent to Monad because Monad is a strong monad in a CCC.)

The closest you can get with only Arrow is an Applicative, as you saw in the definition of instance (Arrow a) => Applicative (ArrowMonad a), which happens to be equivalent in power to join in the Reader monad (since there join = (<*> id)), but not the stronger monadic join:

joinArr' :: a x (x -> y) -> a x y
joinArr' f = (f &&& id) >>> arr (uncurry ($))

Note that instead of a higher-order arrow here, a x (a x y), we just reuse the (->) type.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • How can we be sure there's no other way to write `joinArr` down (without `app` or `uncurry`/`curry`)? Is this an observation or am I missing out on some theory? – Zhiltsoff Igor Jul 10 '20 at 14:22
  • @ZhiltsoffIgor: Because the type `a x (a x y)` is a “higher-order function”—that is, it has an `a` constructor *under* another `a`. This is using the strict definition of HoF, which includes *returning* functions (currying) in addition to accepting them as inputs, whereas in Haskell we informally call only the latter case a HoF, because we always have the option to uncurry `(->)`. The *minimal* structure needed to allow this is a category with embedded exponentials, which is precisely a CCC, which we spell `ArrowApply` or `Monad`. Not sure, but I think you could prove it by parametricity… – Jon Purdy Jul 10 '20 at 18:24