arrowp-qq
A preprocessor (aka syntax-desugarer) for arrow notation
based on the original arrowp
developed by Ross Paterson ross@soi.city.ac.uk.
arrowp-qq
extends the original arrowp
in three dimensions:
- It replaces the
haskell-src
based parser with one based on haskell-src-exts
, which handles most of GHC 8.0.2 Haskell syntax.
- It provides not only a preprocessor but also a quasiquoter, which is a better option in certain cases.
- It extends the desugaring to handle static conditional expressions. See the semantics section below for more details.
Note: arrowp-qq
provides an enhanced superset of the original arrowp
's functionality. One should have no reason to install both. Considering arrowp
no longer builds under modern versions of GHC and Cabal/Stack, arrowp-qq
should clearly be the more optimum package to install.
Both arrowp
and arrowp-qq
were originally developed during the days when GHC's Arrows
extension was not fully mature--a time when proc
syntax was not the norm. Of course, recent versions of GHC now support this notation directly, and give better error messages to boot. Unfortunately, GHC's proc
notation desugarer is in some cases not as good as it could be. As such, arrowp-qq
's quasi-quoter can still be useful in producing slightly more optimized desugaring for performance-critical, arrowized applications.
The modern use cases of arrowp-qq
are as follows:
- Viewing how your
proc
blocks (roughly) look like after desugaring for debugging/profiling purposes.
- Via the
arrowp
executable.
- NOTE:
arrowp-qq
is NOT guaranteed to produce the same desugaring as GHC (see limitations & semantics below).
- Optimizing the performance of your
proc
blocks via smarter desugaring.
- Either through the
arrowp
executable OR (preferably) the provided quasiquoter.
Limitations
The parser cannot handle banana brackets for
control operators in arrow notation (the proc keyword in the original paper),
due to a limitation
in haskell-src-exts
. In order to use banana brackets, the recommendation
is to fall back to the GHC Arrows parser.
Support for GHC Haskell notation inside arrow blocks is not complete, e.g.
multi-way-if and lambda case are unlikely to work as expected. If you run into
one of these, please open an issue or vote for an existing one, as I plan to extend
the support on demand.
Installation
arrowp-qq
is now compatible with Cabal v3's Nix-style local builds & installs:
cabal install arrowp-qq
Usage
Viewing desugared proc
syntax
arrowp myfile.hs | less
Optimization
Via the proc
quasiquoter
addA :: Arrow a => a b Int -> a b Int -> a b Int
addA f g = [proc| x -> do
y <- f -< x
z <- g -< x
returnA -< y + z |]
Via the arrowp preprocessor
Add the following GHC pragma to the top of the source file:
{-### OPTIONS -F -pgmF arrowp ### -}
This can be useful for preserving compatibility with vanilla proc
notation, at the cost of flexibility; that is to say, all proc
blocks within the source file will be desugared via arrowp-qq
.
Desugaring Semantics
Static conditional expression optimization
As mentioned previously, arrowp-qq
extends the original arrowp
's desugaring to handle static conditional expressions. Given:
proc inputs -> do
results <- processor -< inputs
if outputResultsArg
then outputSink -< results
else returnA -< ()
returnA -< results
The standard arrowp
(and GHC) desugaring for this code is:
= ((processor >>> arr (\ results -> (results, results))) >>>
(first
(arr
(\ results -> if outputResultsArg then Left results else Right ())
>>> (outputSink ||| returnA))
>>> arr (\ (_, results) -> results)))
This requires an ArrowChoice
, but there is a more efficient desugaring which
performs the choice at compile time and thus an Arrow
suffices:
((processor >>> arr (\ results -> (results, results))) >>>
(first
(if outputResultsArg then outputSink else arr (\ results -> ()))
>>> arr (\ (_, results) -> results)))
first
call optimization
The GHC desugarer does not do a very good job of minimizing the number of
first
calls inserted. In certain Arrow
instances, this can have a material effect
on performance. Example:
trivial = proc inputs -> do
chunked <- chunk -< inputs
results <- process -< chunked
returnA -< results
This code ought to desugar to a chain of arrows, and indeed, both arrowp and
arrowp-qq desugar this to:
trivial = chunk >>> process
However GHC will produce (approximately) the following code:
arr(\inputs -> (inputs,inputs)) >>> first chunk >>> first process >>> arr fst