gallais/idris-tparsec

Boxed combinators?

clayrat opened this issue · 8 comments

Using combinators on rec appears to be fairly common, and it seems to always require defining helpers to make types align, e.g.:

andbox : All (Box (Parser' s) :-> Box (Parser' t) :-> Box (Parser' (s, t)))
andbox {s} {t} p q = map2 {a=Parser' s} {b=Parser' t} (\p, q => and p q) p q

nelistbox : All (Box (Parser' s) :-> Box (Parser' (NEList s)))
nelistbox {s} p = map {a=Parser' s} nelist p

Should we maybe provide a separate module for these out of the box :), or there's a more elegant solution?

The fact that Induction.Nat.map nelist p doesn't work out of the box looks like
a bug in Idris' elaborator to me: the error mentions an explicit index (j1 : Nat)
but the All-defined types only ever work with implicit indices...

So I figured we could use Idiom brackets instead: the problem is just begging for us
to throw Applicative at it (we already have map and app and Box (Parser ...)
has a pure!).

However I can't seem to declare

Applicative (\ a => Box (Parser' a) n) where

  pure  = box
  (<*>) = Induction.Nat.app

Apparently an implementation cannot mention a universally quantified variable
standing for a value. All the arguments have to be either types or constructors...

So I guess there is no other way than to define a TParsec.Combinators.Box with
definitions with exactly the same name that can be used qualified. It's a shame.

Apparently an implementation cannot mention a universally quantified variable standing for a value. All the arguments have to be either types or constructors...

Yeah, that's a known limitation in current Idris. You can do it for named implementations:

[appbox] Applicative (\ a => Box (Parser' a) n) where
  ...

but that will require wrapping every usage of it in using implementation appbox or passing it via the @{..} syntax, which is not so elegant.

There is, however, a light at the end of the tunnel..

Wait. Does that mean that if we had a BoxedParser : Nat -> Type -> Type we would
be able to define an instance for BoxedParser n?

Ah. No. Nevermind. Plus it wouldn't fit in the interface anyway because
Box's argument is a Nat-indexed Type family, not a mere Type...

I don't think so, BoxedParser would evaluate to a lambda and cause the exact same error :(

I've just discovered though, that you can in fact inline the helper - writing map {a=Parser' _} nelist rec and map2 {a=Parser' _} {b=Parser' _} (\p,q => and p q) inside a larger expression both seem to work fine.

What if you write map {a=Parser _ _} nelist rec instead? Because if that's enough
then we can consider writing a module with specialised versions of map and app!

Yeah, writing map {a=Parser _ _ _ _} nelist rec works! I'm using 4 params here since the project using this is still based on the master branch.

In the #36 PR I have added a bunch of combinators called some variants of lift
that take a function on parsers and make it a function on boxed parsers provided
that the return type is a boxed parser.

Once I had this my life was a lot easier as I could propagate the Box wherever
I wanted without having to deal with the implicits Idris cannot infer when using
map2.