Как действует функция seq force?
Фон
Этот вопрос возникает из-за того, что Брент Йорги поставил на OPLSS: напишите функцию f :: (Int -> Int) -> Bool
, которая отличает f undefined
от f (\x -> undefined)
. Все наши ответы либо использовали seq
, либо что-то вроде шаблонов ударов, которые desugar в seq
. Например:
f :: (Int -> Int) -> Bool
f g = g `seq` True
*Main> f undefined
*** Exception: Prelude.undefined
*Main> f (\x -> undefined)
True
Комментарии GHC на seq
говорят, что
e1 `seq` e2
используется для десузара в
case e1 of { _ -> e2 }
поэтому я попробовал desugaring вручную. Это не сработало:
f' g = case g of { _ -> True }
*Main> f' undefined
True
*Main> f' (\x -> undefined)
True
Вопрос
Это зависит от более сложного seq
, описанного в конце комментария, и если да, то как это работает? Может ли такой f
быть записан без этих примитивов?
x `seq` e2 ==> case seq# x RW of (# x, _ #) -> e2 -- Note shadowing!
e1 `seq` e2 ==> case seq# x RW of (# _, _ #) -> e2
Ответы
Ответ 1
Существует более высокоуровневое описание STG-машины в Как сделать быстрое карри: push/enter vs eval/apply
На рисунке 2 приведено правило CASEANY, которое работает для функций. В этой статье предложение "является значением" означает либо:
- это приложение с насыщенным конструктором
- это функция
- это приложение частичной функции (которое по-прежнему является функцией, семантически)
Необработанные значения, включая литералы, обрабатываются специально, более подробную информацию можно найти в Unboxed values в качестве граждан первого класса
Все это детали реализации и скрыты внутри компилятора (GHC). Выражение case Haskell не заставляет его проверять, только сопоставление образцов и seq делают.
Ответ 2
seq
не может быть реализован в Haskell. Вместо этого, это примитивный "крючок" для оценки в нормальной форме с низким начальником в любой среде выполнения вашего Haskell. Например. на GHC он скомпилирован в case
в ядре GHC, который запускает оценку для внешнего конструктора.
Так как он не может быть реализован в чистом Haskell, он определяется (в GHC) как primop:
pseudoop "seq"
a -> b -> b
{ Evaluates its first argument to head normal form, and then returns its second
argument as the result. }
Так как функции не имеют нормальной формы, seq
останавливает оценку, когда она достигает единицы.
Магически доступно компилятору. То же самое относится к другим примитивам, таким как par
или unsafeCoerce
, токен RealWorld
, forkOn
и т.д. Весь полезный материал.