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
seq
ifadeleri değerlendirilmeye zorlamak için kullanılan özel bir işlevdir. Anlambilimi, zayıf kafa normal formuna seq x y
her y
değerlendirildiğinde, zayıf kafa normal formuna x
da 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 acc
ya 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 acc
ve 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.