Fonksiyonel Programlama - Değişmezlik


12

FP (özellikle F #, ancak diğer FP de Tamam) değişmez veriler ile ilgili anlamaya çalışıyorum ve devlet dolu düşünme (OOP tarzı) eski alışkanlığı kırmak. Buradaki soruya verilen yanıtın bir kısmı, OOP'ta durumsal temsillerle FP'deki değişmez olanlarla çözülen sorunların etrafındaki herhangi bir yazımı araştırmamı yineledi (Örneğin: Üreticiler ve Tüketici ile bir kuyruk). Herhangi bir düşünce veya bağlantı bekliyoruz? Şimdiden teşekkürler.

Düzenleme : Soruyu biraz daha açıklığa kavuşturmak için, nasıl değişmez yapılar (ex: kuyruk) FP birden çok iş parçacığı (ex: üretici ve tüketici) arasında aynı anda paylaşılır


Eşzamanlılık sorunlarını ele almanın bir yolu, her seferinde kuyruğun kopyalarını oluşturmaktır (biraz pahalı, ancak çalışır).
İş

Yanıtlar:


19

Bazen bu şekilde ifade edilmesine rağmen, fonksiyonel programlama¹ durum bilgisi olan hesaplamaları engellemez. Yaptığı şey, programcıyı durumu açık hale getirmeye zorlamaktır.

Örneğin, zorunlu bir kuyruk (bazı sözde dillerde) kullanarak bazı programların temel yapısını ele alalım:

q := Queue.new();
while (true) {
    if (Queue.is_empty(q)) {
        Queue.add(q, producer());
    } else {
        consumer(Queue.take(q));
    }
}

İşlevsel kuyruk veri yapısına (tek seferde bir farkla başa çıkmak için zorunlu bir dilde) karşılık gelen yapı şöyle görünecektir:

q := Queue.empty;
while (true) {
    if (q = Queue.empty) {
        q := Queue.add(q, producer());
    } else {
        (tail, element) := Queue.take(q);
        consumer(element);
        q := tail;
    }
}

Kuyruk şimdi değiştirilemediğinden, nesnenin kendisi değişmez. Bu sözde kodda, qkendisi bir değişkendir; atamaları q := Queue.add(…)ve q := tailfarklı bir nesneye işaret olun. Kuyruk işlevlerinin arabirimi değişti: her biri işlemden kaynaklanan yeni kuyruk nesnesini döndürmelidir.

Tamamen işlevsel bir dilde, yani yan etkisi olmayan bir dilde, tüm durumu açık hale getirmeniz gerekir. Üretici ve tüketici muhtemelen bir şeyler yapıyor olduğundan, durumları burada da aracının arayüzünde olmalıdır.

main_loop(q, other_state) {
    if (q = Queue.empty) {
        let (new_state, element) = producer(other_state);
        main_loop(Queue.add(q, element), new_state);
    } else {
        let (tail, element) = Queue.take(q);
        let new_state = consumer(other_state, element);
        main_loop(tail, new_state);
    }
}
main_loop(Queue.empty, initial_state)

Şimdi her devlet parçasının nasıl açıkça yönetildiğine dikkat edin. Kuyruk düzenleme işlevleri kuyruğu girdi olarak alır ve çıktı olarak yeni bir kuyruk oluşturur. Üretici ve tüketici de devletlerini geçer.

Eşzamanlı programlama çok iyi uymuyor fonksiyonel programlama, ama çok iyi uyuyor etrafında fonksiyonel programlama. Fikir, bir grup ayrı hesaplama düğümü çalıştırmak ve mesaj alışverişi yapmalarına izin vermektir. Her düğüm işlevsel bir program çalıştırır ve ileti gönderip alırken durumu değişir.

Örneğin devamı, tek bir kuyruk olduğu için belirli bir düğüm tarafından yönetiliyor. Tüketiciler bu düğüme bir eleman elde etmek için bir mesaj gönderir. Yapımcılar bu düğüme eleman eklemek için bir mesaj gönderir.

main_loop(q) =
    consumer->consume(q->take()) || q->add(producer->produce());
    main_loop(q)

Eşzamanlılık right³ alır dil “sanayileşmiş” biridir Erlang . Erlang'ı öğrenmek kesinlikle eşzamanlı programlama hakkında aydınlanmanın yoludur⁴.

Şimdi herkes yan etkisiz dillere geçiyor!

¹ Bu terim birçok anlamlara sahiptir; burada yan etkiler olmadan programlama yapmak için kullandığınızı düşünüyorum ve bu da benim kullandığım anlam.
² Örtük durum ile programlama zorunlu programlamadır ; nesne yönelimi tamamen dik bir kaygıdır.
³ Enflamatuar, biliyorum, ama demek istediğim. Paylaşılan belleğe sahip konular eşzamanlı programlamanın montaj dilidir. Mesaj geçişini anlamak çok daha kolaydır ve yan etki eksikliği, eşzamanlılığı tanıttığınız anda gerçekten parlar.
Ve bu, Erlang hayranı olmayan, başka nedenlerle gelen birinden geliyor.


2
+1 Herhalde Erlang'ın saf bir FP dili olmadığını söyleyebilsem de çok daha eksiksiz bir cevap.
Rein Henrichs

1
@ Henrichs Rein: Gerçekten. Aslında, mevcut tüm ana akım dillerden Erlang, Nesne Yönelimini en sadık şekilde uygulayan dildir.
Jörg W Mittag

2
@ Jörg Anlaştı. Yine de, saf FP ve OO'nun dik olduğu söylenebilir.
Rein Henrichs

Bu nedenle, eşzamanlı bir yazılımda değişmez bir kuyruk uygulamak için, düğümler arasında mesajların gönderilmesi ve alınması gerekir. Bekleyen mesajlar nerede saklanır?
mouviciel

@mouviciel Kuyruk öğeleri düğümün gelen ileti kuyruğunda saklanır. Bu ileti kuyruğu tesisi, dağıtılmış bir altyapının temel bir özelliğidir. Yerel eşzamanlılık için iyi çalışan ancak dağıtılmış sistemlerle çalışmayan alternatif bir altyapı tasarımı, alıcı hazır olana kadar göndereni engellemektir. Bunun her şeyi açıklamadığını, eşzamanlı programlama üzerine bir veya iki kitabın bunu tam olarak açıklayacağını anlıyorum.
Gilles 'SO- kötü olmayı bırak'

4

Bir FP dilindeki durumsal davranış, önceki durumdan yeni duruma geçiş olarak uygulanır. Örneğin, enqueue bir kuyruktan ve bir değerden, enqueued değeriyle yeni bir kuyruğa dönüşüm olacaktır. Dequeue, kuyruktan değere dönüşüm ve değeri kaldırılmış yeni bir kuyruk olur. Monadlar gibi yapılar, bu durum dönüşümünü (ve diğer hesaplama sonuçlarını) faydalı şekillerde soyutlamak için tasarlanmıştır.


3
Her ekleme / kaldırma işlemi için yeni bir kuyruksa, iki (veya daha fazla) zaman uyumsuz işlem (iş parçacığı) kuyruğu nasıl paylaşır? Sıranın yenilerini tasarlamak bir kalıp mı?
venkram

Eşzamanlılık tamamen farklı bir sorudur. Bir yorumda yeterli cevabı veremiyorum.
Rein Henrichs

2
@ Henrichs: "bir yorumda yeterli cevap veremez". Bu genellikle, yorumla ilgili sorunları çözmek için yanıtı güncellemeniz gerektiği anlamına gelir.
S.Lott

Eşzamanlılık da monadik olabilir, bkz. Haskells Control.Concurrency.STM.
alternatif

1
@ S.Lott bu durumda OP'nin yeni bir soru sorması gerektiği anlamına gelir. Eşzamanlılık değişmez veri yapıları ile ilgili olan bu soruya OT'dir.
Rein Henrichs

2

... FP'deki değişmez olanlarla OOP'taki durumsal temsillerle çözülen sorunlar (Örn: Üreticiler ve Tüketici ile bir kuyruk)

Sorunuz "XY sorunu" olarak adlandırılan sorudur. Özellikle, belirttiğiniz kavram (üreticiler ve tüketici ile kuyruk) aslında bir çözümdür ve tarif ettiğiniz gibi bir "sorun" değildir. Bu bir zorluk yaratır, çünkü doğası gereği saf olmayan bir şeyin tamamen işlevsel bir şekilde uygulanmasını istersiniz. Cevabım bir soru ile başlıyor: çözmeye çalıştığınız sorun nedir?

Birden fazla üreticinin sonuçlarını tek bir paylaşılan tüketiciye göndermesinin birçok yolu vardır. Belki de F # 'deki en belirgin çözüm, tüketiciyi bir temsilci (aka MailboxProcessor) yapmak ve üreticilere Postsonuçlarını tüketici temsilcisine kazandırmaktır. Bu dahili olarak bir kuyruk kullanır ve saf değildir (F # 'da mesaj göndermek kontrolsüz bir yan etkidir, safsızlıktır).

Bununla birlikte, altta yatan sorunun daha çok paralel programlamanın dağılım-toplama modeli gibi olması muhtemeldir. Bu sorunu çözmek için bir dizi girdi değeri oluşturabilir ve ardından Array.Parallel.mapbunların üzerinde bir seri kullanarak sonuçları toplayabilirsiniz Array.reduce. Alternatif olarak, PSeqsekans elemanlarını paralel olarak işlemek için modülden fonksiyonları kullanabilirsiniz .

Ayrıca, durumsal düşüncede yanlış bir şey olmadığını vurgulamalıyım. Saflığın avantajları vardır, ancak kesinlikle her derde deva değildir ve eksikliklerinden de haberdar olmalısınız. Gerçekten de, bu yüzden F # saf bir işlevsel dil değildir: bu yüzden tercih edildiklerinde safsızlıkları kullanabilirsiniz.


1

Clojure, eşzamanlılıkla yakından ilişkili olan çok iyi düşünülmüş bir devlet ve kimlik kavramına sahiptir. Değişmezlik önemli bir rol oynar, Clojure'daki tüm değerler değişmezdir ve referanslarla erişilebilir. Referanslar basit göstergelerden daha fazlasıdır. Değere erişimi yönetiyorlar ve farklı semantiklere sahip birden fazla türü var. Bir referans, yeni (değişmez) bir değere işaret edecek şekilde modifiye edilebilir ve bu tür bir değişikliğin atomik olması garanti edilir. Ancak, değişiklikten sonra diğer tüm evreler hala en azından referansa erişinceye kadar orijinal değer üzerinde çalışır.

Clojure'da devlet ve kimlik hakkında mükemmel bir makale okumanızı şiddetle tavsiye ederim , detayları yapabileceğimden çok daha iyi açıklıyor.

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.