İşlevsel programlama dilleri herhangi bir durumu kaydedemezse, bir kullanıcıdan gelen girdiyi okumak (yani onu nasıl "saklıyorlar" demek istiyorum) veya bu konuda herhangi bir veriyi depolamak gibi bazı basit şeyleri nasıl yaparlar?
Anladığınız gibi, işlevsel programlamanın durumu yoktur - ancak bu, verileri depolayamayacağı anlamına gelmez. Aradaki fark, şu satırlar boyunca bir (Haskell) ifadesi yazarsam
let x = func value 3.14 20 "random"
in ...
Şu durumda değerinin x
her zaman aynı olduğu garanti edilir ...
: hiçbir şey onu değiştiremez. Benzer şekilde, f :: String -> Integer
bir işleve sahipsem (bir dizge alan ve bir tamsayı döndüren bir işlev), f
bunun bağımsız değişkenini değiştirmeyeceğinden veya herhangi bir genel değişkeni değiştirmeyeceğinden veya bir dosyaya veri yazmayacağından emin olabilirim . Sepp2k'in yukarıdaki bir yorumda söylediği gibi, bu değişmezlik, programlar hakkında mantık yürütmek için gerçekten yararlıdır: verilerinizi katlayan, döndüren ve bozan işlevler yazarsınız, yeni kopyalar döndürerek onları birbirine zincirleyebilirsiniz ve hiçbirinin olmadığından emin olabilirsiniz. Bu işlev çağrılarından biri "zararlı" her şeyi yapabilir. Bunun x
her zaman olduğunu biliyorsunuz x
ve birinin x := foo bar
beyanı arasında bir yere yazdığından endişelenmenize gerek yok .x
ve kullanımı, çünkü bu imkansız.
Şimdi, bir kullanıcıdan gelen girdileri okumak istersem ne olur? KennyTM'nin dediği gibi, fikir, saf olmayan bir işlevin tüm dünyayı bir argüman olarak geçen ve hem sonucunu hem de dünyayı döndüren saf bir işlev olmasıdır. Tabii ki, bunu gerçekten yapmak istemezsiniz: Birincisi, korkunç derecede hantal, diğeri için, aynı dünya nesnesini yeniden kullanırsam ne olur? Yani bu bir şekilde soyutlanıyor. Haskell, bunu IO türü ile yönetir:
main :: IO ()
main = do str <- getLine
let no = fst . head $ reads str :: Integer
...
Bu bize bunun main
hiçbir şey döndürmeyen bir IO eylemi olduğunu söylüyor ; Bu eylemi yürütmek, bir Haskell programını çalıştırmak demektir. Kural, GÇ türlerinin bir GÇ eyleminden asla kaçamayacağıdır; bu bağlamda, bu eylemi kullanarak tanıtıyoruz do
. Böylece, iki şekilde düşünülebilen getLine
an'ı döndürür IO String
: birincisi, çalıştırıldığında bir dizge üreten bir eylem olarak; ikincisi, saf olmayan bir şekilde elde edildiği için IO tarafından "lekelenmiş" bir dize olarak. Birincisi daha doğrudur, ancak ikincisi daha yararlı olabilir. <-
Sürer String
durumlar o . Sonra hem kullanabilir ve içinde . Böylece saf olmayan verileri ( içine ) ve saf verileri ( ) sakladık .IO String
ve saklar str
biz IO eylem olduğumuza -ama biz bu yüzden "kaçış" Yapamam, bu yedeklemek sarmak gerekecek. Sonraki satır bir tamsayıyı ( okumaya çalışırreads
) ve ilk başarılı eşleşmeyi alır (fst . head
); bunların hepsi saf (IO yok), bu yüzden ona bir isim veriyoruzlet no = ...
no
str
...
getLine
str
let no = ...
IO ile çalışmak için bu mekanizma çok güçlüdür: programınızın saf, algoritmik bölümünü saf olmayan, kullanıcı etkileşimi tarafından ayırmanıza ve bunu tür düzeyinde uygulamanıza olanak tanır. SizinminimumSpanningTree
işlevi muhtemelen kodunuzda bir yere başka bir şey değiştirmek ya da kullanıcıya bir mesaj yazmak ve benzeri olamaz. Güvenli.
Haskell'de IO kullanmak için bilmeniz gereken tek şey bu; Tek istediğin buysa, burada durabilirsin. Ancak bunun neden işe yaradığını anlamak istiyorsanız , okumaya devam edin. (Ve bu öğelerin Haskell'e özgü olacağını unutmayın - diğer diller farklı bir uygulama seçebilir.)
Yani bu muhtemelen biraz hile gibi göründü, bir şekilde saf Haskell'e kirlilik ekledi. Ama öyle değil — IO tipini tamamen saf Haskell içinde uygulayabileceğimiz ortaya çıktı (bize verildiği sürece RealWorld
). Buradaki fikir şudur: Bir IO eylemi IO type
, RealWorld -> (type, RealWorld)
gerçek dünyayı alan ve hem türde bir nesne hem type
de değiştirilmiş olanı döndüren bir işlevle aynıdır RealWorld
. Ardından, bu türü delirmeden kullanabilmemiz için birkaç işlev tanımlarız:
return :: a -> IO a
return a = \rw -> (a,rw)
(>>=) :: IO a -> (a -> IO b) -> IO b
ioa >>= fn = \rw -> let (a,rw') = ioa rw in fn a rw'
İlki, hiçbir şey yapmayan IO eylemleri hakkında konuşmamıza izin veriyor: return 3
gerçek dünyayı sorgulamayan ve sadece geri dönen bir IO eylemidir3
. >>=
"Bind" olarak telaffuz edilen operatör, IO eylemlerini çalıştırmamıza izin verir. IO eyleminden değeri çıkarır, onu ve gerçek dünyayı işlevden geçirir ve ortaya çıkan GÇ eylemini döndürür. Bunu not et>>=
asla IO eylemlerin sonuçları kaçmasına izin verilmesini kuralımızla zorlar.
Daha sonra yukarıdakileri main
aşağıdaki sıradan işlev uygulamaları kümesine dönüştürebiliriz:
main = getLine >>= \str -> let no = (fst . head $ reads str :: Integer) in ...
Haskell çalışma zamanı atlama-başlar main
ilk ileRealWorld
ve biz hazırız! Her şey saf, sadece süslü bir sözdizimi var.
[ Düzenleme: @Conal'ın belirttiği gibi , bu aslında Haskell'in IO yapmak için kullandığı şey değil. Eşzamanlılık eklerseniz veya aslında bir IO eyleminin ortasında dünyanın değişmesi için herhangi bir yol eklerseniz bu model bozulur, bu nedenle Haskell'in bu modeli kullanması imkansızdır. Yalnızca sıralı hesaplama için doğrudur. Bu nedenle, Haskell'in IO'su biraz kaçış olabilir; öyle olmasa bile, kesinlikle bu kadar zarif değil. @ Conal'ın gözlemine göre, Tackling the Awkward Squad'da Simon Peyton-Jones'un söylediklerine bakın [pdf] , bölüm 3.1'de bakın; bu doğrultuda alternatif bir model oluşturabilecek şeyleri sunar, ancak daha sonra karmaşıklığı nedeniyle onu bırakır ve farklı bir yol izler.]
Yine, bu IO'nun ve genel olarak değişkenliğin Haskell'de nasıl çalıştığını (hemen hemen) açıklıyor; eğer bu bilmeni istiyorum hepsi bu, buradan okuma durduramaz. Son bir doz teori istiyorsanız, okumaya devam edin - ama unutmayın, bu noktada, sorunuzdan gerçekten çok uzaklara gittik!
Son bir şey: bu yapı ortaya çıkıyor - return
ve ile parametrik bir tip >>=
- çok genel; Bir monad olarak adlandırılır ve oluyor do
notasyonu, return
ve >>=
bunlardan herhangi biri ile çalışır. Burada gördüğünüz gibi, monadlar büyülü değildir; tüm büyülü olan budo
blokların işlev çağrılarına dönüşmesidir. RealWorld
Tip herhangi sihir tek yerdir. []
Liste yapıcısı gibi türler de monadlardır ve bunların saf olmayan kodla hiçbir ilgisi yoktur.
Artık monad kavramı hakkında (neredeyse) her şeyi biliyorsunuz (yerine getirilmesi gereken birkaç kanun ve resmi matematiksel tanım hariç), ancak sezgiden yoksunuz. İnternette çok sayıda monad öğreticisi var; Sevdiğim bu bir , ama seçenekler vardır. Ancak bu muhtemelen size yardımcı olmayacak ; Sezgiyi elde etmenin tek gerçek yolu, onları kullanmak ve doğru zamanda birkaç öğretici okumaktır.
Ancak, IO'yu anlamak için bu sezgiye ihtiyacınız yok . Monadları tam olarak anlamak, pastanın üzerine krema yapmaktır, ancak şu anda IO'yu kullanabilirsiniz. Ben size ilk main
işlevi gösterdikten sonra kullanabilirsiniz . Hatta IO kodunu saf olmayan bir dildeymiş gibi ele alabilirsiniz! Ancak altta yatan işlevsel bir temsil olduğunu unutmayın: kimse hile yapmıyor.
(Not: Uzunluk için üzgünüm. Biraz uzağa gittim.)