2015-04-19 5 views
10

Я изучал, как использовать государственную монаду, и я заметил некоторое нечетное поведение с точки зрения порядка исполнения. Удаление отвлекающих бит, которые связаны с использованием фактического состояния, скажем, у меня есть следующий код:Порядок выполнения в монадах

import Control.Monad 
import Control.Monad.State 
import Debug.Trace 

mainAction :: State Int() 
mainAction = do 
    traceM "Starting the main action" 
    forM [0..2] (\i -> do 
     traceM $ "i is " ++ show i 
     forM [0..2] (\j -> do 
      traceM $ "j is " ++ show j 
      someSubaction i j 
     ) 
    ) 

Запуск runState mainAction 1 в GHCI производит следующий вывод:

j is 2   
j is 1   
j is 0   
i is 2   
j is 2   
j is 1   
j is 0   
i is 1   
j is 2   
j is 1   
j is 0   
i is 0   
Outside for loop 

, который, кажется, как в обратном порядке исполнения что можно ожидать. Я подумал, что, может быть, это причуда forM и попробовал с sequence который специально заявляет, что он работает его вычисления последовательно слева направо, как так:

mainAction :: State Int() 
mainAction = do 
    traceM "Outside for loop" 
    sequence $ map handleI [0..2] 
    return() 
    where 
    handleI i = do 
     traceM $ "i is " ++ show i 
     sequence $ map (handleJ i) [0..2] 
    handleJ i j = do 
     traceM $ "j is " ++ show j 
     someSubaction i j 

Однако версия sequence производит тот же результат. Какова фактическая логика с точки зрения порядка исполнения, который здесь происходит?

ответ

21

Haskell is ленивый, что означает, что товары не выполняются немедленно. Вещи выполняются всякий раз, когда их результат необходим, но не раньше. Иногда код вообще не выполняется, если его результат не нужен.

Если вы вставляете кучу trace звонков в чистую функцию, вы увидите эту лень. Первое, что необходимо, будет выполнено в первую очередь, так что это первый звонок, который вы видите в первую очередь: trace.

Когда что-то говорит, что «вычисление выполняется слева направо», это означает, что результат будет таким же, как если бы вычисление выполнялось слева направо. То, что на самом деле происходит под капотом, может сильно отличаться.

Это действительно почему это плохая идея делать I/O внутри чистых функций. Как вы обнаружили, вы получаете «странные» результаты, потому что порядок выполнения может быть практически любым, что дает правильный результат.

Почему это хорошая идея? Когда язык не применяет конкретный порядок выполнения (например, традиционный «сверху вниз» порядок, видимый на императивных языках), компилятор может выполнять тонну оптимизаций, например, например, не выполнять какой-либо код вообще, потому что его результат не нужен.

Я бы порекомендовал вас не думаю слишком много о выполнении заказа в Haskell. Не должно быть никаких оснований. Оставьте это до компилятора. Вместо этого подумайте о том, какие значения вы хотите. Соответствует ли функция правильному значению? Затем он работает, независимо от того, в каком порядке он выполняет вещи в

+0

Спасибо за подробное объяснение! Я столкнулся с этой проблемой, пытаясь перевести реализацию Java-приложения Smith-Waterman с очень мутацией. Каким будет лучший способ перевести такой код на такой язык, как Haskell? – Arbus

+2

Вы имеете в виду «оценить» везде, где вы написали «выполнить» (как и OP). –

+1

@Arbus Вы можете использовать 'ST'monad для выполнения мутации – jberryman

7

Я подумал, что, может быть, это причуда forM и попробовал с sequence который специально заявляет, что он работает его вычисления последовательно слева направо, как это.: [...]

Вы должны научиться делать следующее, каверзный различие:

  1. порядок оценки
  2. Порядок Эффекты (a.k.a.«действия»)

Что forM, sequence и аналогичные функции обещание, что эффекты будут упорядочены слева направо. Так, например, следующее гарантируется для печати символов в том же порядке, что они происходят в строке:

putStrLn :: String -> IO() 
putStrLn str = forM_ str putChar >> putChar '\n' 

Но это вовсе не означает, что выражения вычисляются в этом слева-направо. Программа должна оценить достаточное количество выражений, чтобы выяснить, что такое следующее действие, но это часто не требует оценки всего в каждом выражении, участвующем в более ранних действиях.

В вашем примере используется монада State, которая доходит до чистого кода, что подчеркивает проблемы порядка. Единственное, что функции обхода, такие как forM, обещают в этом случае: get s внутри действий, сопоставленных с элементами списка, увидит эффект put s для элементов слева от них.

Смежные вопросы