Лучший способ создать тип Maybe в результате частей Maybe

У меня есть Request тип:

data Request =
  Request {
     reqType :: RequestType,
     path    :: String,
     options :: [(String, String)]
  } deriving Show

И я разбираю его (из сырого HTTP запрос), следующим образом:

parseRawRequest :: String -> Request
parseRawRequest rawReq =
    Request {
        reqType = parseRawRequestType rawReq,
        path    = parseRawRequestPath rawReq,
        options = parseRawRequestOps  rawReq
  }

Теперь звонки parseRawRequestType, parseRawRequestPath (и т.д.) может потерпеть неудачу. Чтобы сделать мой код более устойчивым, я изменил сигнатуру их типа с:

parseRawRequestType :: String -> RequestType

в

parseRawRequestType :: String -> Maybe RequestType

Но как лучше повернуть parseRawRequest в Maybe Request? Нужно ли вручную проверять каждый компонент (reqType, path, options) за Nothingили есть другой способ, по которому я скучаю?

Должен быть способ как-то составить объект создания и Nothing-Проверка!

Я написал следующее, но это кажется грязным и не идеальным: (Непроверенный)

parseRawRequest :: String -> Maybe Request
parseRawRequest rawReq
  | Nothing `elem` [reqType, path, options] = Nothing
  | otherwise                               =
    Just Request { reqType=reqType, path=path, options=options }
  where reqType = parseRawRequestType rawReq
        path    = parseRawRequestPath rawReq
        options = parseRawRequestOps  rawReq

Приветствия.

1 ответ

Решение

Это именно тот шаблон, который Аппликативные Функторы (Control.Applicative) представлять. Аппликативы похожи на обычные функторы, но с двумя дополнительными операциями:

pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b

pure позволяет поместить любое значение в аппликатив, и означает, что для любого аппликативного, вы можете написать fmap как fmap f x = pure f <*> x,

Интересный оператор в этом случае <*>, Идея состоит в том, что если у вас есть функция "внутри" функтора, вы можете применить ее к другому значению в функторе. Если вы делаете Request <$> (_ :: Maybe RequestType), вы получите что-то типа Maybe (String -> [(String, String)] -> Request), <*> Оператор тогда позволит вам взять это и применить его к чему-то типа Maybe String получить Maybe [(String, String)] -> Request) и так далее.

Пример:

data RequestType
data Request =
  Request { reqType :: RequestType, path :: String, options :: [(String, String)] }

parseRawRequestType :: String -> Maybe RequestType
parseRawRequestType = undefined
parseRawRequestPath :: String -> Maybe String
parseRawRequestPath = undefined
parseRawRequestOps :: String -> Maybe [(String, String)]
parseRawRequestOps = undefined
parseRawRequest :: String -> Maybe Request
parseRawRequest rawReq = Request <$> parseRawRequestType rawReq
                                 <*> parseRawRequestPath rawReq
                                 <*> parseRawRequestOps rawReq

Однако обратите внимание, что применяемая функция должна иметь тип f (a -> b) вместо a -> m b общего оператора монадического связывания. В эффектном контексте вы можете думать об этом как <*> дает возможность упорядочить эффекты без проверки промежуточных результатов, тогда как >>= дает вам немного больше силы (примечание: существенная разница между силой аппликативного функтора и монады - это функция join :: m (m a) -> m a, Вы можете подумать, как получить >>= с <*> а также join?). Однако Applicative - это более общий интерфейс, что означает, что вы можете использовать их в большем количестве случаев, и иногда они могут иметь хорошие свойства, когда дело доходит до анализа / оптимизации. Похоже, что есть неплохой обзор Аппликативов против Монад и когда вы можете использовать Аппликативы здесь.

Другие вопросы по тегам