#haskell
#хаскелл
Вопрос:
У меня есть следующий код, но я чувствую, что он слишком уродливый и императивный. Кто-нибудь перефразировал бы это, чтобы быть более функциональным? (Я перепутал с MaybeT, но не смог заставить его работать) Также приветствуются прикладные ответы.
getString :: IO String
pred :: String -> Bool
f :: String -> String
result :: IO (Maybe String)
result = do
s <- getString
if pred s
then return $ Just $ f s
else return Nothing
РЕДАКТИРОВАТЬ: последующий вопрос: что, если оба pred и f также возвращают результаты в IO (должен ли я разделить это на отдельный вопрос?)
getString :: IO String
pred :: String -> IO Bool
f :: String -> IO String
result :: IO (Maybe String)
result = do
s <- getString
b <- pred s
if b
then Just <$> f s
else return Nothing
Ответ №1:
Я бы начал с того, что вывел логику из IO
монады. Затем ваша функция может быть записана как
result :: IO (Maybe String)
result = foo <$> getString
foo :: String -> Maybe String
foo s | pred s = Just (f s)
| otherwise = Nothing
Вероятно, вы могли бы писать foo
по-разному, используя некоторые причудливые комбинаторы, но я не думаю, что здесь это необходимо. Самое главное — вывести вашу логику из IO
, чтобы ее было легче тестировать.
Комментарии:
1. 1 Нельзя отрицать, что извлечение логики из
IO
важнее, чем наличие модного однострочного решения.
Ответ №2:
Вот хороший маленький комбинатор:
ensure :: MonadPlus m => (a -> Bool) -> (a -> m a)
ensure p x = guard (p x) >> return x
Теперь мы можем написать чистую функцию, которая проверяет ваш предикат и применяется f
при необходимости:
process :: String -> Maybe String
process = fmap f . ensure pred
Перенос этого в IO
действие — это просто другой fmap
:
result = fmap process getString
Лично я бы, вероятно, встроил process
и написал это таким образом:
result = fmap (fmap f . ensure pred) getString
… это относительно чистое описание того, что происходит.
Ответ №3:
Очевидное преобразование вашего кода заключается в том, чтобы учитывать return
операции:
result = do
s <- getString
return $ if pred s
then Just (f s)
else Nothing
Это делает шаблон более очевидным:
result = liftM g getString
g s | pred s = Just (f s)
| otherwise = Nothing
Применяя f
извне, мы можем сделать следующий шаблон очевидным:
g s = liftM f $ if pred s then Just s else Nothing
Что позволяет нам переназначить if
блок:
g = liftM f . mfilter pred . return
Подводя итог:
result = liftM (liftM f . mfilter pred . return) getString
Ответ №4:
import Control.Monad
result = getString >>= (return . fmap f . (mfilter pred . Just) )
Комментарии:
1. Обратите внимание, что это
mfilter
заменяет ваш if-then-else.2.
result = (f <$> mfilter pred . Just) <$> getString
3. @FUZxxl, который не вводит проверку.
mfilter pred . Just :: a -> Maybe a
, ноf <$> x
ожидаетx :: Maybe a
. Вам нужен какой-то прикладной оператор композицииf <.> g = x -> f <$> g x
, который, если ему присвоен более низкий приоритет, чем.
позволяетresult = (f <.> mfilter pred . Just) <$> getString
. Кроме того, заменаJust
наreturn
позволяет использовать любойMonad
.4. Как насчет
result = (fmap f . mfilter pred . return) <$> getString
?
Ответ №5:
Вы не можете легко отделаться от неуклюжести if-then-else
, но вы можете избавиться от избыточности returns
:
import Control.Monad
result :: IO (Maybe String)
result = go <$> getString where
go s | pred s = Just $ f s
| otherwise = Nothing