Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use System.cmd in Scotty action

I am trying to learn how do statements in haskell work. I a trying to make a very simple program where you can call a REST endpoint and execute a system command (something very simple like "ls") The problem comes with combining different actions types in a single do statement.


import Web.Scotty
import System.Cmd

main = do
  putStrLn "Starting Server"
  scotty 3000 $ do
    get "/url" $ do                         
      system "ls"
      text "Success"

But I get the next compiler error:

Main.hs:12:7:
    Couldn't match expected type ‘Web.Scotty.Internal.Types.ActionT
                                    Data.Text.Internal.Lazy.Text IO a0’
                with actual type ‘IO GHC.IO.Exception.ExitCode’
    In a stmt of a 'do' block: system "ls"
    In the second argument of ‘($)’, namely
      ‘do { system "ls";
            text "Success" }’

I am having a hard time trying to learn Haskell!

like image 749
Rafael Páez Bastida Avatar asked Mar 03 '23 05:03

Rafael Páez Bastida


1 Answers

In Haskell, do-notation is used to chain statement-like things. A statement is some type constructor like IO applied to a particular result type. For example, the statement system "ls" has type IO ExitCode.

Other type constructors other than IO can work as statements. All that do-notation requires is that the type constructor implements the Monad interface which explains how to chain statements sensibly.

However, within a single do-block, only one type of statement is allowed! They must be all IO statements, or all ActionT Text IO statements. In your example you are mixing the two, which causes the error. Scotty's get function expects an ActionT Text IO statement:

get :: RoutePattern -> ActionM () -> ScottyM ()
-- ActionM is actually a synonym for ActionT Text IO

The good news is that there's a way to convert (the usual Haskell term is "lift") IO statements into ActionT Text IO statements. The latter are actually a kind of "decorator" (the usual Haskell term is "monad transformer") over IO actions, which enable extra functionality related to Scotty. You can "lift" IO actions into the decorator using the liftIO function, like this:

get "/url" $ do                         
     liftIO (system "ls")
     text "Success"

In general, when can we use liftIO to lift a plain IO statement into a "decorated" statement? The "decorator" type constructor must have a MonadIO instance besides the usual Monad instance. MonadIO is what provides the liftIO function.

In our case, looking at the available instances for ActionT:

(MonadIO m, ScottyError e) => MonadIO (ActionT e m)

Which means something like "if m is has a MonadIO instance—like IO trivially does—and the error type e has a ScottyError instance—like Text does—then we can lift IO statements to ActionT e m statements".

And the specialized type for liftIO is:

liftIO :: IO a -> ActionT Text IO a
like image 110
danidiaz Avatar answered Mar 11 '23 03:03

danidiaz