Zayıf Kafa Normal Formu nedir?


290

Ne yapar Zayıf Baş Normal Formu (WHNF) ortalama? Ne yapar Baş Normal formu (HNF) ve Normal Formu (NF) ortalama?

Gerçek Dünya Haskell şöyle diyor:

Bilinen seq işlevi, kafa normal formu (kısaltılmış HNF) dediğimiz ifadeyi değerlendirir. En dıştaki kurucuya (“kafa”) ulaştığında durur. Bu, bir ifadenin tamamen değerlendirildiği normal formdan (NF) farklıdır.

Haskell programcılarının zayıf kafa normal formuna (WHNF) başvurduklarını da duyacaksınız. Normal veriler için zayıf kafa normal formu kafa normal formuyla aynıdır. Fark sadece işlevler için ortaya çıkar ve burada bizi ilgilendirmek için çok abartılıdır.

Birkaç kaynak ve tanım okudum ( Haskell Wiki ve Haskell Mail List ve Free Dictionary ) ama anlamıyorum. Birisi belki bir örnek verebilir veya meslekten olmayan bir tanım sağlayabilir mi?

Ben buna benzer olacağını tahmin ediyorum:

WHNF = thunk : thunk

HNF = 0 : thunk 

NF = 0 : 1 : 2 : 3 : []

Nasıl seqve ($!)WHNF ve HNF ilgilidir?

Güncelleme

Hala kafam karıştı. Bazı cevapların HNF'yi görmezden geldiğini biliyorum. Çeşitli tanımların okunmasından sonra, WHNF ve HNF'deki normal veriler arasında hiçbir fark olmadığı görülmektedir. Bununla birlikte, bir işlev söz konusu olduğunda bir fark var gibi görünüyor. Fark yoksa, neden seqgereklidir foldl'?

Başka bir karışıklık noktası, seqWHNF'ye indirgenen ve aşağıdaki örneğe hiçbir şey yapmayacak olan Haskell Wiki'den. Sonra seqdeğerlendirmeyi zorlamak için kullanmaları gerektiğini söylüyorlar . Bu onu HNF'ye zorlamıyor mu?

Ortak acemi yığın taşan kodu:

myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)

Seq ve zayıf kafa normal formunu (whnf) anlayan insanlar, burada neyin yanlış gittiğini hemen anlayabilirler. (acc + x, len + 1) zaten whnf içindedir, yani bir değeri whnf'ye düşüren seq, bunun için hiçbir şey yapmaz. Bu kod, tıpkı orijinal foldl örneği gibi gövdeleri oluşturacak, sadece bir demet içinde olacaklar. Çözüm, sadece demetin bileşenlerini zorlamaktır, örn.

myAverage = uncurry (/) . foldl' 
          (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)

- Stackoverflow'da Haskell Wiki


1
Genellikle WHNF ve RNF'den bahsediyoruz. (RNF NF dediğiniz şeydir)
alternatif

5
@monadic RNF'deki R ne anlama geliyor?
dave4420

7
@ dave4420: İndirimli
marc

Yanıtlar:


399

Basit bir anlatımla açıklama yapmaya çalışacağım. Diğerlerinin işaret ettiği gibi, normal kafa şekli Haskell için geçerli değildir, bu yüzden burada düşünmeyeceğim.

Normal form

Normal formdaki bir ifade tamamen değerlendirilir ve başka hiçbir alt ifade değerlendirilemez (başka bir deyişle değerlendirilmemiş thunks içermez).

Bu ifadelerin tümü normal formdadır:

42
(2, "hello")
\x -> (x + 1)

Bu ifadeler normal formda değildir:

1 + 2                 -- we could evaluate this to 3
(\x -> x + 1) 2       -- we could apply the function
"he" ++ "llo"         -- we could apply the (++)
(1 + 1, 2 + 2)        -- we could evaluate 1 + 1 and 2 + 2

Zayıf kafa normal formu

Zayıf kafa normal formundaki bir ifade, en dıştaki veri yapıcısına veya lambda soyutlamasına ( kafa ) göre değerlendirilmiştir. Alt ifadeler değerlendirilmiş olabilir veya olmayabilir . Bu nedenle, her normal form ifadesi de zayıf kafa normal formundadır, ancak tam tersi genel olarak geçerli değildir.

Bir ifadenin zayıf kafa normal formunda olup olmadığını belirlemek için, sadece ifadenin en dış kısmına bakmamız gerekir. Bir veri oluşturucu veya lambda ise, zayıf kafa normal formundadır. Bu bir işlev uygulamasıysa, değildir.

Bu ifadeler zayıf kafa normal formundadır:

(1 + 1, 2 + 2)       -- the outermost part is the data constructor (,)
\x -> 2 + 2          -- the outermost part is a lambda abstraction
'h' : ("e" ++ "llo") -- the outermost part is the data constructor (:)

Belirtildiği gibi, yukarıda listelenen tüm normal form ifadeleri de zayıf kafa normal formundadır.

Bu ifadeler zayıf kafa normal formunda değildir:

1 + 2                -- the outermost part here is an application of (+)
(\x -> x + 1) 2      -- the outermost part is an application of (\x -> x + 1)
"he" ++ "llo"        -- the outermost part is an application of (++)

Yığın taşmaları

Bir ifadeyi zayıf kafa normal formuna göre değerlendirmek için önce diğer ifadelerin WHNF'ye göre değerlendirilmesi gerekebilir. Örneğin, 1 + (2 + 3)WHNF'yi değerlendirmek için önce değerlendirmemiz gerekir 2 + 3. Tek bir ifadeyi değerlendirmek, bu iç içe değerlendirmelerin çok çoğuna yol açarsa, sonuç bir yığın taşması olur.

Bu, büyük bir kısmı değerlendirilene kadar herhangi bir veri oluşturucu veya lambda üretmeyen büyük bir ifade oluşturduğunuzda olur. Bunlar genellikle bu tür kullanımlardan kaynaklanır foldl:

foldl (+) 0 [1, 2, 3, 4, 5, 6]
 = foldl (+) (0 + 1) [2, 3, 4, 5, 6]
 = foldl (+) ((0 + 1) + 2) [3, 4, 5, 6]
 = foldl (+) (((0 + 1) + 2) + 3) [4, 5, 6]
 = foldl (+) ((((0 + 1) + 2) + 3) + 4) [5, 6]
 = foldl (+) (((((0 + 1) + 2) + 3) + 4) + 5) [6]
 = foldl (+) ((((((0 + 1) + 2) + 3) + 4) + 5) + 6) []
 = (((((0 + 1) + 2) + 3) + 4) + 5) + 6
 = ((((1 + 2) + 3) + 4) + 5) + 6
 = (((3 + 3) + 4) + 5) + 6
 = ((6 + 4) + 5) + 6
 = (10 + 5) + 6
 = 15 + 6
 = 21

İfadeyi zayıf kafa normal formuna getirmeden önce nasıl derinleşmesi gerektiğine dikkat edin.

Merak edebilirsiniz, Haskell neden iç ifadeleri önceden azaltmıyor? Bu Haskell'in tembelliklerinden kaynaklanıyor. Genel olarak her alt ifadeye ihtiyaç duyulacağı varsayılamayacağından, ifadeler dışarıdan içinde değerlendirilir.

(GHC, bir alt ifadenin her zaman gerekli olduğu bazı durumları algılayacak bir katılık analizörüne sahiptir ve daha sonra bunu önceden değerlendirebilir. Ancak bu yalnızca bir optimizasyondur ve sizi taşmalardan kurtarmak için buna güvenmemelisiniz).

Öte yandan, bu tür bir ifade tamamen güvenlidir:

data List a = Cons a (List a) | Nil
foldr Cons Nil [1, 2, 3, 4, 5, 6]
 = Cons 1 (foldr Cons Nil [2, 3, 4, 5, 6])  -- Cons is a constructor, stop. 

Tüm alt ifadelerin değerlendirilmesi gerektiğini bildiğimizde bu büyük ifadeleri oluşturmaktan kaçınmak için, iç kısımları önceden değerlendirilmeye zorlamak istiyoruz.

seq

seqifadeleri değerlendirilmeye zorlamak için kullanılan özel bir işlevdir. Anlambilimi, zayıf kafa normal formuna seq x yher ydeğerlendirildiğinde, zayıf kafa normal formuna xda değerlendirildiği anlamına gelir .

Tanımlamasında kullanılan diğer yerlerin arasında foldl', katı varyantıdır foldl.

foldl' f a []     = a
foldl' f a (x:xs) = let a' = f a x in a' `seq` foldl' f a' xs

Her yineleme foldl'akümülatörü WHNF'ye zorlar. Bu nedenle, büyük bir ifade oluşturmayı önler ve bu nedenle yığının taşmasını önler.

foldl' (+) 0 [1, 2, 3, 4, 5, 6]
 = foldl' (+) 1 [2, 3, 4, 5, 6]
 = foldl' (+) 3 [3, 4, 5, 6]
 = foldl' (+) 6 [4, 5, 6]
 = foldl' (+) 10 [5, 6]
 = foldl' (+) 15 [6]
 = foldl' (+) 21 []
 = 21                           -- 21 is a data constructor, stop.

Ancak HaskellWiki'deki örnek olarak, akümülatör sadece WHNF için değerlendirildiğinden, bu her durumda sizi kurtarmaz. Örnekte, akümülatör bir demettir, bu nedenle sadece demet yapıcısının değerlendirilmesini zorlar, değil accya da len.

f (acc, len) x = (acc + x, len + 1)

foldl' f (0, 0) [1, 2, 3]
 = foldl' f (0 + 1, 0 + 1) [2, 3]
 = foldl' f ((0 + 1) + 2, (0 + 1) + 1) [3]
 = foldl' f (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1) []
 = (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1)  -- tuple constructor, stop.

Bundan kaçınmak için, bunu yapmak zorundayız ki, tuple yapıcısının değerlendirilmesi accve len. Bunu kullanarak yapıyoruz seq.

f' (acc, len) x = let acc' = acc + x
                      len' = len + 1
                  in  acc' `seq` len' `seq` (acc', len')

foldl' f' (0, 0) [1, 2, 3]
 = foldl' f' (1, 1) [2, 3]
 = foldl' f' (3, 2) [3]
 = foldl' f' (6, 3) []
 = (6, 3)                    -- tuple constructor, stop.

31
Kafa normal formu, bir lambda gövdesinin de azaltılmasını gerektirirken, zayıf kafa normal formunun bu gereksinimi yoktur. Yani \x -> 1 + 1WHNF ancak HNF olduğunu.
hammar

Wikipedia, HNF'nin "kafa pozisyonunda beta-redeks yoksa başın normal formda olduğunu" belirtir. Haskell beta-redex alt ifadeleri olmadığı için "zayıf" mı?

Katı veri yapıcılar oyuna nasıl giriyor? Sadece seqargümanlarını çağırmak gibi mi?
Bergi

1
@CaptainObvious: 1 + 2 ne NF ne de WHNF değil. İfadeler her zaman normal bir biçimde değildir.
hammar

2
@Zorobay: Sonucu yazdırmak için GHCi, ifadeyi sadece WHNF için değil, tamamen NF olarak değerlendirir. İki değişken arasındaki farkı anlatmanın bir yolu, bellek istatistiklerini etkinleştirmektir :set +s. Daha sonra bunun foldl' fdaha fazla thunks ayırdığınıfoldl' f' görebilirsiniz .
hammar

43

Haskell Wikibooks'un tembellik tanımındaki Thunks ve Zayıf Kafa Normal Formu bölümü, bu yararlı tasvirle birlikte WHNF'nin çok iyi bir açıklamasını sağlar:

Değerin (4, [1, 2]) adım adım değerlendirilmesi.  İlk aşama tamamen değerlendirilmemiştir;  sonraki tüm formlar WHNF'de ve sonuncusu da normal formdadır.

Değerin (4, [1, 2]) adım adım değerlendirilmesi. İlk aşama tamamen değerlendirilmemiştir; sonraki tüm formlar WHNF'de ve sonuncusu da normal formdadır.


5
İnsanların kafa normal formunu görmezden gelmelerini söylediklerini biliyorum, ama bu diyagramda normal bir kafa formunun neye benzediğini gösteren bir örnek verebilir misiniz?
CMCDragonkai

28

Haskell programları ifadelerdir ve değerlendirme yapılarak yürütülür .

Bir ifadeyi değerlendirmek için, tüm işlev uygulamalarını tanımlarına göre değiştirin. Bunu yapma sırası önemli değil, ama yine de önemli: en dıştaki uygulama ile başlayın ve soldan sağa doğru ilerleyin; buna tembel değerlendirme denir .

Misal:

   take 1 (1:2:3:[])
=> { apply take }
   1 : take (1-1) (2:3:[])
=> { apply (-)  }
   1 : take 0 (2:3:[])
=> { apply take }
   1 : []

Değiştirilecek başka işlev uygulaması kalmadığında değerlendirme durur. Sonuç normal formda (veya azaltılmış normal formda , RNF). Bir ifadeyi hangi sırayla değerlendirirseniz değerlendirin, daima aynı normal formla sonuçlanırsınız (ancak yalnızca değerlendirme sona erdiğinde).

Tembel değerlendirme için biraz farklı bir açıklama var. Yani, her şeyi sadece zayıf kafa normal formuna göre değerlendirmeniz gerektiğini söylüyor . Bir ifadenin WHNF'de olması için tam olarak üç durum vardır:

  • Bir kurucu: constructor expression_1 expression_2 ...
  • (+) 2Veya gibi çok az sayıda bağımsız değişken içeren yerleşik bir işlevsqrt
  • Bir lambda ifadesi: \x -> expression

Başka bir deyişle, ifadenin başı (yani en dıştaki işlev uygulaması) daha fazla değerlendirilemez, ancak işlev bağımsız değişkeni değerlendirilmemiş ifadeler içerebilir.

WHNF örnekleri:

3 : take 2 [2,3,4]   -- outermost function is a constructor (:)
(3+1) : [4..]        -- ditto
\x -> 4+5            -- lambda expression

notlar

  1. WHNF'deki "head" bir listenin başına değil, en dıştaki fonksiyon uygulamasına atıfta bulunur.
  2. Bazen insanlar değerlendirilmemiş ifadelere "thunks" derler, ama bunun bunu anlamanın iyi bir yolu olduğunu düşünmüyorum.
  3. Baş normal formu (HNF) Haskell için önemsizdir. Lambda ifadelerinin gövdelerinin de bir ölçüde değerlendirilmesi bakımından WHNF'den farklıdır.

Kullanılmasıdır seqiçinde foldl'yürürlüğe WHNF gelen HNF için değerlendirme?

1
@snmcdonald: Hayır, Haskell HNF'yi kullanmıyor. Değerlendirme , ikinci ifadeyi değerlendirmeden önce WHNF'ye seq expr1 expr2yönelik ilk ifadeyi expr1değerlendirecektir expr2.
Heinrich Apfelmus

26

Örneklerle iyi bir açıklama http://foldoc.org/Weak+Head+Normal+Form Head normal formunda, işlev soyutlamasının içindeki bir ifadenin bitlerini bile basitleştirirken, "zayıf" kafa normal formu işlev soyutlamalarında durur .

Eğer varsa, kaynaktan:

\ x -> ((\ y -> y+x) 2)

zayıf kafa normal formunda, ancak kafa normal formunda değil ... çünkü olası uygulama henüz değerlendirilemeyen bir fonksiyonun içinde sıkışmış.

Gerçek kafa normal formunun verimli bir şekilde uygulanması zor olacaktır. İşlevlerin içine atılmak gerekir. Zayıf kafa normal formunun avantajı, fonksiyonları opak bir tip olarak uygulayabilmeniz ve bu nedenle derlenmiş diller ve optimizasyonla daha uyumlu olmasıdır.


12

WHNF, lambdaların bedeninin değerlendirilmesini istemiyor, bu yüzden

WHNF = \a -> thunk
HNF = \a -> a + c

seq ilk argümanının WHNF'de olmasını istiyor, bu yüzden

let a = \b c d e -> (\f -> b + c + d + e + f) b
    b = a 2
in seq b (b 5)

için değerlendirir

\d e -> (\f -> 2 + 5 + d + e + f) 2

HNF kullanmak yerine

\d e -> 2 + 5 + d + e + 2

Ya da örneği yanlış anlıyorum ya da WHNF ve HNF'de 1 ve 2'yi karıştırıyorsunuz.
Zhen

5

Temel olarak, bir çeşit gövdeye sahip olduğunuzu varsayalım t.

Şimdi, tfonksiyonlar dışında aynı olan WHNF veya NHF'yi değerlendirmek istiyorsak ,

t1 : t2thunks nerede t1ve t2vardır. Bu durumda, t1sizin 0(ya da daha fazla, 0ekstra bir kutu açmadan verilecek bir sandık) olurdu

seqve $!WHNF'yi değerlendirin. Bunu not et

f $! x = seq x (f x)

1
@snmcdonald HNF'yi Yoksay. seq, WHNF olarak değerlendirildiğinde, WHNF ile ilgili ilk argümanı değerlendirdiğini söylüyor.
alternatif
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.