Rlang-QQ-0.3.0.0: quasiquoter for inline-R code

Safe HaskellNone
LanguageHaskell2010

RlangQQ

Contents

Description

A quasiquoter to help with calling R from ghc.

Synopsis

the quasiquoter

r :: QuasiQuoter Source

Calls R with the supplied string. Variables in R prefixed hs_ cause the corresponding (un-prefixed) variable to be converted. The variable(s) must be in at least one class FromRDS or ToRDS. Currently the relation between where variables are used and assigned to (using <-) determines the Intent.

Expressions are also supported. These must be text between $( ), just like template haskell. One condition is that the contents between the parentheses must be parseable by haskell-src-meta/haskell-src-exts. So if you find the hs_ notation unpleasant you can still interpolate using $(x).

An example of both styles is

import RlangQQ

x = [0 .. 10  :: Double]

main = do
  [r|
    library(ggplot2)
    png(file='test.png')
    plot(qplot( hs_x, $(map (sin . (*pi) . (/10)) x) ))
    dev.off()
    |]

You get a plot:

While it is only somewhat usable, you can have Rnw/Rmd documents (knitr) that include haskell code. One example is given here. A second option is to use IHaskell, for which there are two example notebooks in the same http://code.haskell.org/~aavogt/Rlang-QQ/examples

rChan :: QuasiQuoter Source

[rChan| |] does the same as [r| |], except the return value will be a Chan (Record a).

conversion of values

class ToRDS a Source

same as Binary but should be compatible with R's saveRDS binary mode, which is for single objects

Minimal complete definition

toRDS

Instances

ToRDS Double 
ToRDS Int 
ToRDS Int32 
ToRDS Integer 
ToRDS String

"abc" => c('ab')

ToRDS Text

T.pack "abc" => c('ab')

ToRDS [Double] 
ToRDS [Int]

converts to an Int32, which is bad on 64 bit systems where maxBound :: Int is a bigger number than maxBound :: Int32

ToRDS [Int32] 
ToRDS [Integer]

converts to an Int32 first

ToRDS [String]

["abc","def"] => c('abc','def')

ToRDSRecord k __ ___ xs => ToRDS (Record xs)

lab .=. val .*. emptyRecord => list(lab= (toRDS val) ).

The type variables with underscores should be hidden

ToRDS (Vector Double)

becomes a numeric vector

ToRDS (Vector Int32)

integer vector

ToRDS (Vector Text)

character vector c('ab','cd')

ToRDS (Vector Double)

becomes a numeric vector

ToRDS (Vector Int32) 
(ToRDS t, ToRDS (RDA rs), ShowLabel k l2) => ToRDS (RDA ((:) * (Tagged k l2 t) rs))

internal

ToRDS (RDA ([] *))

internal

IxSize i => ToRDS (Array i Int32) 
IxSize i => ToRDS (Array i Double)

Data.Array.Array become arrays in R

(ToRDS t, ShowLabel k l) => ToRDS (Tagged k l t)

probably internal

(Source r Int32, Shape sh) => ToRDS (Array r sh Int32)

repa

(Source r Double, Shape sh) => ToRDS (Array r sh Double)

repa

class FromRDS a Source

Minimal complete definition

fromRDS

Instances

FromRDS Double 
FromRDS Int 
FromRDS Int32 
FromRDS Integer 
FromRDS String 
FromRDS Text 
FromRDS [Double] 
FromRDS [Int] 
FromRDS [Int32] 
FromRDS [Integer] 
FromRDS [String] 
FromRDSRec b d => FromRDS (Record d)

R lists become HList records. list(x=1,y=b) parses to something like

let x = Label :: Label "x"
    y = Label :: Label "y"
in    x .=. 1
  .*. y .=. "b"
  .*. emptyRecord

You have to get the result type right (ie. Record [Tagged "x" Double, Tagged "y" String]) for things to parse

FromRDS (Vector Double) 
FromRDS (Vector Int32) 
FromRDS (Vector Text) 
FromRDS (Vector Double) 
FromRDS (Vector Int32) 
(FromRDS t, FromRDS (RDA rs), ShowLabel k l2) => FromRDS (RDA ((:) * (Tagged k l2 t) rs))

internal

FromRDS (RDA ([] *))

internal

IxSize i => FromRDS (Array i Int32)

note indices become 0-based (see IxSize)

IxSize i => FromRDS (Array i Double)

note indices become 0-based (see IxSize)

(FromRDS t, ShowLabel k l) => FromRDS (Tagged k l t)

probably internal

Shape sh => FromRDS (Array U sh Int32)

repa

Shape sh => FromRDS (Array U sh Double)

repa

records

If the quasiquote assigns to variables hs_x and hs_y, the result type will be IO (Record '[LVPair "x" x, LVPair "y" y]). The types x and y have to be determined on the haskell side. Here is a complete example:

>>> :set -XQuasiQuotes -XDataKinds -XNoMonomorphismRestriction
>>> let x = [2 :: Double]
>>> let q = [r| hs_y <- 1 + hs_x; hs_z <- 2 |]

These labels could be generated by template haskell with $(makeLabels6 (words "y z"))

>>> let y = Label :: Label "y"
>>> let z = Label :: Label "z"
>>> do o <- q; print (o .!. y ++ o .!. z :: [Double])
[3.0,2.0]

listToRecN :: (HAllTaggedLV (HReplicateR * n (Tagged Symbol "" x)), SameLabels [*] [*] (HReplicateR * n (Tagged Symbol "" x)) (HReplicateR * n (Tagged Symbol "" x)), SameLength' * * (RecordValuesR (HReplicateR * n (Tagged Symbol "" x))) (RecordValuesR (HReplicateR * n (Tagged Symbol "" x))), SameLength' * * (HReplicateR * n (Tagged Symbol "" x)) (HReplicateR * n (Tagged Symbol "" x)), HList2List (RecordValuesR (HReplicateR * n (Tagged Symbol "" x))) x, HMapAux HList TaggedFn (RecordValuesR (HReplicateR * n (Tagged Symbol "" x))) (HReplicateR * n (Tagged Symbol "" x)), RecordValues (HReplicateR * n (Tagged Symbol "" x))) => Proxy HNat n -> [x] -> Record (HReplicateR * n (Tagged Symbol "" x)) Source

convert a haskell list into a record with labels all of type "". The length of the list is decided by the (type of the) first argument which is a HNat

listToRecN :: _ => Proxy (n :: HNat) -> [x] -> Record (HReplicateR n (Tagged "" x))

n :: QuasiQuoter Source

HList uses it's own nat, distinct from Nat, for various reasons.

Specifying those HNat can be done like:

[n| 5 |]

which is shorter than the equivalent

hSucc $ hSucc $ hSucc $ hSucc $ hSucc hZero

connecting to a single R session

Variables like ch_x ch_longVariableName inside the quasiquote generate references to x and longVariableName. These variables should have type Chan (a, b -> IO '()'). newChan can produce values of that type, but some versions with restricted types are provided:

do
 x <- newRChan
 longVariableName <- newRChan' (undefined :: Double)

The whole input to R is re-sent each time whenever a whole set of ch_ variables is available. examples/test4.hs has an a working example shows that keeping the same R-session open is much faster, but that results may be confusing since nothing explicitly says (besides this documentation here) that the same code is re-sent.

newRChan :: IO (Chan (a, b -> IO ())) Source

newChan with a more restricted type

newRChan' :: a -> IO (Chan (a, b -> IO ())) Source

newRChan (undefined :: Double) produces an even more restricted type than newRChan', which can help make type errors more sensible and/or avoid ambiguous type variable

sendRcv :: Chan (t, b -> IO ()) -> t -> IO b Source

y <- sendRcv c x sends the value x using the chan c. Provided that an [r| |] quasiquote above refers to a ch_c, the call to sendRcv will eventually produce a Record y which provides a reference to all the output variables that R calculated with the given x.

TODO

debugging
Write file that can be run to loading a quote into R interpreter (ie the same thing as readProcess R "--no-save" ...). For now it's pretty simple to just cd Rtmp and load/source things. also, return R's stdout stderr exitcode in the HList. This won't be practical for the Chan option since the stdout is getting consumed?
antiquote
doesn't do escapes, so a string '$( blah )' might end badly
more examples
conversion both ways etc.
read NULL as Maybe?
more datatypes
support things like ??, ...
call R functions as if they were defined in haskell
This can be achieved already by doing something like
x <- newRChan
[r| hs_f <- ch_x + 1 |]
let f :: Double -> IO Double
    f xVal = (.!. (Label :: Label "f")) `fmap` sendRcv x xVal

But perhaps something can be done to generate the above code from something much shorter like:

[r| hs_f <- function(x) x + 1 |]

Can this be made to work without looking at whether there is a function() after the <-?

call hs functions as if they were defined in R
Likewise, we might like values like f x = x + 1 to be callable from R (for example by defining a foreign export ccall f :: Int32 -> Int32), and dyn.load(something); .C(f, x=1L). Alternatively the R interpreter might have a way to communicate some other way.
use libR.so
there is a hR package which might allow usage similar to hslua (ie. calling individual R functions)

one drawback is that it uses lists for vectors...

static analysis
(optionally?) call something like codetools on the generated R code to infer result/argument types. Or perhaps translate R code into some constraints:
class RApp (x :: [*]) r
instance (UpcastNumR a b ~ r, UpcastNumR b a ~ r) => RApp [Proxy "+", a, b] r
type family UpcastNumR a b
type instance UpcastNumR Double Int = Double
type instance UpcastNumR Int Int = Int

the benefit here is that users could add their own RApp instances. On the other hand, perhaps using a separate constraint solver will be less confusing in terms of type errors (ie. failure to infer a type from R which will happen (features like do.call) should not complicate the types seen on the haskell side).

or run the code first with some dummy inputs (say vectors of length 10 or so), and assume those types will continue to be the same.

http://cran.r-project.org/web/packages/lambda.r/index.html