-
Simple regression with Yesod and R
2016-09-04
SourceAs announced in a previous article, I did an app with
opencpu
that performs a simple linear regression in R and returns a report.Now I have converted this app to a Yesod+R app. Thus it does not use
opencpu
anymore. Only Haskell and standard JavaScript libraries.The contents of this article are my notes about the way I achieved this goal.
Structure of the folder
This folder is available in a Github repo.
. ├── .cabal-sandbox -> /home/stla/.cabal-sandbox/ ├── cabal.sandbox.config -> /home/stla/cabal.sandbox.config ├── FileToBase64.hs ├── index.hamlet ├── R │ ├── child_regression.Rmd │ ├── knitRegression.R │ └── regression.Rmd ├── simplereg.hs └── static ├── bootstrap │ ├── bootstrap-4.0.0.min.css │ ├── bootstrap-4.0.0.min.js │ └── bootstrap.file-input.js ├── css │ └── regression.css ├── jqplot-1.0.9 │ ├── jquery.jqplot.min.css │ ├── jquery.jqplot.min.js │ └── plugins │ ├── jqplot.canvasAxisLabelRenderer.js │ ├── jqplot.canvasTextRenderer.js │ ├── jqplot.cursor.js │ ├── jqplot.highlighter.js │ └── jqplot.trendline.js ├── jquery │ └── jquery-1.10.2.min.js ├── js │ ├── jsontotable.js │ └── main.js ├── jsonTable │ └── jsonTable.js └── PapaParse └── papaparse-4.1.2.min.js
simplereg.hs
is the main Haskell code;FileToBase64
is an auxiliary module called bysimplereg.hs
;index.hamlet
is the html code in hamlet format;the
static
folder contains alljs
andcss
files;the
R
folder contains anR
script and auxiliary files.
The hamlet file, the main
css
, and the mainjs
Take the file index.html.
Remove all the
css
, put it in the maincss
filestatic/css/regression.css
(or anothercss
file).Remove all the remote
<script>
and<link>
tags (they will be included bysimplereg.hs
).Remove the main
js
script, and put its contents in the filestatic/js/main.js
.Once everything above is done, convert
index.html
toindex.hamlet
with html2hamlet.
The main
js
script is modified at one place only: the place where it uses theopencpu.js
library, obviously. Instead, an Ajax PUT request is used.Include the remote scripts and links in the main Haskell file
The remote scripts and links are included as follows (see here):
addScript $ StaticR jquery_jquery_1_10_2_min_js addScript $ StaticR _PapaParse_papaparse_4_1_2_min_js addStylesheet $ StaticR bootstrap_bootstrap_4_0_0_min_css ...
Instead of writing this code by hand, load the following module in GHCi:
import System.FilePath.Find (find, always, extension, (==?), (||?)) import System.Directory (setCurrentDirectory, getCurrentDirectory) import System.FilePath (takeExtension) import Data.String.Utils (replace) import Data.Char (isUpper) getFiles :: FilePath -> IO [FilePath] getFiles directory = do filePaths <- find always (extension ==? ".js" ||? extension ==? ".css") "./" return $ map (drop 2) filePaths linecode :: FilePath -> String linecode file | extension == ".js" = " addScript $ StaticR " ++ typeSafeURL | extension == ".css" = " addStylesheet $ StaticR " ++ typeSafeURL where extension = takeExtension file typeSafeURL = underscore ++ (replace "/" "_" $ replace "." "_" $ replace "-" "_" file) underscore = if isUpper (file !! 0) then "_" else "" code :: FilePath -> IO() code directory = do currentDirectory <- getCurrentDirectory setCurrentDirectory directory files <- getFiles directory mapM_ putStrLn $ map linecode files setCurrentDirectory currentDirectory
You get the desired code by running
code "path/to/static"
in GHCi.Calling R
The method, using an Ajax request, is demonstrated on a simple example in Running R in a Yesod application.
Here we put the main
R
functionknitRegression
in the R folder and we source it in Haskell (here):runR :: Args -> IO FilePath runR (Args dat conflevel filetype) = do tmp <- getTemporaryDirectory r <- [r|source("R/knitRegression.R") knitRegression(jsonlite::fromJSON(dat_hs), conflevel_hs, filetype_hs, tmp_hs)|] return $ (fromSomeSEXP r :: FilePath)
It returns the absolute path of the output file, saved in a temporary folder.
Base64 encoding
I have not found a sure way to make the output file available to the client. The solution I adopted consists in encoding the file in base64 with the module I wrote, FileToBase64. Thus the file is encoded to a string which can be used in the
href
attribute of a<a>
tag. It is sent to the client as the result of the Ajax PUT request.