1
votes

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

Error Messages

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>"]
   |                                  ^^^^^^^^

Goal

The hello function is a stub. I would like to proof that the following mechanism work.

  1. extract a parameter as String

  2. apply to a String -> String function

  3. return the result as response

What have I read and tried

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.

Questions

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.

1
What are your error messages? Focusing on those is a good way to arrive at a solution.Michael Litchard
Also, when you say, "What does the types actually mean?", which types do you want explained? I've identified several.Michael Litchard
@MichaelLitchard Hi Michael, I have updated with the error messages. I mean the types for param and ActionM. Thanks!Gavin

1 Answers

2
votes

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 }