Хаскелл: конкатенацию двух строк ИО


Сегодня я попытался соединить две строки ввода-вывода и не смог заставить их работать.

Итак, проблема в следующем. Предположим, что у нас есть s1 :: IO String и s1 :: IO String. Как реализовать функцию (+++) :: IO String -> IO String -> IO String, которая работает точно так же, как (++) :: [a] -> [a] -> [a], но для строки ввода-вывода?

И более общий вопрос заключается в том, как реализовать более общую функцию (+++) :: IO a -> IO a -> IO a? Или, может быть, даже более общее? Я не очень хорошо разбираюсь в функторах и монадах.

Я буду признателен за любую помощь.

4 2

4 ответа:

Вы можете использовать liftM2 из Control.Monad:

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c


> :t liftM2 (++)
liftM2 (++) :: Monad m => m [a] -> m [a] -> m [a]

В качестве альтернативы можно использовать нотацию do:

(+++) :: Monad m => m [a] -> m [a] -> m [a]
ms1 +++ ms2 = do
    s1 <- ms1
    s2 <- ms2
    return $ s1 ++ s2

Оба они эквивалентны. Фактически, определение для liftM2 реализуется как

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 f m1 m2 = do
    val1 <- m1
    val2 <- m2
    return $ f val1 val2

Очень просто! Все, что он делает, - это извлекает значения из двух монадических действий и применяет к ним функцию из 2 аргументов. Это относится к функции liftM, которая выполняет эту операцию для функции только с одним аргументом. В качестве альтернативы, как указывали другие, вы можно использовать экземпляр IO S Applicative в Control.Applicative и использовать аналогичную функцию liftA2.

Вы можете заметить, что универсальные Applicative s имеют сходное поведение с универсальными Monad s в определенных контекстах, и причина этого заключается в том, что они математически очень похожи. На самом деле, для каждого Monad, Вы можете сделать Applicative из него. Следовательно, вы также можете сделать Functor из каждого Applicative. Есть много людей, взволнованных предложением функтор-Аппликатив-Монада , которое было вокруг на некоторое время, и, наконец, будет реализован в предстоящей версии GHC. Они составляют очень естественную иерархию Functor > Applicative > Monad.
import Control.Applicative (liftA2)

(+++) :: Applicative f => f [a] -> f [a] -> f [a]
(+++) = liftA2 (++)

Теперь в GHCI

>> getLine +++ getLine
Hello <ENTER>
World!<ENTER>
Hello World!
(++) <$> pure "stringOne" <*> pure "stringTwo" 

Реализовать функцию (+++)... который работает точно так же, как (++) :: [a] -> [a] -> [a], но для IO String?

Не делай этого, это плохая идея. Конкатенация строк-это чисто функциональная операция, нет никаких оснований для ее использования в монаде IO. За исключением того места, где вам нужен результат – который будет где-то в середине какой-то другой ИО, я полагаю. Ну, тогда просто используйте do-нотацию, чтобы привязать строки чтения к именам переменных, и используйте обычные (++) на них!
do
  print "Now start obtaining strings..."
  somePreliminaryActions
  someMoreIOStuff
  s1 <- getS1
  s2 <- getS2
  yetMoreIO
  useConcat'dStrings (s1 ++ s2)
  print "Done."

Это хорошо, чтобы сделать это более компактным, написав s12 <- liftA2 (++) getS1 getS2. Но я бы сделал это прямо на месте, а не определял его отдельно.

Для более длительных операций вы, конечно, можете определить отдельное именованное действие, но оно должно быть каким-то образом значимым.

Вы не должны думать об объектах IO String как о "IO-строках". Они не являются таковыми, как [Int] не являются "целыми числами списка". Объект типа IO String - это действие, которое, будучи произведено, может предоставить объект String в монаде IO. Это не струна сам.