I am new to Haskell and testing some concepts with Scotty web library.
However, I can't get a simple hello world page working. I am stuck at converting the parameter as String and apply to another function.
Here is the high-level code that is not working yet.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Web.Scotty
main :: IO ()
main = scotty 3000 $
get "/" $ do
name <- param "name" `rescue` (\_ -> return "haskell")
greeting <- hello name
html $ concat ["<h1>hello ", greeting, "</h1>"]
hello :: String -> String
hello s = "hello " ++ s
app/Main.hs:11:17: error:
• Couldn't match type ‘[]’
with ‘Web.Scotty.Internal.Types.ActionT
Data.Text.Internal.Lazy.Text IO’
Expected type: Web.Scotty.Internal.Types.ActionT
Data.Text.Internal.Lazy.Text IO Char
Actual type: String
<Omitted>
|
11 | greeting <- hello name
| ^^^^^^^^^^
app/Main.hs:12:12: error:
• Couldn't match expected type ‘Data.Text.Internal.Lazy.Text’
with actual type ‘[Char]’
<Omitted>
|
12 | html $ concat ["<h1>hello ", greeting, "</h1>"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/Main.hs:12:34: error:
• Couldn't match expected type ‘[Char]’ with actual type ‘Char’
<Omitted>
|
12 | html $ concat ["<h1>hello ", greeting, "</h1>"]
| ^^^^^^^^
The hello
function is a stub. I would like to proof that the following mechanism work.
extract a parameter as String
apply to a String -> String
function
return the result as response
I have read the Scotty doc and some code examples.
I read that the param
is of type Parsable a => Text -> ActionM a
and ActionM
is of type ActionT Text IO
.
I have tried name :: T.Text <- param "name"
, T.unpack
, liftIO
, etc. but no luck. I think I don't understand the types fully.
What do the types for param
and ActionM
actually mean?
How can I extract the parameter as String to use with another functions?
Thank you.
First, some working code:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Text.Lazy (Text)
import Web.Scotty
main :: IO ()
main = scotty 3000 $
get "/" $ do
name <- param "name" `rescue` (\_ -> return "haskell")
let greeting = hello name
html $ mconcat ["<h1>hello ", greeting, "</h1>"]
hello :: Text -> Text
hello s = "hello " <> s
Since hello
is not in the ActionM
monad, let
binding can be used instead of the <-
syntax.
param
can be used to parse any query parameter that is of the Parseable
type class.
param :: Parsable a => Text -> ActionM a
means that given the text name of a parameter, param
can give back any type you need as long as it is Parseable
. Check the docs for a list of types that are available. Note that String
is not in that list, however Text
is. That is why in the above code I changed the hello
function to work with Text
instead of String
. If you prefer to use String
, you could unpack the parsed param like:
name <- T.unpack <$> param "name" `rescue` (\_ -> return "haskell")
let greeting = hello name -- where `hello :: String -> String`
(But then you will need to repack the result into text before using the html
function)
The other changes that were required were replacing concat
with mconcat
and ++
with <>
. These functions are accomplishing the same thing as concat
and ++
, however are more general and work with all monoids instead of just lists.
Your last question about what the type of ActionM
means.
Under the hood, ActionM
is a specialized form of ActionT
: ActionM = ActionT Text IO
ActionT
represents a computation which takes place in an environment (the http request), can modify an internal state (the http response), and can result in an error. It's made using a stack of monad transformers which looks like this:
newtype ActionT e m a = ActionT
{ runAM :: ExceptT (ActionError e) (ReaderT ActionEnv (StateT ScottyResponse m)) a }