Что означает Нормальная Форма Слабой Головы (WHNF)? Что означает нормальная форма головы (HNF) и нормальная форма (NF)?
Реальный мир Haskell утверждает:
Привычная функция seq вычисляет выражение к тому, что мы называем нормальной головой (сокращенно HNF). Он останавливается, как только достигает самого внешнего конструктора («головы»). Это отличается от нормальной формы (NF), в которой выражение полностью оценивается.
Вы также услышите, что программисты на Haskell ссылаются на нормальную форму слабой головы (WHNF). Для нормальных данных слабая нормальная форма головы такая же, как нормальная форма головы. Разница возникает только для функций, и она слишком сложна, чтобы беспокоить нас здесь.
Я прочитал несколько ресурсов и определений ( Haskell Wiki и Список рассылки Haskell и Бесплатный словарь ), но я не понимаю. Может ли кто-нибудь привести пример или дать определение непрофессионала?
Я предполагаю, что это будет похоже на:
WHNF = thunk : thunk
HNF = 0 : thunk
NF = 0 : 1 : 2 : 3 : []
Как seq
и ($!)
связаны с WHNF и HNF?
Обновить
Я все еще в замешательстве. Я знаю, что некоторые ответы говорят, чтобы игнорировать HNF. Из прочтения различных определений кажется, что нет никакой разницы между обычными данными в WHNF и HNF. Тем не менее, кажется, что есть разница, когда дело доходит до функции. Если не было разницы, зачем это seq
нужно foldl'
?
Другая путаница seq
связана с тем, что Haskell Wiki утверждает, что сводится к WHNF, и ничего не будет делать в следующем примере. Тогда они говорят, что они должны использовать, seq
чтобы форсировать оценку. Разве это не принуждает его к HNF?
Общий код переполнения стека новичков:
myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)
Люди, которые понимают seq и нормальную форму слабой головы (whnf), могут сразу понять, что здесь не так. (acc + x, len + 1) уже находится в whnf, поэтому seq, который уменьшает значение до whnf, ничего не делает для этого. Этот код будет создавать thunks точно так же, как и в оригинальном примере foldl, они просто будут внутри кортежа. Решение состоит в том, чтобы просто заставить компоненты кортежа, например,
myAverage = uncurry (/) . foldl' (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)
источник
Ответы:
Я постараюсь дать объяснение простыми словами. Как уже отмечали другие, нормальная форма головы не относится к Хаскеллу, поэтому я не буду ее здесь рассматривать.
Нормальная форма
Выражение в нормальной форме полностью вычисляется, и никакое подвыражение не может быть оценено далее (то есть оно не содержит необработанных блоков).
Все эти выражения в нормальной форме:
Эти выражения не в нормальной форме:
Слабая голова нормальной формы
Выражение в нормальной форме со слабой головой было оценено как самый внешний конструктор данных или лямбда-абстракция ( голова ). Подвыражения могут или не могут быть оценены . Следовательно, каждое выражение нормальной формы также находится в нормальной форме со слабой головой, хотя в общем случае обратное не имеет места.
Чтобы определить, находится ли выражение в нормальной форме со слабой головой, нам нужно только взглянуть на крайнюю часть выражения. Если это конструктор данных или лямбда, то у него слабая нормальная форма. Если это функциональное приложение, это не так.
Эти выражения в слабой голове в нормальной форме:
Как уже упоминалось, все перечисленные выше выражения в нормальной форме также находятся в нормальной форме со слабой головой.
Эти выражения не в слабой голове нормальной форме:
Переполнение стека
При оценке выражения в нормальной форме со слабой головой может потребоваться, чтобы другие выражения сначала оценивались в WHNF. Например, чтобы оценить
1 + (2 + 3)
WHNF, мы сначала должны оценить2 + 3
. Если оценка одного выражения приводит к слишком многим из этих вложенных вычислений, результатом является переполнение стека.Это происходит, когда вы создаете большое выражение, которое не создает конструкторов данных или лямбда-выражений, пока большая его часть не будет оценена. Это часто вызвано этим типом использования
foldl
:Обратите внимание, как оно должно пройти достаточно глубоко, прежде чем оно сможет привести выражение в нормальную форму слабой головы.
Вы можете удивиться, почему Haskell не сокращает внутренние выражения раньше времени? Это из-за лени Хаскелла. Поскольку в общем случае нельзя предполагать, что каждое подвыражение будет необходимо, выражения оцениваются извне в.
(GHC имеет анализатор строгости, который обнаруживает некоторые ситуации, когда подвыражение всегда необходимо, и он может оценить его заранее. Однако это всего лишь оптимизация, и вы не должны полагаться на него, чтобы спасти вас от переполнений).
Такое выражение, с другой стороны, совершенно безопасно:
Чтобы избежать построения этих больших выражений, когда мы знаем, что все подвыражения должны быть оценены, мы хотим, чтобы внутренние части оценивались заранее.
seq
seq
это специальная функция, которая используется для принудительного вычисления выражений. Его семантикаseq x y
означает, что всякий раз, когдаy
оценивается слабая нормальная форма головы,x
также оценивается слабая нормальная форма головы.Это среди других мест, используемых в определении
foldl'
, строгий вариантfoldl
.Каждая итерация
foldl'
заставляет аккумулятор WHNF. Поэтому он избегает создания большого выражения и поэтому избегает переполнения стека.Но, как упоминается в примере на HaskellWiki, это не спасает вас во всех случаях, поскольку аккумулятор оценивается только в WHNF. В этом примере аккумулятор является кортежем, поэтому он будет принудительно вычислять конструктор кортежа, а не
acc
илиlen
.Чтобы избежать этого, мы должны сделать так, чтобы оценка конструктора кортежей вынуждала вычислять
acc
иlen
. Мы делаем это с помощьюseq
.источник
\x -> 1 + 1
что WHNF, но не HNF.seq
свои аргументы?:set +s
. Затем вы можете увидеть, что вfoldl' f
итоге выделяется больше громкоговорителей, чемfoldl' f'
.В разделе « Нормальная форма Thunks и Weak Head» в описании лени на Haskell Wikibooks приводится очень хорошее описание WHNF вместе с этим полезным описанием:
источник
Программы на Haskell являются выражениями и выполняются путем выполнения оценки .
Чтобы оценить выражение, замените все приложения функций их определениями. Порядок, в котором вы делаете это, не имеет большого значения, но он по-прежнему важен: начните с самого внешнего приложения и продолжайте слева направо; это называется ленивой оценкой .
Пример:
Оценка останавливается, когда больше не осталось функциональных приложений для замены. Результат в нормальной форме (или уменьшенной нормальной форме , RNF). Независимо от того, в каком порядке вы оцениваете выражение, вы всегда будете иметь одну и ту же нормальную форму (но только если оценка завершится).
Есть немного другое описание для ленивой оценки. А именно, это говорит о том, что вы должны оценивать все, чтобы слабая голова только в нормальной форме . Есть только три случая, когда выражение должно быть в WHNF:
constructor expression_1 expression_2 ...
(+) 2
илиsqrt
\x -> expression
Другими словами, заголовок выражения (т. Е. Самое внешнее приложение функции) не может быть вычислен дальше, но аргумент функции может содержать неоцененные выражения.
Примеры WHNF:
Ноты
источник
seq
вfoldl'
силе оценки от WHNF до HNF?seq expr1 expr2
оценивает первое выражениеexpr1
в WHNF перед оценкой второго выраженияexpr2
.Хорошее объяснение с примерами дано на http://foldoc.org/Weak+Head+Normal+Form. Нормальная форма головы упрощает даже биты выражения внутри абстракции функции, в то время как нормальная форма «слабой» головы останавливается на абстракциях функции. ,
Из источника, если у вас есть:
это в нормальной форме со слабой головой, но не в нормальной форме головы ... потому что возможное приложение застряло внутри функции, которая еще не может быть оценена.
Реальную голову нормальную форму будет сложно реализовать эффективно. Это потребует возни внутри функций. Таким образом, преимущество слабой нормальной формы в том, что вы все еще можете реализовывать функции как непрозрачный тип, и, следовательно, он более совместим с компилируемыми языками и оптимизацией.
источник
WHNF не хочет, чтобы тело лямбда оценивалось, поэтому
seq
хочет, чтобы его первый аргумент был в WHNF, поэтомуоценивает
вместо того, что бы использовать HNF
источник
В принципе, у вас есть какое - то стук,
t
.Теперь, если мы хотим оценить
t
WHNF или NHF, которые являются одинаковыми за исключением функций, мы обнаружили бы, что мы получаем что-то вродеt1 : t2
гдеt1
иt2
есть гром. В этом случае,t1
будет ваш0
(или, скорее, спасибо, что0
без дополнительной распаковки)seq
и$!
оценить WHNF. Обратите внимание, чтоисточник