Путаница из-за IORefs, чтобы сделать счетчик - PullRequest
4 голосов
/ 16 декабря 2010

Я нашел пример кода и немного его изменил

counter = unsafePerform $ newIORef 0

newNode _ = unsafePerformIO $
              do
                i <- readIORef counter
                writeIORef counter (i+1)
                return i

При каждом запуске возвращается 1, затем 2, 3, 3 и т. Д.

Но когда я изменяю его на

newNode = unsafePerformIO $
              do
                i <- readIORef counter
                writeIORef counter (i+1)
                return i

тогда я получаю 0 каждый раз, когда запускаю его.

Почему это происходит, и что я могу сделать, чтобы это исправить?

Ответы [ 3 ]

10 голосов
/ 16 декабря 2010

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

Слово предупреждения: использование unsafePerformIO для чего-либо, кроме действия ввода-вывода, которое, как вы знаете, является референциальнымпрозрачный опасен.Он может плохо взаимодействовать с некоторыми оптимизациями и вообще не вести себя так, как вы ожидаете.Есть причина, по которой в его названии есть слово «небезопасно».

Как способ поиграться с unsafePerformIO ваш код в порядке, но если вы когда-нибудь захотите использовать что-то подобное в реальном коде, яНастоятельно призываю вас пересмотреть.

4 голосов
/ 17 декабря 2010

Так же, как пояснение: для приложения, подобного тому, которое вы, похоже, создаете, создание IORef с использованием unsafePerformIO вполне нормально и допускает наиболее близкое приближение Haskell к глобальным переменным в процедурных языках.То, что, вероятно, не разумно, использует это в своих действиях.Например, newNode, вероятно, лучше всего записать как:

newNode = do i <- readIORef counter
             writeIORef counter (i+1)
             return i

И тогда любое место, которое вы намереваетесь вызвать newNode, должно быть само действием ввода-вывода.

Еще одна маленькая деталь: counter будет иметь тип IORef Integer по умолчанию, если вы не измените его с помощью default.Не пытайтесь дать ему общий тип, такой как Num a => IORef a: в этом заключается опасность.

3 голосов
/ 16 декабря 2010

Yous не должен использовать такие конструкции в обычном программировании, так как компилятор может применять различные оптимизации, которые убивают желаемый эффект или делают его непредсказуемым.Если возможно, используйте что-то вроде монады State.Он чище и всегда будет вести себя так, как вы хотите.Вот и вы:

-- This function lets you escape from the monad, it wraps the computation into a monad to make it, well, countable
counter :: (State Int x) -> x
counter = flip evalState 0

-- Increments the counter and returns the new counter
newNode :: State Int Int
newNode = modify (+1) >>= get

Пожалуйста, посмотрите, что печально за sepp2k за ответ на ваш вопрос.То, что вы объясняете, особенно полезно, если у вас есть что-то глобальное (например, конфиги), которое вряд ли изменится, что должно быть доступно практически везде.Используйте это с умом, так как это противоречит основному принципу чистоты.Если это возможно, используйте вместо этого монады (как я объяснил).

...