Ответьте на запрос с 200 или 404 на основе содержания 'Maybe`, используя Servant - PullRequest
3 голосов
/ 19 января 2020

В настоящее время я пытаюсь реализовать простой веб-сервер с сервантом. На данный момент у меня есть IO (Maybe String), который я хочу показать через конечную точку GET (это может быть поиск в базе данных, который может или не может вернуть результат, следовательно, IO и Maybe). Если Maybe содержит значение, ответ должен содержать это значение со статусом ответа 200 OK . Если Maybe равно Nothing, то должно быть возвращено 404 Not Found .

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

type MaybeAPI = "maybe" :> Get '[ JSON] String

server :: Server MaybeAPI
server = stringHandler

maybeAPI :: Proxy MaybeAPI
maybeAPI = Proxy

app :: Application
app = serve maybeAPI server

stringHandler :: Handler String
stringHandler = liftIO $ fmap (\s -> (fromMaybe (throwError err404) s)) ioMaybeString 

ioMaybeString :: IO (Maybe String)
ioMaybeString = return $ Just "foo"

runServer :: IO ()
runServer = run 8081 app

Я знаю, что это, вероятно, более многословно, чем нужно, но я думаю, что его можно упростить, как только он заработает. Проблема заключается в stringHandler, для которого компиляция завершается неудачно:

Нет экземпляра для (MonadError ServerError []), возникающего из-за использования throwError

Итак, мой вопрос: это способ реализации такой конечной точки в Servant? Если так, как я могу исправить реализацию? Мои Haskell знания довольно ограничены, и я никогда не использовал throwError раньше, поэтому вполне возможно, что я что-то здесь упускаю. Любая помощь приветствуется!

1 Ответ

2 голосов
/ 19 января 2020

Как я уже упоминал в своем комментарии, проблема в том, что в ошибочной строке:

stringHandler :: Handler String
stringHandler = liftIO $ fmap (\s -> (fromMaybe (throwError err404) s)) ioMaybeString 

s - это Maybe String, поэтому использовать его в качестве второго аргумента для fromMaybe, первый аргумент должен быть String - и throwError никогда не будет генерировать строку.

Хотя вы говорили о том, что ваш код, возможно, слишком многословен, и вы бы посмотрели на его упрощение позже, я думаю, что часть проблема здесь в том, что в этом конкретном обработчике вы пытаетесь быть слишком сжатым. Давайте попробуем написать это в более простом c, псевдо-императивном стиле. Поскольку Handler является монадой, мы можем записать это в блок do, который проверяет значение s и выполняет соответствующее действие:

stringHandler :: Handler String
stringHandler = do
    s <- liftIO ioMaybeString
    case s of
        Just str -> return str
        Nothing -> throwError err404

Обратите внимание, что throwError может выдавать значение типа Handler a для любого типа, который в данном случае должен быть String. И что мы должны использовать liftIO на ioMaybeString, чтобы поднять его в Handler монаду, иначе это не будет проверкой типов.

Я могу понять, почему вы могли подумать, что fromMaybe было хорошим подходит здесь, но, по сути, это не так - причина в том, что это «чистая» функция, которая вообще не включает ввод-вывод, тогда как когда вы говорите о выдаче ошибок на сервере, вы совершенно неизбежно делаете ввод-вывод. Эти вещи по сути не могут смешиваться в одной функции. (Что также делает fmap неуместным - это, безусловно, может быть использовано для «поднятия» чистого вычисления для работы над действиями ввода-вывода, но здесь, как я уже сказал, вычисление, которое вам принципиально необходимо, не является чистым.)

И если вы хотите сделать приведенную выше функцию stringHandler более краткой, хотя я не думаю, что это действительно улучшение, вы все равно можете явно использовать >>= вместо блока do, не делая код слишком нечитабельно:

stringHandler = liftIO ioMaybeString >>= f
    where f (Just str) = return str
          f Nothing = throwError err404
...