Genelde üretim kodunun Lazy I / O kullanmaktan kaçınması gerektiğini duydum. Sorum şu, neden? Tembel I / O'ları sadece etrafta oynamanın dışında kullanmak hiç uygun mudur? Alternatifleri (örneğin sayım görevlileri) daha iyi yapan nedir?
Genelde üretim kodunun Lazy I / O kullanmaktan kaçınması gerektiğini duydum. Sorum şu, neden? Tembel I / O'ları sadece etrafta oynamanın dışında kullanmak hiç uygun mudur? Alternatifleri (örneğin sayım görevlileri) daha iyi yapan nedir?
Yanıtlar:
Tembel IO'nun, edindiğiniz kaynağı serbest bırakmanın, programınızın verileri nasıl tükettiğine bağlı olması nedeniyle, biraz öngörülemez olması sorunu vardır - "talep modeli". Programınız kaynağa son referansı bıraktıktan sonra, GC sonunda bu kaynağı çalıştıracak ve serbest bırakacaktır.
Tembel akışlar programlamak için çok uygun bir stildir. Bu nedenle kabuk borular çok eğlenceli ve popülerdir.
Bununla birlikte, kaynaklar kısıtlıysa (yüksek performanslı senaryolarda veya makinenin sınırlarına göre ölçeklenmeyi bekleyen üretim ortamlarında olduğu gibi) temizlemek için GC'ye güvenmek yetersiz bir garanti olabilir.
Bazen ölçeklenebilirliği artırmak için kaynakları hevesle serbest bırakmanız gerekir.
Öyleyse, artımlı işlemeden vazgeçmek anlamına gelmeyen tembel IO'nun alternatifleri nelerdir (bu da çok fazla kaynak tüketir)? Eh, var foldl
getirdiği iteratees veya Enumerator'lar, aka, işleme dayalı 2000'lerin sonunda Oleg KISELYOV ve beri ağ tabanlı çok sayıda proje tarafından popüler.
Verileri tembel akışlar olarak veya tek bir büyük toplu iş olarak işlemek yerine, son parça okunduğunda kaynağın garantili sonlandırılmasıyla yığın tabanlı katı işlemeyi soyutlarız. Yinelemeli programlamanın özü budur ve çok güzel kaynak kısıtlamaları sunar.
Yineleme tabanlı IO'nun dezavantajı, biraz garip bir programlama modeline sahip olmasıdır (güzel iş parçacığı tabanlı kontrole karşı olay tabanlı programlamaya kabaca benzer). Herhangi bir programlama dilinde kesinlikle gelişmiş bir tekniktir. Ve programlama problemlerinin büyük çoğunluğu için tembel IO tamamen tatmin edicidir. Bununla birlikte, çok sayıda dosya açacaksanız veya birçok soket üzerinde konuşacaksanız veya birçok eşzamanlı kaynağı kullanıyorsanız, bir yineleme (veya numaralayıcı) yaklaşımı mantıklı olabilir.
Dons çok iyi bir cevap verdi, ancak (benim için) yinelemelerin en zorlayıcı özelliklerinden biri olanı dışarıda bıraktı: eski verilerin açıkça saklanması gerektiği için alan yönetimi hakkında akıl yürütmeyi kolaylaştırıyorlar. Düşünmek:
average :: [Float] -> Float
average xs = sum xs / length xs
Bu iyi bilinen bir boşluk sızıntısıdır, çünkü xs
hem sum
ve hem de hesaplamalarının yapılabilmesi için listenin tamamı bellekte tutulmalıdır length
. Bir kat oluşturarak verimli bir tüketici yapmak mümkündür:
average2 :: [Float] -> Float
average2 xs = uncurry (/) <$> foldl (\(sumT, n) x -> (sumT+x, n+1)) (0,0) xs
-- N.B. this will build up thunks as written, use a strict pair and foldl'
Ancak bunu her akış işlemcisi için yapmak biraz zahmetli. Bazı genellemeler var ( Conal Elliott - Beautiful Fold Zipping ), ancak anlaşılmamış gibi görünüyorlar. Bununla birlikte, yinelemeler size benzer bir ifade düzeyi sağlayabilir.
aveIter = uncurry (/) <$> I.zip I.sum I.length
Bu bir katlama kadar verimli değildir çünkü liste hala birden çok kez yinelenir, ancak parçalar halinde toplanır, böylece eski veriler verimli bir şekilde çöp toplanabilir. Bu özelliği kırmak için, stream2list ile olduğu gibi, girdinin tamamını açıkça saklamak gerekir:
badAveIter = (\xs -> sum xs / length xs) <$> I.stream2list
Bir programlama modeli olarak yinelemelerin durumu devam eden bir çalışmadır, ancak bir yıl öncesine göre çok daha iyidir. Yararlıdır ne combinators Bizler öğrenme (örneğin zip
, breakE
, enumWith
) ve hangi iteratees dahili ve combinators sürekli daha ekspresivitesini sağlamak, bunun sonucunda daha az bulunmaktadır.
Bununla birlikte, Dons gelişmiş bir teknik oldukları konusunda haklı; Bunları kesinlikle her G / Ç problemi için kullanmam.
Üretim kodunda her zaman tembel I / O kullanıyorum. Don'un bahsettiği gibi, bu yalnızca belirli durumlarda bir sorun. Ancak sadece birkaç dosyayı okumak için iyi çalışıyor.
Güncelleme: Son zamanlarda haskell -cafe Oleg Kiseljov , unsafeInterleaveST
(ST monad içinde tembel IO uygulamak için kullanılan) çok güvensiz olduğunu gösterdi - eşitlik mantığını bozuyor. bad_ctx :: ((Bool,Bool) -> Bool) -> Bool
Böyle inşa etmeye izin verdiğini gösteriyor .
> bad_ctx (\(x,y) -> x == y)
True
> bad_ctx (\(x,y) -> y == x)
False
==
değişmeli olsa bile .
Tembel GÇ ile ilgili başka bir sorun: Asıl GÇ işlemi, örneğin dosya kapatıldıktan sonra, çok geç olana kadar ertelenebilir. Haskell Wiki'den Alıntı - Tembel GÇ ile ilgili sorunlar :
Örneğin, yeni başlayanlar için yaygın bir hata, bir dosyayı okumayı bitirmeden önce kapatmaktır:
wrong = do fileData <- withFile "test.txt" ReadMode hGetContents putStr fileData
Sorun şu ki withFile, fileData zorlanmadan önce tutamacı kapatır. Doğru yol, tüm kodu withFile'a iletmektir:
right = withFile "test.txt" ReadMode $ \handle -> do fileData <- hGetContents handle putStr fileData
Burada, veriler Dosya bitmeden önce tüketilir.
Bu genellikle beklenmeyen bir durumdur ve yapılması kolay bir hatadır.
Ayrıca bkz: Geç G / Ç ile ilgili üç sorun örneği .
hGetContents
ve withFile
anlamsızdır çünkü eski, tutamacı "sözde kapalı" bir duruma sokar ve sizin için kapatmayı (tembel olarak) ele alır, böylece kod tam olarak eşittir readFile
veya openFile
onsuzdur hClose
. Temelde tembel I / O budur . Eğer kullanmıyorsanız readFile
, getContents
veya hGetContents
tembel I / O'yu kullanmıyoruz. Örneğin line <- withFile "test.txt" ReadMode hGetLine
iyi çalışıyor.
hGetContents
Dosyayı sizin için kapatmanıza rağmen, "erken" kapatmanıza da izin verilebilir ve kaynakların tahmin edilebilir şekilde serbest bırakılmasına yardımcı olur.
Şimdiye kadar bahsedilmeyen tembel IO ile ilgili bir başka sorun da şaşırtıcı davranışlara sahip olmasıdır. Normal bir Haskell programında, programınızın her bir parçasının ne zaman değerlendirildiğini tahmin etmek bazen zor olabilir, ancak neyse ki saflık nedeniyle performans problemleriniz yoksa bunun bir önemi yoktur. Tembel IO tanıtıldığında, kodunuzun değerlendirme sırası aslında anlamı üzerinde bir etkiye sahiptir, bu nedenle zararsız olduğunu düşünmeye alıştığınız değişiklikler size gerçek sorunlara neden olabilir.
Örnek olarak, makul görünen ancak ertelenmiş GÇ tarafından daha kafa karıştırıcı hale getirilen kod hakkında bir soru: withFile vs. openFile
Bu sorunlar her zaman ölümcül değildir, ancak düşünülmesi gereken başka bir şey ve tüm işi önceden yapmakla ilgili gerçek bir sorun olmadıkça kişisel olarak tembel IO'dan kaçınmam için yeterince şiddetli bir baş ağrısı.