fixIO

Apr 06, 2009 19:13

Предыстория: http://thesz.livejournal.com/950788.html?thread=7471620#t7471620

Т.е. предполагалось, что любой mdo можно развернуть в letrec (т.е. do без mfix).

Однако, простой пример

mdo
tid1 <- forkOS (print tid2)
tid2 <- forkOS (print tid1)
return ()

будет в зависимости от настроения то выводить ThreadId, то показывать <>. Всё потому, что для хранения переменной используется IORef. Можно переписать mfix для IO, используя MVar:

newtype MyIO a = MyIO { runMyIO :: IO a }
deriving (Functor, Monad, MonadIO)

instance MonadFix MyIO where
mfix f = MyIO $ do
ref <- newEmptyMVar
vars <- unsafeInterleaveIO $ takeMVar ref
realVars <- runMyIO $ f vars
putMVar ref realVars
return realVars

Тогда вместо <> просто висим в ожидании MVar. Зато наш пример начинает работать по человечески всегда:

myMain :: MyIO ()
myMain = mdo
tid1 <- liftIO $ forkOS (print tid2)
tid2 <- liftIO $ forkOS (print tid1)
return ()

Какие могут быть подводные камни у такой реализации?

Ну, и самое главное. Я наконец понял, что перевести mdo в letrec не всегда возможно. Например, здесь. Если мы просто завернём все tid в монаду, то получим дли-и-и-инную цепочку thunk-ов, которую не сможем развернуть - stack overflow, однако! Если мы сделаем IO ленивым (тот же unsafeInterleaveIO), то получим многократное выполнение. Т.е. нам придётся вернуться к реализации ленивого IO с сохранением результата через какую нибудь изменяемую переменную. Ленивый - чтобы не зациклилось, сохранять результат - чтобы не перезапускалось.

fixpoint, монады, haskell, рекурсия, fp

Previous post Next post
Up