Как превратить IO Actions в «чистую» функцию - PullRequest
0 голосов
/ 25 сентября 2018

новичок Хаскелла здесь.У меня под рукой функция более высокого порядка myTransform, которая принимает функцию fn :: String -> String и делает некоторые причудливые вещи.Давайте предположим, что реализация

myTransform :: Int -> (String -> String) -> String -> [String]
myTransform n f = take n . iterate f

Теперь я хочу преобразовать внешнюю программу, которая, если я правильно понимаю, является действием ввода-вывода.Предпочтительно, подпись должна быть String -> IO String:

import System.Process
externProg :: String -> IO String
externProg s = readProcess "echo" ["-n", s, "+ 1"] ""

. Вопрос в том, можно ли как-то вписать эту функцию String -> IO String в этот слот аргумента String -> String, не изменяя и даже не зная,как myTransform реализует?

Ответы [ 3 ]

0 голосов
/ 25 сентября 2018

Нет, вы не можете.Вам нужно будет сделать монадическую версию myTransform.Привычно добавлять столичную карту MIe в mapM.Fold Becomse FoldM ... К сожалению, нет итерации M.Поэтому я бы пропустил iterateM и реализовал бы его напрямую.

myTransformM' :: (Monad m) => Int -> (String -> m String) -> String -> m [String]
myTransformM' 0 f str = return [str]
myTransformM' n f str = do
    results <- myTransformM (n-1) f str
    next <- f (head results)
    return (next:results)

myTransformM n f str = do
    results <- myTransformM' n f str
    return $ reverse results

Вы могли бы заметить, что результаты первой функции упорядочены наоборот.Это сделано для того, чтобы избежать квадратичной функции.

Вы можете попробовать сами, что произойдет, если вы реализуете iterateM.Это будет просто петля вечно.Это связано с тем, что Haskell никогда не узнает, вернетесь ли вы обратно в список или произойдет ли IOError где-нибудь в будущем.Точно так же, если вы возьмете монаду «Может быть», Хаскелл никогда не узнает, вернули ли вы на самом деле Just list или если где-то внизу есть Nothing.

iterateM :: (Monad m) => (a -> m a) -> a -> m [a]
iterateM f a = do
    result <- f a
    results <- iterateM f result
    return (result:results)
0 голосов
/ 25 сентября 2018

Ответ Йокто очень хорошо объясняет проблему.Я только добавлю еще один пример.

Рассмотрим эту функцию:

f1 :: (Int -> Int) -> Int
f1 g = g 10 + g 20

f2 :: (Int -> Int) -> Int
f2 g = g 20 + g 10

Эти функции имеют точно такую ​​же семантику.Реализация на Haskell может переписать первое во второе, если оно того пожелает, без ущерба для результата.

Теперь рассмотрим

myG :: Int -> IO Int
myG x = print x >> return x

main :: IO ()
main = do
   x <- f1 myG  -- assume this could be made to work, somehow
   print x

Что следует печатать?Интуитивно он печатает либо

10
20
30

, либо

20
10
30

в зависимости от порядка оценки, используемого в f1.Это плохо, поскольку f2 также можно было бы использовать, и это должно привести к тому же результату, но, скорее всего, приведет к другому.Хуже того, компилятор может оптимизировать один в другой, поэтому любой конкретный вывод не гарантирован: компилятор может изменить его по прихоти.

Это большая проблема, которую Haskell должен был избежать.Порядок IO-эффектов должен быть полностью указан в программе.Для этого программист должен быть лишен возможности превращать вещи ввода-вывода (например, Int -> IO Int) в вещи не-ввода-вывода (например, Int -> Int).

Если мы использовали монадический тип для f, как в

f3 :: (Int -> IO Int) -> IO Int
f3 g = ...

тогда Haskell заставит нас указать порядок между g 10 и g 20.

0 голосов
/ 25 сентября 2018

Это обычный дуп, но у меня есть момент, чтобы ...

Нет, вам нужно запустить действие ввода-вывода и, таким образом, получить значение String, которое передается вашему myTransform.

Например:

main :: IO ()
main =
  do stdout <- externProg "myProg"  -- "execute" the IO action and obtain "stdout :: String"
     let res = myTransform stdout  -- res :: String
     putStrLn res

Или когда вы освоитесь с языком и если у вас все в порядке со стилем:

main :: IO ()
main = putStrLn . myTransform =<< externProg "myProg"
...