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
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.