1

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
Mattマット
  • 81
  • 1
  • 4
  • 5
    Triple check your indentation. Avoid tab characters in the source code. Every line in a `do` block must start exactly on the same column. `a >> b` is the same as `do a` and `b` on the next line, provided `a` and `b` have exactly the same indentation. – chi Jun 01 '22 at 12:51
  • 4
    ...OTOH, `do {a; return b}` is very different from `a >> b` / `do {a; b}`. For a law-abiding monad, it is equivalent to `fmap (const b) a`, which doesn't even use any monad operations! – leftaroundabout Jun 01 '22 at 13:24
  • 1
    What is the compile error you get from the first code sample (it would help if you pasted it in its entirety)? You definitely have an indentation error in the last block (the second statement in the do block starts with the `(` in `(return`, but that lines up with the `e` in `method` above rather than with the `m`), but I don't see one in the first example. Blindly adding the `return` is unlikely to have addressed any problem in your first sample, anyway. – Ben Jun 02 '22 at 01:45
  • @Ben I should have posted the entire error, I'm sorry. The issue was the column alignment. – Mattマット Jun 02 '22 at 13:45
  • @chi @Ben @leftaroundabout you all were correct, how should we create this answer? having the 'do' be on it's own line, and then ensuring that the subsequent lines 'method PUT' and 'ok $ toResponse "api/person put"` were vertically aligned solved the issue. As Ben stated, blindly adding a return wasn't doing me or anyone else any good. – Mattマット Jun 02 '22 at 13:57
  • @Mattマット Do you use tabs for indentation by any chance? Haskell's layout is based on *alignment* rather than *indentation*; correctly aligning code that is indented with tabs requires a horrible mix of tabs and spaces that is a nightmare to get right (for humans and for editors), and getting it wrong often results in invisible errors. If you want to use tabs you pretty much need to **always** line break after a keyword introducing a layout block (do, let, where, etc), because then the alignment position will always be some number of tabs and zero spaces. Or just don't use tabs in Haskell. – Ben Jun 02 '22 at 22:40
  • @Mattマット If it was just an alignment error, then I'm not fussed how you want to handle the error. Feel free to self-answer and accept, if you would like. The specifics of this situation are unlikely to be exactly the same for any future user, so I don't think a detailed write-up will have much future value (unless you would like a more complete answer about alignment and tabs?). There are plenty of other questions and answers about Haskell alignment for Google to find. – Ben Jun 02 '22 at 22:44
  • 1
    @Ben I convinced myself that it wasn't an alignment error by trying out the curly-bracket block syntax, and I also tried fully parenthesizing the code. I will make an answer describing what worked for my use case. I'm not familiar with how the rep system works, so I was simply wondering if there was a way to ensure that the kind people that helped me got their rep. – Mattマット Jun 03 '22 at 23:29

1 Answers1

1
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" }

Mattマット
  • 81
  • 1
  • 4