@hackage arrowp-qq0.3.0

A preprocessor and quasiquoter for translating arrow notation

Hackage Stackage Nightly Travis Build Status

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:

  1. It replaces the haskell-src based parser with one based on haskell-src-exts, which handles most of GHC 8.0.2 Haskell syntax.
  2. It provides not only a preprocessor but also a quasiquoter, which is a better option in certain cases.
  3. 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