Ввод-вывод и, возможно, взаимодействие с монадой

#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