Отказ от использования case-выражений для последовательного поиска в Snap

Я играюсь с фреймворком Snap и часто сталкиваюсь со случаем, когда я выполняю поиск в базе данных на основе параметра, который я получаю из поля формы.

Рассмотрим, например. следующие две функции

getParam :: (MonadSnap m) => ByteString -> m (Maybe ByteString)
doLookup :: (MonadIO (m b v), MonadSnaplet m, MonadState s (m b b), HasAcid s UrlDB) => ByteString -> m b v (EventResult QueryByURL)

где UrlDB — это сопоставление между целыми числами и URL-адресами. Сложная сигнатура типа второй функции связана с использованием кислотного состояния и в конечном итоге приводит к Maybe Integer.

queryByURL :: Text -> Query UrlDB (Maybe Integer)

Пока что мой обработчик выглядит так

indexHandler :: Handler MyApp MyApp ()
indexHandler = do
    mUrl <- getParam "url"
    case mUrl of
        Nothing  -> render "index"
        Just url -> do
            mId <- doLookup $ url
            case mId of
                Nothing -> render "index"
                Just i  -> do
                               fancyStuffWith i
                               render "index"

Итак, первое, что меня раздражает, — это ступенчатость падежных выражений. Во-вторых, это тройственное явление render "index". По сути, всякий раз, когда одно из двух значений Maybe равно Nothing, я хочу вернуть представление по умолчанию.

Каким будет самый чистый способ сделать это?


person Aton    schedule 23.04.2014    source источник


Ответы (2)


Именно для этого предназначен преобразователь монады MaybeT. Ваш код может быть написан так.

indexHandler :: Handler MyApp MyApp ()
indexHandler = do
    runMaybeT $ do
        url <- MaybeT $ getParam "url"
        i <- MaybeT $ doLookup url
        fancyStuffWith i
    render "index"

пакет ошибок объединяет эти вещи и добавляет множество удобных функций для работы с ними. В дополнение к MaybeT, преобразователь монады DoesT делает что-то подобное, но отслеживает сообщения об ошибках, так что вы можете отследить, когда ваши вычисления завершились неудачно.

person mightybyte    schedule 23.04.2014

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

indexHandler :: Handler MyApp MyApp ()
indexHandler = do
    withJust $ getParam "url" $ \url ->
        withJust $ doLookup url $ fancyStuffWith
    render "index"
  where
    withJust :: (IO (Maybe a)) -> (a -> IO()) -> IO ()
    withJust iomval cmd = do
        mval <- iomval
        case mval of
            Nothing -> return ()
            Just val -> cmd val

функция withJust выполняет действие ввода-вывода, которое может не принести значение. В случае успеха значение передается другой команде.

person Simon Bergot    schedule 23.04.2014