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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With