2010-12-16 2 views
4

Я нашел некоторые примеры кода, и изменил его немногоПутаница 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 каждый раз, когда я запустить его.

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

ответ

10

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

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

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

+0

Почему не обрабатывается нонарная функция? – Squidly

+6

@MrBones: Потому что такое поведение почти никогда не будет тем, чего хотят люди. Если я напишу 'x = veryExpensiveFunction foobar', а затем' y = x * x + x', я хочу, чтобы «veryExpensiveFunction foobar» оценивался один раз, а не трижды. И это именно то, что делает Haskell (если тип 'x' не является полиморфным). Единственная причина, по которой вы хотите, чтобы он вел себя как нулевая функция в этом случае, заключается в том, что оценка выражения имеет побочные эффекты, что не может произойти даже без небезопасных операций. – sepp2k

+1

В haskell нет такой функции, как нонарная функция. Все функции принимают одно значение (которое может быть кортежем) и возвращают одно значение (которое может быть другой функцией). Функции не «вызывают» - они применяются к значениям, чтобы давать значения. Вы серьезно хотите, чтобы 'foo = 1 + 2' была функцией, которая говорит процессору добавлять 1 и 2 каждый раз, когда вы используете слово foo !? – sclv

3

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

+8

Чтобы подчеркнуть ответы здесь - лучший способ исправить это - никогда не использовать когда-либо * ** когда-либо ** использовать 'unsafePerformIO', если вы действительно * действительно * действительно * ** действительно ** rrreeeaaallly знаете, что вы делаете и зачем вам это нужно. И даже тогда, вероятно, не использовать его. – sclv

4

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

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

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

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

+1

Просто из любопытства, с какими опасностями вы говорите, используя 'Num a => IORef a'? –

+0

Потому что система типов завинчивается вверх. Я где-то читал об этом, но больше не могу найти ресурс. Возможно, в исходном коде для 'unsafePerformIO' – fuz

+1

@Grazer: он может нарушать безопасность типа; см. [документация для 'unsafePerformIO'] (http://hackage.haskell.org/packages/archive/base/latest/doc/html/System-IO-Unsafe.html#v:unsafePerformIO). (Хотя их пример теперь печатает «*» «для меня, а не для сброса ядра, это все равно не должно быть возможным». Вы также можете создать значение, которое изменяется во второй раз, когда вы его оцениваете. –

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