Здесь происходит две проблемы.
Причина, по которой первая попытка (с использованием явного rnf
) не работает, заключается в том, что, используя return
, вы создали thunk, которыйполностью оценивает себя, когда оценивается, но сам thunk не оценивается.Обратите внимание, что тип оценки - a -> IO a
: тот факт, что он возвращает значение в IO
, означает, что evaluate
может наложить порядок:
return (error "foo") >> return 1 == return 1
evaluate (error "foo") >> return 1 == error "foo"
В результате этот код:
force s = evaluate $ s `using` rdeepseq
будет работать (например, будет иметь то же поведение, что и mapM_ evaluate s
).
Случай использования строгих каналов немного сложнее, но я полагаю, что это связано сошибка в строгом параллелизме.Дорогие вычисления на самом деле выполняются в рабочих потоках, но они не приносят вам большой пользы (вы можете проверить это явно, скрыв некоторые асинхронные исключения в ваших строках и увидев, в каком потоке поверхности исключений).
В чем ошибка?Давайте посмотрим на код строгого writeChan
:
writeChan :: NFData a => Chan a -> a -> IO ()
writeChan (Chan _read write) val = do
new_hole <- newEmptyMVar
modifyMVar_ write $ \old_hole -> do
putMVar old_hole $! ChItem val new_hole
return new_hole
. Мы видим, что modifyMVar_
вызывается на write
, прежде чем мы оценим thunk.Последовательность операций тогда:
writeChan
введено - Мы
takeMVar write
(блокирование любого другого, кто хочет записать на канал) - Мыоцените дорогой thunk
- Мы поместили дорогой thunk на канал
- We
putMVar write
, разблокируя все остальные темы
Вы не видите этогоповедение с evaluate
вариантами, потому что они выполняют оценку до того, как блокировка получена.
Я отправлю Дону сообщение об этом и посмотрю, согласен ли он, что такое поведение является неоптимальным.
Дон соглашается, что это поведение неоптимально.Мы работаем над патчем.