Путаница по поводу IORefs, чтобы сделать счетчик

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

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 ответа

Решение

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

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

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

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

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

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

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

Вам не следует использовать такие конструкции в обычном программировании, так как компилятор может применять различные оптимизации, которые убивают желаемый эффект или делают его непредсказуемым. Если возможно, используйте что-то вроде 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 за ответ на ваш вопрос. То, что вы объясняете, особенно полезно, если у вас есть что-то глобальное (например, конфиги), которое вряд ли изменится, что должно быть доступно практически везде. Используйте это с умом, так как это противоречит основному принципу чистоты. Если это возможно, используйте монады вместо этого (как я объяснил).

Другие вопросы по тегам