-
Running R in a Yesod application
2016-09-01
SourceBasic demo
This small app takes two numbers and returns their sum after performing the addition in R.
This is an Haskell app created with Yesod, and R is called from Haskell with the help of the inline-R package. You may take a look at my inline-R demo and at my article Hello Yesod+Ajax before.
- {-# LANGUAGE DataKinds #-}
- {-# LANGUAGE GADTs #-}
- {-# LANGUAGE PartialTypeSignatures #-}
- {-# LANGUAGE OverloadedLists #-}
- {-# LANGUAGE ScopedTypeVariables #-}
- {-# LANGUAGE QuasiQuotes #-}
- {-# LANGUAGE TemplateHaskell #-}
- {-# LANGUAGE ViewPatterns #-}
- {-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
- {-# LANGUAGE TypeFamilies #-}
- import Yesod
- import GHC.Generics
- import qualified Language.R.Instance as R
- import H.Prelude.Interactive
- data HelloInlineR = HelloInlineR
- mkYesod "HelloInlineR" [parseRoutes|
- / HomeR GET
- /data DataR PUT
- |]
- instance Yesod HelloInlineR
- data Args = Args {
- _x :: Double,
- _y :: Double
- } deriving (Show,Generic)
- instance FromJSON Args
- getHomeR :: Handler ()
- getHomeR = sendFile typeHtml "static/raddition.html"
- runR :: Double -> Double -> IO(Double)
- runR x y =
- do
- r <- [r|x_hs + y_hs|]
- return $ (fromSomeSEXP r :: Double)
- putDataR :: Handler String
- putDataR = do
- arguments <- requireJsonBody :: Handler Args
- r <- liftIO $ runR (_x arguments) (_y arguments)
- return $ show r
- main :: IO ()
- main = do
- R.initialize R.defaultConfig
- warp 3000 HelloInlineR
File raddition.html:
- <html>
- <head>
- <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
- <script type="text/javascript">
- function save() {
- $.ajax({
- url: "/data",
- type: "PUT",
- processData: false,
- data: JSON.stringify({
- _x: Number($("#x").val()),
- _y: Number($("#y").val())
- }),
- success: function (result) {
- alert (result);
- },
- error: function(xhr, status, error) {
- alert("Error: " + xhr.responseText);
- },
- dataType: "text",
- contentType: "application/json"
- });
- }
- </script>
- </head>
- <body>
- <h3>Enter two numbers:</h3>
- <input id="x" type="number" />
- <input id="y" type="number" />
- <br><br>
- <input id="submit" type="button" value="Addition" onclick="save()">
- </body>
- </html>
Instead of requesting a html file, we can create its contents in the Haskell code:
- ...
- import Yesod.Form.Jquery (YesodJquery (urlJqueryJs))
- ...
- instance Yesod HelloInlineR
- instance YesodJquery HelloInlineR
- ...
- getHomeR :: Handler Html
- getHomeR = defaultLayout $ do
- setTitle "Yesod + Ajax"
- getYesod >>= addScriptEither . urlJqueryJs
- [whamlet|
- <input #x type=number>
- <input #y type=number>
- <button #add>Addition
- |]
- toWidget script
- script = [julius|
- $(function(){
- $("#add").click(function(){
- $.ajax({
- contentType: "application/json",
- processData: false,
- url: "@{DataR}",
- type: "PUT",
- data: JSON.stringify({
- _x: Number($("#x").val()),
- _y: Number($("#y").val())
- }),
- success: function(result) {
- alert(result);
- },
- dataType: "text"
- });
- });
- });
- |]
- ...
Passing a dataframe
Get the dataframe as a stringified JSON object, and use the
jsonlite
package.- getHomeR :: Handler Html
- getHomeR = defaultLayout $ do
- setTitle "Yesod + Ajax"
- getYesod >>= addScriptEither . urlJqueryJs
- [whamlet|
- <button #submit>Submit
- |]
- toWidget script
- script = [julius|
- $(function(){
- $("#submit").click(function(){
- $.ajax({
- contentType: "text",
- processData: false,
- url: "@{DataR}",
- type: "PUT",
- data: JSON.stringify(JSON.stringify([{name: "Alice", age: 25}, {name: "Bob", age: 30}])),
- success: function(result) {
- alert(result);
- },
- dataType: "text"
- });
- });
- });
- |]
- runR :: String -> IO(Int32) -- requires Data.Int
- runR dat = do
- r <- [r|ncol(jsonlite::fromJSON(dat_hs))|]
- return $ (fromSomeSEXP r :: Int32)
- putDataR :: Handler String
- putDataR = do
- dat <- requireJsonBody :: Handler String
- r <- liftIO $ runR dat
- return $ show r