İşlevsel programlama dilleri nasıl çalışır?


92

İşlevsel programlama dilleri herhangi bir durumu kaydedemezse, bir kullanıcıdan gelen girdileri okumak gibi basit şeyleri nasıl yaparlar? Girişi nasıl "saklarlar" (veya bu konuyla ilgili herhangi bir veriyi saklarlar?)

Örneğin: Bu basit C şeyi Haskell gibi işlevsel bir programlama diline nasıl çevrilebilir?

#include<stdio.h>
int main() {
    int no;
    scanf("%d",&no);
    return 0;
}

(Sorum şu mükemmel gönderiden ilham aldı: "İsimler Krallığında Yürütme" . Bunu okumak bana tam olarak nesne yönelimli programlamanın ne olduğunu, Java'nın bunu uç bir şekilde nasıl uyguladığını ve işlevsel programlama dillerinin nasıl bir kontrast.)



4
Bu iyi bir soru, çünkü sistemik düzeyde bir bilgisayarın yararlı olması için duruma ihtiyacı var. Simon Peyton-Jones (Haskell'in arkasındaki geliştiricilerden biri) ile bir röportajı izledim, burada yalnızca tamamen devletsiz yazılım çalıştıran bir bilgisayarın yalnızca bir şeyi başarabileceğini söyledi: Ateşli olun! Aşağıda birçok iyi cevap var. İki ana strateji vardır: 1) Saf olmayan bir dil oluşturmak. 2) Soyut duruma yönelik kurnaz bir plan yapın, Haskell'in yaptığı gibi, esasında eskisini değiştirmek yerine yeni, biraz değiştirilmiş bir Dünya yaratın.
zararlar

14
SPJ, eyaletten değil, yan etkilerden bahsetmiyor muydu? Saf hesaplamalar, bağımsız değişken bağlamalarında ve çağrı yığınında örtük bir çok duruma sahiptir, ancak yan etkiler olmadan (örneğin, G / Ç) yararlı hiçbir şey yapamaz. İki nokta gerçekten oldukça farklı - tonlarca saf, durum bilgili Haskell kodu var ve Statemonad çok zarif; Öte yandan IO, isteksizce kullanılan çirkin, kirli bir hack.
CA McCann

4
camccann haklı. İşlevsel dillerde bol miktarda devlet var. Zorunlu dillerdeki gibi "uzaktan ürkütücü eylem" yerine açıkça yönetiliyor.
SADECE

1
Burada bazı karışıklıklar olabilir. Belki bilgisayarların yararlı olması için efektlere ihtiyacı vardır, ancak bence buradaki soru bilgisayarlar değil, programlama dilleri ile ilgilidir.
Conal

Yanıtlar:


80

İş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 xher zaman aynı olduğu garanti edilir ...: hiçbir şey onu değiştiremez. Benzer şekilde, f :: String -> Integerbir işleve sahipsem (bir dizge alan ve bir tamsayı döndüren bir işlev), fbunun 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 xher zaman olduğunu biliyorsunuz xve birinin x := foo barbeyanı 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 mainhiç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 getLinean'ı 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 Stringdurumlar o . Sonra hem kullanabilir ve içinde . Böylece saf olmayan verileri ( içine ) ve saf verileri ( ) sakladık .IO String ve saklar strbiz 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 = ...nostr...getLinestrlet 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 typede 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 3gerç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 mainaş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 - returnve ile parametrik bir tip >>=- çok genel; Bir monad olarak adlandırılır ve oluyor donotasyonu, returnve >>=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. RealWorldTip 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 mainiş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.)


6
Haskell hakkında beni her zaman etkileyen şey (yaptığım ve öğrenmek için cesurca çaba sarf ediyorum) sözdiziminin çirkinliğidir. Sanki diğer dillerin en kötü parçalarını alıp bir kovaya atıp öfkeyle karıştırıyorlardı. Ve bu insanlar daha sonra C ++ 'ın (yer yer) garip sözdiziminden şikayet edecekler!

19
Neil: Gerçekten mi? Aslında Haskell'in sözdizimini çok temiz buluyorum. Merak ediyorum; özellikle neyi kastediyorsun? ( > >
Değeri ne olursa olsun

6
Bana göre Haskell'in sözdizimi Scheme kadar temiz olmasa da, kaşlı ayraçlı dillerin en güzel sözdizimiyle bile karşılaştırılmaya başlamıyor, C ++ en kötüleri arasında. . Zevk için hesap yok sanırım. Herkesin sözdizimsel olarak hoş bulduğu bir dil olduğunu sanmıyorum.
CA McCann

8
@NeilButterworth: Sorununun işlev adları kadar sözdizimi olmadığından şüpheleniyorum. Gibi fonksiyonlar ise >>=veya $daha yerine aramıştı nerede bindve apply, haskell kod perl'de gibi az çok görünürdü. Demek istediğim, haskell ve düzen sözdizimi arasındaki temel fark, haskell'in infix operatörlerine ve isteğe bağlı parenlere sahip olmasıdır. İnsanlar infix operatörlerini aşırı kullanmaktan kaçınırlarsa, haskell daha az parensli şemaya çok benzer.
sepp2k

5
@camcann: Pekala, ama demek istediğim şudur: Planın temel sözdizimi (functionName arg1 arg2). Parenleri kaldırırsanız functionName arg1 arg2, haskell sözdizimidir. İnfix operatörlerine keyfi olarak korkunç adlarla izin verirseniz, arg1 §$%&/*°^? arg2bu daha çok haskell'e benzer. (Sadece btw ile dalga geçiyorum, aslında haskell'i seviyorum).
sepp2k

23

Burada pek çok iyi cevap var ama bunlar uzun. Faydalı kısa bir cevap vermeye çalışacağım:

  • İşlevsel diller durumu, C'nin yaptığı aynı yerlere koyar: adlandırılmış değişkenlerde ve öbek üzerinde tahsis edilen nesnelerde. Farklılıklar şu şekildedir:

    • İşlevsel bir dilde, bir "değişken" kapsama girdiğinde (bir işlev çağrısı veya izin bağlama yoluyla) ilk değerini alır ve bu değer daha sonra değişmez . Benzer şekilde, öbek üzerinde tahsis edilen bir nesne, daha sonra değişmeyen tüm alanlarının değerleriyle hemen başlatılır.

    • "Durum değişiklikleri", mevcut değişkenleri veya nesneleri değiştirerek değil, yeni değişkenleri bağlayarak veya yeni nesneler tahsis ederek ele alınır.

  • IO bir hile ile çalışır. Bir dizge üreten yan etkili bir hesaplama, bağımsız değişken olarak bir Dünyayı alan ve dizgeyi ve yeni bir Dünyayı içeren bir çift döndüren bir işlev tarafından tanımlanır. Dünya, tüm disk sürücülerinin içeriğini, gönderilen veya alınan her ağ paketinin geçmişini, ekrandaki her pikselin rengini ve bunun gibi şeyleri içerir. İşin püf noktası, Dünya'ya erişimin dikkatli bir şekilde kısıtlanması ve böylece

    • Hiçbir program dünyanın bir kopyasını yapamaz (nereye koyarsınız?)

    • Hiçbir program dünyayı çöpe atamaz

    Bu numarayı kullanmak, durumu zaman içinde değişen tek ve benzersiz bir Dünya olmasını mümkün kılar. Olan dil çalışma zamanı sistemi, değil işlevsel bir dilde yazılmış, yerinde benzersiz Dünya güncellenmesi yerine yenisini dönerek yan etkileyen hesaplama uygulamak.

    Bu numara Simon Peyton Jones ve Phil Wadler tarafından dönüm noktası niteliğindeki makaleleri "Imperative Functional Programming" de güzel bir şekilde açıklanmıştır .


4
Anlayabildiğim kadarıyla, bu IOhikaye ( World -> (a,World)) Haskell'e uygulandığında bir efsanedir, çünkü bu model yalnızca salt sıralı hesaplamayı açıklarken Haskell'in IOtürü eşzamanlılığı içerir. "Tamamen sıralı" derken, dünyanın (evrenin) bile, zorunlu bir hesaplamanın başlangıcı ve bitişi arasında, bu hesaplama dışında değişmesine izin verilmediğini kastediyorum. Örneğin, bilgisayarınız uzaklaşırken beyniniz vb. Yapamaz. Eşzamanlılık daha benzer bir şeyle ele alınabilir World -> PowerSet [(a,World)], bu da belirsizliğe ve serpiştirmeye izin verir.
Conal

1
@Conal: IO hikayesinin belirsizliğe ve serpiştirmeye oldukça güzel bir şekilde genellendiğini düşünüyorum; Doğru hatırlıyorsam, "Garip Takım" gazetesinde oldukça iyi bir açıklama var. Ama gerçek paralelliği net bir şekilde açıklayan iyi bir makale bilmiyorum.
Norman Ramsey

3
Anladığım kadarıyla, "Awkward Squad" kâğıt terk basit denotasyonel modelini genelleme girişimi IO, yani World -> (a,World)yerine (popüler ve kalıcı "mit" Ben anılacaktır) ve operasyonel bir açıklama verir. Bazıları işlemsel anlambilimden hoşlanır, ancak beni tamamen memnun bırakmazlar. Lütfen başka bir cevapta daha uzun cevabıma bakın.
Conal

+1 Bu, IO Monad'leri daha çok anlamama ve soruyu yanıtlamama yardımcı oldu.
CaptainCasey

Çoğu Haskell derleyicisi aslında IOolarak tanımlamaktadır RealWorld -> (a,RealWorld), ancak gerçek dünyayı temsil etmek yerine, bu sadece soyut bir değerdir ve derleyici tarafından optimize edilmek zorundadır.
Jeremy List

19

Daha fazla yer açmak için yeni bir cevaba yorum yanıtını kesiyorum:

Yazdım:

Anlayabildiğim kadarıyla, bu IOhikaye ( World -> (a,World)) Haskell'e uygulandığında bir efsanedir, çünkü bu model yalnızca salt sıralı hesaplamayı açıklarken Haskell'in IOtürü eşzamanlılığı içerir. "Tamamen sıralı" derken, dünyanın (evrenin) bile, zorunlu bir hesaplamanın başlangıcı ve bitişi arasında, bu hesaplama dışında değişmesine izin verilmediğini kastediyorum. Örneğin, bilgisayarınız uzaklaşırken beyniniz vb. Yapamaz. Eşzamanlılık, daha benzer bir şeyle ele alınabilir World -> PowerSet [(a,World)], bu da belirsizliğe ve serpiştirmeye izin verir.

Norman şunu yazdı:

@Conal: IO hikayesinin belirsizliğe ve serpiştirmeye oldukça güzel bir şekilde genellendiğini düşünüyorum; Doğru hatırlıyorsam, "Garip Takım" gazetesinde oldukça iyi bir açıklama var. Ama gerçek paralelliği net bir şekilde açıklayan iyi bir makale bilmiyorum.

@Norman: Hangi anlamda genelliyor? Genellikle verilen ifade modelinin / açıklamasının World -> (a,World)Haskell ile eşleşmediğini, IOçünkü belirsizliği ve eşzamanlılığı hesaba katmadığını ileri sürüyorum. Buna uyan daha karmaşık bir model olabilir World -> PowerSet [(a,World)], ancak böyle bir modelin geliştirilip geliştirilmediğini ve yeterli ve tutarlı olduğunu bilmiyorum. IOBinlerce FFI tarafından ithal edilen zorunlu API çağrısı ile dolu olduğu düşünüldüğünde, böyle bir canavarın bulunabileceğinden kişisel olarak şüpheliyim . Ve IObu nedenle amacını yerine getiriyor:

Açık problem: IOmonad Haskell'in günah kutusu haline geldi. (Ne zaman bir şeyi anlamasak, onu IO monadına atarız.)

(Simon PJ'nin POPL konuşmasından Saçlı gömlek giymek Saçlı gömlek giymek: Haskell üzerine bir retrospektif .)

Bölüm 3.1'de mücadele Awkward Squad hakkında çalışmalarını değil ne yaptığı, Simon noktaları type IO a = World -> (a, World)da dahil olmak üzere, "Biz eşzamanlılık eklerken yaklaşım iyi ölçek değildir". Daha sonra olası bir alternatif model önerir ve daha sonra, ifade niteliğindeki açıklamalar yapma girişimini terk eder.

Bununla birlikte, bunun yerine, işlem taşlarının anlambilimine standart yaklaşımlara dayanan işlemsel bir semantiği benimseyeceğiz.

Kesin ve kullanışlı bir gösterim modeli bulmadaki bu başarısızlık, Haskell IO'yu neden "işlevsel programlama" dediğimiz şeyin özünden ve derin faydalarından bir sapma olarak gördüğümün ya da Peter Landin'in daha özel olarak "ifade edici programlama" olarak adlandırdığı şeyin temelinde yatmaktadır. . Yorumları burada görün.


Uzun cevap için teşekkürler. Sanırım belki de yeni operasyonel efendilerimiz tarafından beynim yıkandı. Sola hareket ettirenler ve sağa hareket edenler vb. Bazı yararlı teoremleri kanıtlamayı mümkün kılmıştır. Belirsizliği ve eşzamanlılığı açıklayan sevdiğiniz herhangi bir ifade modeli gördünüz mü ? Bende yok.
Norman Ramsey

1
World -> PowerSet [World]Belirsizliği ve serpiştirme tarzı eşzamanlılığı net bir şekilde yakalamayı seviyorum . Bu alan tanımı bana, ana akım eşzamanlı zorunlu programlamanın (Haskell's dahil) inatçı olduğunu - kelimenin tam anlamıyla sıralı olmaktan üssel olarak daha karmaşık olduğunu söylüyor. Haskell IOmitinde gördüğüm en büyük zarar , bu içsel karmaşıklığı karartıyor ve devrilmesini engelliyor.
Conal

Neden World -> (a, World)bozuk olduğunu görsem de, değiştirmenin neden World -> PowerSet [(a,World)]eşzamanlılığı doğru bir şekilde modellediğini bilmiyorum. Bana göre bu, içindeki programların IOlist monad gibi bir şeyde çalışması gerektiğini, kendilerini döndürülen setin her öğesine uygulayarak çalıştırması gerektiği anlamına geliyor. IOeylem tarafından . Neyi kaçırıyorum?
Antal Spector-Zabusky

3
@Absz: Öncelikle benim önerdiğim model World -> PowerSet [(a,World)]doğru değil. Onun World -> PowerSet ([World],a)yerine deneyelim . PowerSetolası sonuçlar kümesini verir (belirsizlik). [World]ara durumların dizisidir (liste / belirsizlik monad değil), serpiştirmeye (iş parçacığı zamanlaması) izin verir. Ve tüm ara durumlardan geçmeden önce ([World],a)erişime izin verdiği için de tam olarak doğru değil a. Bunun yerine, World -> PowerSet (Computation a)nerede kullanılacağını tanımlayındata Computation a = Result a | Step World (Computation a)
Conal

Hala bir sorun görmüyorum World -> (a, World). Eğer Worldtip gerçekten tüm dünyayı kapsamaktadır, o zaman da aynı anda çalışan tüm işlemler hakkında bilgi ve aynı zamanda tüm sivil determinizm 'rastgele tohum' içermektedir. Ortaya çıkan World, zamanın ilerlemiş olduğu ve bazı etkileşimlerin gerçekleştirildiği bir dünyadır. Bu modelle ilgili tek gerçek sorun, çok genel olması ve değerlerinin Worldinşa edilememesi ve manipüle edilememesidir.
Rotsor

17

Fonksiyonel programlama lambda Calculus'tan türemiştir. İşlevsel programlamayı gerçekten anlamak istiyorsanız, http://worrydream.com/AlligatorEggs/ adresine bakın.

Lambda Calculus'u öğrenmenin ve sizi heyecan verici Fonksiyonel programlama dünyasına götürmenin "eğlenceli" bir yoludur!

Lambda Calculus'u bilmek işlevsel programlamada nasıl yardımcı olur?

Yani Lambda Calculus, Lisp, Scheme, ML, Haskell, .... gibi birçok gerçek dünya programlama dilinin temelidir.

Varsayalım, herhangi bir girdiye üç ekleyen bir işlevi açıklamak istediğimizi varsayalım, böylece şunu yazacağız:

plus3 x = succ(succ(succ x)) 

"Artı3, herhangi bir x sayısına uygulandığında, x'in halefinin halefini veren bir işlevdir"

Herhangi bir sayıya 3 ekleyen işlevin artı3 olarak adlandırılmasına gerek olmadığını unutmayın; "artı3" adı, bu işlevi adlandırmak için yalnızca uygun bir kısaltmadır

(plus3 x) (succ 0) ≡ ((λ x. (succ (succ (succ x)))) (succ 0))

Bir fonksiyon için lambda sembolünü kullandığımıza dikkat edin (Sanırım bir tür Timsah gibi görünüyor, tahmin ediyorum ki Timsah yumurtaları fikrinin nereden geldiğini tahmin ediyorum)

Lambda sembolü Timsah (bir işlev) ve x ise rengidir. X'i bir argüman olarak da düşünebilirsiniz (Lambda Calculus fonksiyonlarının gerçekte sadece bir argümana sahip olduğu varsayılır) geri kalanını fonksiyonun gövdesi olarak düşünebilirsiniz.

Şimdi soyutlamayı düşünün:

g ≡ λ f. (f (f (succ 0)))

F argümanı, bir işlev konumunda (bir çağrıda) kullanılır. Bir girdi olarak başka bir işlevi aldığı için ga daha yüksek dereceli işlevi diyoruz. Diğer işlev çağrılarını f " yumurta " olarak düşünebilirsiniz . Şimdi yarattığımız iki işlevi veya " Timsahları " alarak şuna benzer bir şey yapabiliriz:

(g plus3) = (λ f. (f (f (succ 0)))(λ x . (succ (succ (succ x)))) 
= ((λ x. (succ (succ (succ x)))((λ x. (succ (succ (succ x)))) (succ 0)))
 = ((λ x. (succ (succ (succ x)))) (succ (succ (succ (succ 0)))))
 = (succ (succ (succ (succ (succ (succ (succ 0)))))))

Dikkat ederseniz, λ f Timsahımızın λ x Timsahımızı yediğini ve ardından λ x Timsahımızı yediğini ve öldüğünü görebilirsiniz. Sonra λ x Timsahımız λ f'nin Timsah yumurtalarında yeniden doğar. Sonra süreç tekrar eder ve soldaki λ x Timsah şimdi sağdaki diğer λ x Timsahı yer.

Sonra "kurallarının bu basit bir set kullanabilirsiniz Alligators " yeme " Timsahlar bir gramerini tasarlamak ve böylece Fonksiyonel programlama dilleri doğmuş"!

Böylece, Lambda Calculus'u bilip bilmediğinizi, İşlevsel Dillerin nasıl çalıştığını anlayacaksınız.


@tuckster: Daha önce birkaç kez lambda hesabı çalıştım ... ve evet AlligatorEggs makalesi bana mantıklı geliyor. Ama bunu programlamayla ilişkilendiremiyorum. Benim için şu anda labda hesabı ayrı bir teori gibidir, bu tam da orada. Programlama dillerinde lambda hesabı kavramları nasıl kullanılır?
Lazer

3
@eSKay: Haskell , normal bir programlama dili gibi görünmesini sağlamak için ince bir sözdizimsel şeker katmanına sahip bir lambda hesabıdır. Lisp ailesinin dilleri de Timsah Yumurtalarının temsil ettiği türlenmemiş lambda hesabına çok benzer. Lambda hesabının kendisi, aslında bir tür "işlevsel programlama birleştirme dili" gibi minimalist bir programlama dilidir.
CA McCann

@eSKay: Bazı örneklerle nasıl ilişkili olduğu hakkında biraz ekledim. Umarım bu yardımcı olur!
PJT

Cevabımdan çıkaracaksanız, cevabımı iyileştirmeye çalışabilmem için lütfen neden hakkında bir yorum bırakabilir misiniz? Teşekkür ederim.
PJT

14

Haskell'de durumu işleme tekniği çok basittir. Ve bunun üstesinden gelmek için monadları anlamanıza gerek yok.

Durumlu bir programlama dilinde, tipik olarak bir yerde saklanan bir değeriniz olur, bazı kodlar çalıştırılır ve sonra depolanan yeni bir değeriniz olur. Zorunlu dillerde bu durum sadece "arka planda" bir yerdedir. (Saf) işlevsel bir dilde bunu açık hale getirirsiniz, böylece durumu dönüştüren işlevi açıkça yazarsınız.

Yani, X tipi bir duruma sahip olmak yerine, X'i X'e eşleyen fonksiyonlar yazarsınız. İşte bu kadar! Devlet hakkında düşünmekten, eyalette hangi işlemleri gerçekleştirmek istediğinizi düşünmeye geçersiniz. Daha sonra bu işlevleri birbirine bağlayabilir ve tüm programları oluşturmak için bunları çeşitli şekillerde birleştirebilirsiniz. Elbette sadece X'i X'e eşlemekle sınırlı değilsiniz. Çeşitli veri kombinasyonlarını girdi olarak almak ve sonunda çeşitli kombinasyonları döndürmek için işlevler yazabilirsiniz.

Monadlar, bunu organize etmeye yardımcı olacak birçok araç arasında bir araçtır. Ancak monadlar aslında sorunun çözümü değil. Çözüm, devlet yerine devlet dönüşümlerini düşünmektir.

Bu aynı zamanda G / Ç ile de çalışır. Gerçekte olan şudur: kullanıcıdan doğrudan bir eşdeğeriyle girdi almak scanfve bir yerde saklamak yerine, bunun yerine, sahip olsaydınız sonucuyla ne yapacağınızı söyleyen bir işlev yazarsınız scanfve sonra bunu iletirsiniz. I / O API işlevi. Haskell'de monad >>=kullandığınızda tam olarak ne yapar IO. Dolayısıyla, herhangi bir G / Ç'nin sonucunu hiçbir yerde saklamanıza gerek kalmaz - sadece onu nasıl dönüştürmek istediğinizi söyleyen bir kod yazmanız yeterlidir.


8

(Bazı işlevsel diller saf olmayan işlevlere izin verir.)

İçin tamamen işlevsel diller, gerçek dünya etkileşim Genellikle böyle fonksiyon argümanları, biri olarak yer almaktadır:

RealWorld pureScanf(RealWorld world, const char* format, ...);

Farklı dillerin, dünyayı programcıdan uzaklaştırmak için farklı stratejileri vardır. Örneğin Haskell, worldargümanı gizlemek için monadları kullanır .


Ancak, işlevsel dilin saf kısmı zaten Turing tamamlanmıştır, yani C'de yapılabilecek her şey Haskell'de de yapılabilir. Zorunlu dilden temel fark, durumları yerinde değiştirmek yerine:

int compute_sum_of_squares (int min, int max) {
  int result = 0;
  for (int i = min; i < max; ++ i)
     result += i * i;  // modify "result" in place
  return result;
}

Değiştirme bölümünü bir işlev çağrısına dahil edersiniz, genellikle döngüleri özyinelemelere dönüştürürsünüz:

int compute_sum_of_squares (int min, int max) {
  if (min >= max)
    return 0;
  else
    return min * min + compute_sum_of_squares(min + 1, max);
}

Veya sadece computeSumOfSquares min max = sum [x*x | x <- [min..max]];-)
fredoverflow

@Fred: Listeyi anlama sadece sözdizimsel bir şekerdir (ve sonra Liste monadını ayrıntılı olarak açıklamanız gerekir) Ve nasıl uyguluyorsunuz sum? Yineleme hala gereklidir.
kennytm

3

Fonksiyonel dil olabilir durumunu kaydetmek! Genellikle bunu açıkça yapmanız için sizi ya cesaretlendirir ya da zorlarlar.

Örneğin, Haskell's State Monad'a bakın .


9
Ve her ikisi de basit, genel, işlevsel araçlar açısından tanımlandığından, devletle ilgili hiçbir şey olmadığını Stateveya Monaddurumu mümkün kılan hiçbir şeyin olmadığını unutmayın . Sadece ilgili desenleri yakalarlar, böylece tekerleği yeniden icat etmek zorunda kalmazsınız.
Conal


1

haskell:

main = do no <- readLn
          print (no + 1)

Elbette, işlevsel dillerde değişkenlere bir şeyler atayabilirsiniz. Bunları değiştiremezsiniz (yani temelde tüm değişkenler işlevsel dillerdeki sabitlerdir).


@ sepp2k: neden, onları değiştirmenin ne zararı var?
Lazer

@eSKay eğer değişkenleri değiştiremezseniz hepsinin aynı olduğunu bilirsiniz. Bu, hata ayıklamayı kolaylaştırır, sizi yalnızca bir şeyi ve çok iyi yapan daha basit işlevler yapmaya zorlar. Aynı zamanda eşzamanlılıkla çalışırken çok yardımcı olur.
Henrik Hansen

9
@eSKay: İşlevsel programcılar, değişken durumun hatalar için birçok olasılık sunduğuna ve programların davranışları hakkında akıl yürütmeyi zorlaştırdığına inanıyor. Örneğin, bir işlev çağrınız varsa f(x)ve x'in değerinin ne olduğunu görmek istiyorsanız, x'in tanımlandığı yere gitmeniz yeterlidir. Eğer x değişken olsaydı, x'in tanımı ile kullanımı arasında değiştirilebilecek herhangi bir nokta olup olmadığını da düşünmeniz gerekir (eğer x yerel bir değişken değilse bu önemsiz değildir).
sepp2k

6
Değişken duruma ve yan etkilere güvenmeyenler sadece işlevsel programcılar değildir. Değişmez nesneler ve komut / sorgu ayrımı, pek çok OO programcısı tarafından iyi bir şekilde kabul edilir ve neredeyse herkes , değişken küresel değişkenlerin kötü bir fikir olduğunu düşünür. Haskell gibi diller sadece ileri fikir almak en ...
CA McCann

5
@eSKay: Mutasyonun zararlı olduğu o kadar da değil, mutasyondan kaçınmayı kabul ederseniz, modüler ve yeniden kullanılabilir bir kod yazmak çok daha kolay hale geliyor. Paylaşılan değişken durum olmadan, kodun farklı bölümleri arasındaki bağlantı açık hale gelir ve tasarımınızı anlamak ve sürdürmek çok daha kolaydır. John Hughes bunu benden daha iyi açıklıyor; Why Functional Programming Matters adlı makalesini alın .
Norman Ramsey
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.