Сокращение избыточности в учебном коде happstack - PullRequest
3 голосов
/ 23 августа 2011

В учебнике happstack представлен следующий пример:

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

Кажется, что ok $ здесь избыточен - есть ли способ извлечь это из msum, чтобы у вас не былонаписать ok $ три раза?Я попробовал следующее, но он даже не компилируется:

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

Есть ли правильный способ сделать это (или, что еще лучше, вытащить все ok $ "You did a " и ".\n"), илиэто просто невозможно?

Я все еще в курсе того, как работают монады в Haskell, но если вышеупомянутое невозможно, то вы можете с высокого уровня объяснить, почему нет разумного способа заставить это работать, или чтонужно изменить, чтобы это стало возможным?Я просто пытаюсь обернуть голову вокруг того, что можно и нельзя сделать здесь.

Ответы [ 2 ]

5 голосов
/ 23 августа 2011

ok на самом деле не является избыточным.

Давайте посмотрим на один из блоков до. Мы разделим первый do-блок на отдельную функцию с именем getPart.

getPart :: ServerPart String
getPart = do methodM GET
             ok $ "You did a GET request.\n"

Итак, мы ясно видим, что работаем с монадой ServerPart. Поэтому каждая строка в блоке do должна иметь тип, подобный ServerPart a.

Запись чего-то подобного не будет работать :

getPart :: ServerPart String
getPart = do methodM GET
             "You did a GET request.\n"

потому что последняя строка в этом блоке do имеет тип String, а не require ServerPart String. Типичный способ конвертировать String в ServerPart String - использовать return:

getPart :: ServerPart String
getPart = do methodM GET
             return "You did a GET request.\n"

Помните, что return имеет тип:

return :: (Monad m) => a -> m a

Но, конечно, это не лучше, чем у нас было раньше. Вместо ok у нас есть return. На самом деле нет никакого способа избежать этого «эталона». Вам нужен ServerPart String, а не String, а это означает применение функции, подобной return или ok, для подъема.

Как вы заметили, часть сообщения "You did a " является избыточной. Есть несколько способов справиться с этим. У нас могут быть обработчики, которые просто возвращают ту часть сообщения, которая отличается от этой:

handlers :: ServerPart String
handlers = 
       [ do methodM GET
            ok $ "GET request"
       , do methodM POST
            ok $ "POST request"
       , dir "foo" $ do methodM GET
                        ok $ "GET request on /foo"
       ]

И тогда мы можем получить это String и добавить остальную часть сообщения:

main :: IO ()
main = simpleHTTP nullConf $ do msg <- handlers
                                return ("You did a " ++ msg ++ ".\n")

(это можно выразить более компактно, но я стремлюсь к удобочитаемости здесь).

Одна из проблем этого решения заключается в том, что оно заставляет все эти обработчики соответствовать одной и той же форме. Если бы мы хотели добавить обработчик, который возвращал бы сообщение, которое не соответствовало этому шаблону, у нас были бы проблемы. Другой вариант - создать простую вспомогательную функцию, которая инкапсулирует этот шаблон:

methodMsg :: Method -> String -> ServerPart String
methodMsg mthd msg = do methodM mthd
                        ok $ "You did a " ++ msg ++ ".\n"

main :: IO ()
main = simpleHTTP nullConf $ msum 
       [ methodMsg GET  "GET request"
       , methodMsg POST "POST request"
       , dir "foo" $ methodMsg GET "GET request on /foo"
       -- the bar handler does not follow the pattern
       , dir "bar" $ ok $ "let's go to the bar!"
       ]

Надеюсь, это поможет!

4 голосов
/ 23 августа 2011

Не уверен насчет типа dir, но что-то вроде этого должно работать:

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

С такими короткими блоками у меня возникнет соблазн отменить их:

main :: IO ()
main = simpleHTTP nullConf $ msum 
       [ methodM GET  >> return "GET request"
       , methodM POST >> return "POST request"
       , dir "foo" $ methodM GET >> return "GET request on /foo"
       ] >>= ok . (\s -> "You did a " ++ s ++ ".\n")
...