tomjaguarpaw / Arrows2

Ideas for the next generation of Haskell's Arrow notation
12 stars 0 forks source link

Passing arguments to control operators is often backwards #4

Open lexi-lambda opened 4 years ago

lexi-lambda commented 4 years ago

Control operators have two kinds of arguments: function arguments (which are arrows) and arrow arguments. For an example, consider a function like local:

local :: ArrowReader r arr => arr e a -> arr (e, r) a

Like local from MonadReader, this runs the argument in a modified environment (though unlike the MonadReader version, it accepts the environment directly instead of being given a function to apply to it). Using it in arrow notation looks like this:

y <- (| local (f -< x) |) r

This works okay when the argument to local is small, but it becomes very confusing when the argument command is large. For example, I might want to run a whole block with a modified environment, so I would have to write something like this:

r <- ask -< ()
w <- (| local (do
          y <- f -< x
          z <- g -< y
          h -< (y, z))
     |) (foo $ bar r)

I think this looks totally backwards. Very often, when writing monadic code, I do something like this:

w <- flip local (foo . bar) $ do
       y <- f x
       z <- g y
       h y z

But there isn’t any way to write code that way in arrow notation. This means that if I have a series of nested control operators, I end up with something like

(| withRecordInconsistency
   ((| withRecordDependencies
       ((| mapErrorA (f -< (e, s))
         |) \e -> "in permission for role " <> roleName <<> ": " <> e)
     |) metadataObject schemaObject)
 |) metadataObject

which looks terrible and is hard to read. The equivalent monadic code it was adapted from looks like

withRecordInconsistency metadataObject $
  withRecordDependencies metadataObject schemaObject $
    modifyErr (\e -> "in permission for role " <> rn <<> ": " <> e) $
      m

which is far better.