Ответ Йокто очень хорошо объясняет проблему.Я только добавлю еще один пример.
Рассмотрим эту функцию:
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
.