Лучший способ создать тип 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 - это более общий интерфейс, что означает, что вы можете использовать их в большем количестве случаев, и иногда они могут иметь хорошие свойства, когда дело доходит до анализа / оптимизации. Похоже, что есть неплохой обзор Аппликативов против Монад и когда вы можете использовать Аппликативы здесь.