Haskell do notation vs (>>) operator, happstack routing with 'method'

108 Views Asked by At

I am learning Haskell, and I'm trying to learn the happstack server as well.

I am following the guide at http://happstack.com/docs/crashcourse/index.html#matching-on-request-method-get-post-etc , and their usage of the 'dir' and 'method' functions isn't working in my case.

I am trying to split up the routing sections into helper functions for my sanity, by I am having a hard time understanding how to properly handle the return types in regards to do blocks, the unit ('()') type, and monads.

The repository for my learner project isn't public yet, so I will paste the relevant code in it's entirety here.

EDIT: the below code is using tabs for indentation

main :: IO ()
--main = simpleHTTP nullConf $ ok "Hello, Haskell!!!"
main = simpleHTTP nullConf $ msum
    [
    dir "signup" $ serveFile (asContentType "text/html") "static/index.html",
    dir "static" $ serveDirectory DisableBrowsing [] "static",
    dir "api" api
    --do dir "account" $ ok $ toResponse "account page",
    --do dir "welcome" $ ok $ toResponse "welcome page",
    ----dir "api" $ ok $ toResponse "api endpoint",
    ----do dir "api" $ dir "person" (do method PUT
    ----                ok $ toResponse "api/person"),
    --(do dir "api" $ apiRouting),
    --do dir "static" $ serveDirectory DisableBrowsing [] "static"
    ----seeOther "welcome" ""
    ]

--apiRouting :: (ToMessage a) => ServerPartT IO a
--apiRouting = do
--  dir "person" (do method PUT ok $ toResponse "api/person")

--personHandler :: ServerPartT IO String
--personHandler = return (method PUT (return ok $ toResponse "api/person"))

api :: ServerPartT IO Response
api = msum
    [
        dir "person" person
    ]

person :: ServerPartT IO Response
-- this works
-- person = ok $ toResponse "api/person"
--so does this
--person = msum
--  [
--      ok $ toResponse "api/person"
--  ]
person = msum
    [
        --so, method PUT does nothing besides control which code branch is ran, based on the request
        --that is why the happstack tutorial was using do notation
        --but for some reason, do notation did not work when I tried it
        --if it was because of whitespace or indentation, that's just silly
        --this is the same as the happstack guide, doesn't work because "Couldn't match type ‘()’ with ‘Response -> ServerPartT IO Response’"
        (do method PUT
            ok $ toResponse "api/person put")
        --this works and I think this syntax is nicer, but why does the do block not work?
        --the thing1 >> thing2 operator does thing1, ignores any return value it might have had, and then returns the result of thing2
        --method PUT >> (ok $ toResponse "api/person put")
    ]

for some reason, the do method PUT way does not work, but the '>>' way does.

I was afraid that the do block wasn't returning what I wanted it to, so I tried to use the 'return' statement, but to no avail.

In the Happstack guide, it looks like this:

main :: IO ()
main = simpleHTTP nullConf $ msum
       [ do dir "foo" $ do method GET
                           ok $ "You did a GET request on /foo\n"
       , do method GET
            ok $ "You did a GET request.\n"
       , do method POST
            ok $ "You did a POST request.\n"
       ]

I've tried things like below, but it doesn't work.

person = msum
    [
        --so, method PUT does nothing besides control which code branch is ran, based on the request
        --that is why the happstack tutorial was using do notation
        --but for some reason, do notation did not work when I tried it
        --if it was because of whitespace or indentation, that's just silly
        --this is the same as the happstack guide, doesn't work because "Couldn't match type ‘()’ with ‘Response -> ServerPartT IO Response’"
        do method PUT
            (return (ok $ toResponse "api/person put"))
        --this works and I think this syntax is nicer, but why does the do block not work?
        --the thing1 >> thing2 operator does thing1, ignores any return value it might have had, and then returns the result of thing2
        --method PUT >> (ok $ toResponse "api/person put")
    ]

I thought that do blocks return the result of their last statement, but from the error message it looks like the 'do method PUT' part is just returning '()', which I understand to be similar to the void or unit types in other languages.

I am working my way through Learn You A Haskell, but when I try and do some hands-on learning like this I run into a bunch of gotchas.

EDIT: entire compiler error

web/app/Main.hs:72:20: error:
    • Couldn't match type ‘()’ with ‘ServerPartT IO Response’
      Expected: m0 (m1 Response) -> ServerPartT IO Response
        Actual: m0 (m1 Response) -> ()
    • In the result of a function call
      In a stmt of a 'do' block:
        method PUT (return (ok $ toResponse "api/person put"))
      In the expression:
        do method PUT (return (ok $ toResponse "api/person put"))
   |
72 |                 do method PUT
1

There are 1 best solutions below

0
On
person = msum
    [
        do method PUT
            (return (ok $ toResponse "api/person put"))
    ]

Causes the error:

    • Couldn't match type ‘()’ with ‘ServerPartT IO Response’
      Expected: m0 (m1 Response) -> ServerPartT IO Response
        Actual: m0 (m1 Response) -> ()

because the indentation in the do block is incorrect. The 'return' keyword here is incorrect, as well.

The alignment rules of a 'do' block are such that the first character of the first word after the 'do' block becomes the column value that all subsequent lines of the do block must be aligned with.

Because the 'm' in 'method' did not line up with the second statement in the do block, the do block actually only contained the contents do method PUT. EDIT: The too indented line was actually being interpreted as a continuation of the method PUT expression, becoming do method PUT (return (ok $ toResponse "api/person put")) Thanks, Ben!

The Happstack 'method' function has the return type

ghci> :t method
method
  :: (Happstack.Server.Internal.Monads.ServerMonad m, MonadPlus m,
      Happstack.Server.Routing.MatchMethod method) =>
     method -> m ()

, so the value that the do block "returned" ended up just being m (), causing the error.

If you use tabs instead of spaces, it is necessary to place the first keyword after a statement that creates a block, like 'do', on a new line.

person :: ServerPartT IO Response
person = msum
    [
        do
            method PUT
            ok $ toResponse "api/person put",
        do
            method GET
            ok $ toResponse "api/person get",
        do
            method DELETE
            ok $ toResponse "api/person delete"
    ]

EDIT: read this too

An example from the wikibook is as follows, and an alternative way of solving these white-space related issues would be to use the curly bracket and semicolon syntax:

do { putStr "Hello"
   ; putStr " "
   ; putStr "world!"
   ; putStr "\n" }