-
Simple regression with Yesod and R
2016-09-04
SourceAs announced in a previous article, I did an app with
opencputhat 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
opencpuanymore. 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.jssimplereg.hsis the main Haskell code;FileToBase64is an auxiliary module called bysimplereg.hs;index.hamletis the html code in hamlet format;the
staticfolder contains alljsandcssfiles;the
Rfolder contains anRscript and auxiliary files.
The hamlet file, the main
css, and the mainjsTake the file index.html.
Remove all the
css, put it in the maincssfilestatic/css/regression.css(or anothercssfile).Remove all the remote
<script>and<link>tags (they will be included bysimplereg.hs).Remove the main
jsscript, and put its contents in the filestatic/js/main.js.Once everything above is done, convert
index.htmltoindex.hamletwith html2hamlet.
The main
jsscript is modified at one place only: the place where it uses theopencpu.jslibrary, 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 currentDirectoryYou 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
RfunctionknitRegressionin 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
hrefattribute of a<a>tag. It is sent to the client as the result of the Ajax PUT request.
