Yan etkilerin doğal bir fenomen olduğunu hissediyorum. Ama fonksiyonel dillerde tabu gibi bir şey. Sebepler neler?
Benim sorum işlevsel programlama stiline özgüdür. Tüm programlama dilleri / paradigmaları değil.
Yan etkilerin doğal bir fenomen olduğunu hissediyorum. Ama fonksiyonel dillerde tabu gibi bir şey. Sebepler neler?
Benim sorum işlevsel programlama stiline özgüdür. Tüm programlama dilleri / paradigmaları değil.
Yanıtlar:
İşlevlerinizi / yöntemlerinizi yan etkiler olmadan yazma - bu yüzden bunlar saf işlevlerdir - programınızın doğruluğu hakkında düşünmeyi kolaylaştırır.
Ayrıca, yeni davranışlar oluşturmak için bu fonksiyonları oluşturmayı kolaylaştırır.
Ayrıca, derleyicinin örneğin işlevlerin sonuçlarını hafızada tutabileceği veya Common Subexpression Elimination özelliğini kullanabildiği bazı optimizasyonları mümkün kılar.
Düzenleme: Benjol'un isteği üzerine: Eyaletinizin çoğu yığında depolandığından (Jonas'ın burada çağırdığı gibi veri akışı, kontrol akışı değil ), hesaplamanızın bağımsız olan bölümlerinin yürütülmesini paralelleştirebilir veya başka şekilde yeniden sıralayabilirsiniz. herbiri. Birini diğerine girdi sağlamadığı için bu bağımsız parçaları kolayca bulabilirsiniz.
Yığını geri almanıza ve bilgisayar işlemine devam etmenize izin veren (Smalltalk gibi) hata ayıklayıcıların bulunduğu ortamlarda, saf işlevlere sahip olmak, önceki durumların denetim için uygun olması nedeniyle bir değerin nasıl değiştiğini kolayca görebileceğiniz anlamına gelir. Yüksek mutasyonlu bir hesaplamada, yapınıza veya algoritmanıza açıkça eylemler / geri alma eylemleri eklemiyorsanız, hesaplamanın geçmişini göremezsiniz. (Bu, ilk paragrafa bağlanır: saf işlevler yazmak , programınızın doğruluğunu denetlemeyi kolaylaştırır .)
İşlevsel programlama hakkında bir makaleden :
Uygulamada, uygulamaların bazı yan etkileri olması gerekir. Fonksiyonel programlama diline Haskell'in katkısı olan Simon Peyton-Jones şunları söyledi: “Sonunda, herhangi bir programın durumu manipüle etmesi gerekir. Herhangi bir kara kutu ne olursa olsun yan etkisi olmayan bir program. Bu kutu daha da ısınır. " ( http://oscon.blip.tv/file/324976 ) Anahtar, yan etkileri sınırlamak, net bir şekilde tanımlamak ve kod boyunca saçılmasından kaçınmaktır.
Yanlış anladınız, fonksiyonel programlama, programların anlaşılmasını ve optimize edilmesini kolaylaştırmak için sınırlı yan etkileri teşvik ediyor. Haskell bile dosyalara yazmanıza izin verir.
Temel olarak söylediğim şudur ki, işlevsel programcılar yan etkilerin kötü olduğunu düşünmezler, yalnızca yan etki kullanımını sınırlamanın iyi olduğunu düşünürler. Bunun kadar basit bir ayrım gibi görünebileceğini biliyorum ama her şeyi değiştiriyor.
readFile
yapmak bir dizi eylem tanımlamaktır. Bu dizi, fonksiyonel olarak saf ve ne yapılacağını açıklayan soyut bir ağaç gibidir. gerçek kirli yan etkiler daha sonra çalışma zamanı tarafından gerçekleştirilir.
Birkaç not:
Yan etkisi olmayan fonksiyonlar önemsiz bir şekilde yürütülürken, yan etkisi olan fonksiyonlar tipik olarak bir tür senkronizasyon gerektirir.
Yan etkisi olmayan işlevler daha agresif bir optimizasyona izin verir (örneğin sonuç önbelleği kullanarak şeffaf olarak), çünkü doğru sonucu aldığımız sürece, işlevin gerçekten gerçekleştirilip gerçekleştirilmediği önemli değildir.
deterministic
yan etkisi olmayan fonksiyonlar için bir madde sunar , bu yüzden gereğinden fazla çalıştırılmazlar.
deterministic
Fıkra sadece bu nasıl karşılaştırılabilir bir belirleyici fonksiyon olan derleyici bildiren bir anahtar kelimedir final
Java anahtar değişken değiştiremezsiniz derleyici söyler.
Öncelikle şimdi işlevsel kodda çalışıyorum ve bu açıdan gözle görülür derecede açık görünüyor. Yan etkiler , kod okumaya ve anlamaya çalışan programcılar üzerinde büyük bir zihinsel yük oluşturur . Bu yükü bir süre için serbest kalana kadar fark etmiyorsunuz, sonra aniden yan etkileri olan kodu tekrar okumak zorundasınız.
Bu basit örneği düşünün:
val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.
// Code you are troubleshooting
// What's the expected value of foo here?
İşlevsel bir dilde hala 42 olduğunu biliyorum . foo
Aradaki koda bakmak , daha az anlamak veya aradığı fonksiyonların uygulamalarına bakmak bile zorunda değilim .
Aynı anda eşzamanlılık ve paralelleştirme ve optimizasyon ile ilgili her şey güzel, ama bilgisayar bilimcilerinin broşürde kullandığı şey bu. Değişkeninizi kimin değiştirdiğini merak etmeme gerek yok ve günlük pratikte gerçekten ne zevk aldığım ne.
Hiçbir dilden az kişi yan etkilere neden olmayı imkansız kılar. Tamamen yan etkisiz olan dillerin kullanımı, çok sınırlı bir kapasiteden başka, yasaklanması zor (imkansız).
Neden yan etkiler kötü kabul edilir?
Çünkü bir programın tam olarak ne yaptığını düşünmeyi ve yapmasını beklediğiniz şeyi yaptığını kanıtlamayı çok daha zorlaştırıyorlar.
Çok yüksek bir düzeyde, tüm 3 katmanlı bir web sitesini yalnızca kara kutu testi ile test etmeyi hayal edin. Tabii, ölçeğe bağlı olarak yapılabilir. Ama kesinlikle çok fazla çoğaltma oluyor. Orada eğer olduğunu bir hata (yani bir yan etkisi ile ilgilidir) hata teşhis ve sabit ve düzeltme test ortamına dağıtıldığı kadar, o zaman potansiyel olarak başka testler için tüm sistemi kırılabilir.
Yararları
Şimdi, aşağı ölçeklendir. Yan etkili serbest kod yazma konusunda oldukça başarılı olsaydınız, varolan bazı kodların ne kadar hızlı olduğunu düşünüyorsunuz? Birim testlerini ne kadar hızlı yazabilirsin? Nasıl emin hiçbir yan etkileri ile kod hata içermeyen garantili olduğunu ve kullanıcıların herhangi bir hata maruz kaldıkları sınırlayabilir hissederiz mi var?
Kodun yan etkisi yoksa, derleyici de gerçekleştirebileceği ek optimizasyonlara sahip olabilir. Bu optimizasyonları uygulamak çok daha kolay olabilir. Yan etkili serbest kod için bir optimizasyonun kavramsallaştırılması bile daha kolay olabilir; bu, derleyici satıcınızın yan etkilerde kodda imkansız olan optimizasyonları uygulayabileceği anlamına gelir.
Aynı anda eşzamanlılık, kodun yan etkileri olmadığında uygulamak, otomatik olarak oluşturmak ve optimize etmek için son derece basittir. Bunun nedeni, tüm parçaların herhangi bir sırayla güvenli bir şekilde değerlendirilebilmesidir. Programcıların aynı anda yüksek kod yazmasına izin vermek, Computer Science'ın uğraşması gereken bir sonraki büyük zorluk ve Moore Yasasına karşı kalan az sayıdaki çitlerden biri olarak kabul edilir .
Yan etkiler, kodunuzda daha sonra ya sizin tarafınızdan ya da bazı şüphesiz çalışanlar tarafından ele alınması gereken “sızıntılar” gibidir.
İşlevsel diller, kodu daha az bağlama bağımlı ve daha modüler yapmanın bir yolu olarak durum değişkenlerini ve değişken verileri önler. Modülerlik, bir geliştiricinin çalışmasının diğerinin çalışmasını etkilememesini / baltalamasını önler.
Ekip büyüklüğüyle gelişme oranını ölçeklendirmek, günümüzde yazılım geliştirmenin “kutsal kâsesi” dir. Diğer programcılar ile çalışırken, birkaç şey modülerlik kadar önemlidir. En basit mantıksal yan etkiler bile işbirliğini son derece zorlaştırıyor.
IMHO, bu oldukça ikiyüzlü. Kimse yan etkilerden hoşlanmaz, ama herkes onlara ihtiyaç duyar.
Yan etkiler konusunda bu kadar tehlikeli olan şey, bir işlev çağırırsanız, bu durumun yalnızca bir dahaki sefere çağrıldığında işlevin davranış biçiminde değil, diğer işlevlerde de bu etkisinin olması muhtemeldir. Bu nedenle yan etkiler öngörülemeyen davranış ve önemsiz bağımlılıklar ortaya çıkarmaktadır.
OO ve fonksiyonel gibi programlama paradigmaları bu sorunu ele alır. OO, endişelerin ayrılmasını empoze ederek sorunu azaltır. Bu, çok fazla değişken veri içeren uygulama durumunun, her biri yalnızca kendi durumunu korumaktan sorumlu olan nesneler içine alındığı anlamına gelir. Bu şekilde bağımlılık riski azalır ve problemler çok daha izole edilir ve izlenmesi kolaydır.
İşlevsel programlama, uygulama durumunun programlayıcı açısından basitçe değiştirilemediği çok daha radikal bir yaklaşım izlemektedir. Bu iyi bir fikir, ancak dili kendi başına işe yaramaz hale getiriyor. Neden? Çünkü HERHANGİ BİR G / Ç işleminin yan etkileri vardır. Herhangi bir giriş akışından okuduğunuzda, uygulama durumunun değişmesi muhtemeldir, çünkü aynı işlevi bir daha başlattığınızda, sonuç farklı olabilir. Farklı verileri okuyorsunuz ya da - bir olasılık da - işlem başarısız olabilir. Aynısı çıktı için de geçerlidir. Çıktı bile yan etkileri olan bir işlemdir. Bu, bugünlerde sıkça fark ettiğiniz bir şey değildir, ancak çıktınız için yalnızca 20K'nın olduğunu ve daha fazla çıktı alırsanız uygulamanızın disk alanı yetersiz olduğundan veya başka bir şey yapılmadığından çöktüğünü hayal edin.
Yani evet, yan etkiler bir programcının bakış açısından tehlikeli ve tehlikelidir. Hataların çoğu, uygulama durumunun belirli bölümlerinin neredeyse belirsiz ve çoğu zaman gereksiz yan etkiler yoluyla neredeyse belirsiz bir şekilde kilitlenmesinden kaynaklanmaktadır. Bir kullanıcının bakış açısından, yan etkiler, bir bilgisayarı kullanma noktasıdır. İçeride ne olduğu ve nasıl organize olduğu umurunda değil. Bir şey yaparlar ve bilgisayarın buna göre değişmesini beklerler.
Herhangi bir yan etki, test ederken göz önünde bulundurulması gereken ekstra giriş / çıkış parametreleri sunar.
Bu, kod doğrulamasının çok daha karmaşık olmasını sağlar çünkü ortam sadece onaylanan kodla sınırlı kalamaz, ancak çevredeki ortamın bir kısmını veya tamamını getirmelidir (güncellenen global, o kodda yaşar, bu da buna bağlıdır) tam bir Java EE sunucusu içinde yaşamaya bağlı olan kod ....)
Yan etkilerden kaçınmaya çalışırken, kodu çalıştırmak için gereken dışsallık miktarını sınırlarsınız.
Tecrübelerime göre Nesne Yönelimli programlamadaki iyi tasarım, yan etkileri olan fonksiyonların kullanımını zorunlu kılar.
Örneğin, basit bir UI masaüstü uygulaması edinin. Öbek üzerinde, programımın etki alanı modelinin geçerli durumunu gösteren bir nesne grafiği olan çalışan bir programım olabilir. Mesajlar, bu grafikteki nesnelere ulaşır (örneğin, UI katman denetleyicisinden çağrılan yöntem çağrıları yoluyla). Öbek üzerindeki nesne grafiği (etki alanı modeli) iletilere yanıt olarak değiştirilir. Modelin gözlemcileri herhangi bir değişiklik, UI ve belki de diğer kaynaklar hakkında bilgilendirilir.
Kötü olmaktan ötürü, bu yığın modifiye edici ve ekran modifiye edici yan etkilerin doğru düzenlenmesi, OO tasarımının merkezindedir (bu durumda MVC modeli).
Tabii ki, bu yöntemlerin keyfi yan etkileri olması gerektiği anlamına gelmez. Ve yan etki göstermeyen işlevler, kodunuzun okunabilirliğini ve bazen performansını artıracak bir yere sahiptir.
Yukarıdaki soruların işaret ettiği gibi, işlevsel diller kodun yan etkilere sahip olmasını engellemez ; çünkü bize belirli bir kod parçasında ve hangi yan etkilerin gerçekleşebileceğini yönetmek için araçlar sağlar.
Bu çok ilginç sonuçlara yol açtı. Öncelikle ve en açık şekilde, zaten açıklanmış olan, yan etkisi olmayan kodlarla yapabileceğiniz birçok şey var. Ancak, yan etkileri olan kodlarla çalışırken bile yapabileceğimiz başka şeyler var:
Karmaşık kod tabanlarında, yan etkilerin karmaşık etkileşimleri, hakkında düşünmem gereken en zor şeydir. Kişisel olarak sadece beynimin çalıştığı şekilde konuşabiliyorum. Yan etkiler, ısrarcı durumlar ve mutasyona giren girdiler ve diğerleri, her bir işlevde sadece “ne” olduğunu değil, doğrulukla ilgili şeylerin “ne zaman” ve “nerede” olduğu hakkında düşünmeme neden oluyor.
Sadece "neye" odaklanamıyorum. Arayanlar hala yanlış zamanda, yanlış iş parçacığından, yanlış işyerinde yanlış zamanda çağırarak kötüye kullanabileceğinden, yan etkilere neden olan bir işlevi iyice test ettikten sonra tamamlayamam. sipariş. Bu arada, hiçbir yan etkiye neden olmayan ve sadece bir giriş verilen (girişe dokunmadan) yeni bir çıkış döndüren bir fonksiyonun, bu şekilde yanlış kullanılması oldukça imkansızdır.
Ama ben pragmatik bir türüm, sanırım ya da en azından olmaya çalışıyorum ve kodumuzun doğruluğunu düşünmek için en az tüm yan etkileri en bariz şekilde damgalamak zorunda olduğumuzu sanmıyorum (en azından) Bunu C) gibi dillerde yapmak çok zor bulur. Doğruluk konusunda mantıklı olmanın çok zor bulduğu nokta, karmaşık kontrol akışları ve yan etkiler kombinasyonuna sahip olduğumuz zamandır.
Karmaşık kontrol akışları bana doğada grafik benzeri, genellikle özyinelemeli veya özyinelemeli (örneğin, olayları doğrudan özyinelemeli olarak aramayan ancak doğada özyinelemeli "olan), belki bazı şeyleri yapan akışlardır. gerçek bir bağlantılı grafik yapısını geçme sürecinde ya da bizi kodbazın her türlü farklı parçasına ve hepsi farklı yan etkilere neden olacak şekilde işlemek için eklektik bir olaylar karışımı içeren homojen olmayan bir olay kuyruğunu işlerken. Nihayetinde kodun sonunda çıkacağınız tüm yerleri çizmeye çalıştıysanız, karmaşık bir grafiğe benzeyebilirdi ve potansiyel olarak asla beklemeyeceğiniz grafikteki düğümlerle, o anda orada bulunmuş olacaktı ve hepsinin aynı olduğu düşünülürse yan etkilere neden olmak,
İşlevsel diller son derece karmaşık ve özyinelemeli kontrol akışlarına sahip olabilir, ancak sonuçta doğruluk açısından anlaşılması çok kolaydır çünkü süreçte her türlü eklektik yan etki yoktur. Sadece karmaşık kontrol akışları, neler olup bittiğini ve her zaman doğru olanı yapıp yapmayacağını anlamak için baştan çıkarıcı bulduğu eklektik yan etkilerle karşılaştığında.
Bu yüzden, bu davaları aldığımda, bu kodun doğruluğu konusunda kendinden emin hissetmek imkansız olmasa da, beklenmedik bir şey açmadan bu kod üzerinde değişiklikler yapabileceğim konusunda kendime güvendiğim için çok zor buluyorum. Bu yüzden benim için çözüm ya kontrol akışını basitleştirmek ya da yan etkileri en aza indirgemek / birleştirmek. (Birleştirerek, demek istediğim, iki ya da üç ya da bir değil. düzine). Simpleton beynimin var olan kodun doğruluğu ve tanıttığım değişikliklerin doğruluğu konusunda kendinden emin hissetmesini sağlamak için bu iki şeyden birine ihtiyacım var. Eğer yan etkiler kontrol akışı ile birlikte aynı ve basit ise, yan etkileri ortaya çıkaran kodun doğruluğundan emin olmak oldukça kolaydır:
for each pixel in an image:
make it red
Bu tür bir kodun doğruluğunu düşünmek oldukça kolaydır, ancak temel olarak yan etkiler çok tekdüze olduğundan ve kontrol akışı çok basit olduğu için. Ama diyelim ki böyle bir kodumuz var:
for each vertex to remove in a mesh:
start removing vertex from connected edges():
start removing connected edges from connected faces():
rebuild connected faces excluding edges to remove():
if face has less than 3 edges:
remove face
remove edge
remove vertex
O zaman bu, gülünç derecede basitleştirilmiş sahte koddur ve tipik olarak çok daha fazla fonksiyon ve iç içe döngüler ve devam etmesi gereken çok daha fazla şey içerir (çoklu doku haritalarını, kemik ağırlıklarını, seçim durumlarını vb. Güncellemek), ancak sözde kodu bile zorlaştırır. karmaşık grafik benzeri kontrol akışının etkileşimi ve devam eden yan etkiler nedeniyle doğruluk nedeni. Bu yüzden bunu basitleştirmek için bir strateji, işlemi ertelemek ve bir seferde sadece bir tür yan etkiye odaklanmaktır:
for each vertex to remove:
mark connected edges
for each marked edge:
mark connected faces
for each marked face:
remove marked edges from face
if num_edges < 3:
remove face
for each marked edge:
remove edge
for each vertex to remove:
remove vertex
... bir sadeleştirme yinelemesi olarak bu etkiye bir şey. Bu, kesinlikle bir hesaplama maliyetine neden olan veriyi defalarca ilettiğimiz anlamına gelir, ancak çoğu zaman bu sonuç kodunu daha kolay okuyabildiğimizi, artık yan etkiler ve kontrol akışlarının bu tek biçimli ve daha basit doğada gerçekleştiğini görüyoruz. Ayrıca, her ilmek bağlanmış grafiğin üzerinden geçmek ve ilerlerken yan etkilere neden olmaktan ötürü önbellek dostu hale getirilebilir (örneğin: ertelenen geçişleri sıralı sırayla yapabilmemiz için geçilmesi gerekenleri işaretlemek için paralel bir bit seti kullanın) bit maskesi ve FFS kullanarak). Fakat en önemlisi, ikinci versiyonun hatasızlık ve hatalara neden olmadan değişiklik anlamında sebep olması çok daha kolay. Böylece'
Ve sonuçta, bir noktada meydana gelmek için yan etkilere ihtiyacımız var, yoksa sadece gidecek hiçbir yerde veri çıkaran işlevlere sahip değiliz. Çoğunlukla bir şeyi bir dosyaya kaydetmemiz, bir ekrana bir şey göstermemiz, verileri bir soket üzerinden göndermemiz, bu türden bir şey ve bunların hepsi yan etkilerdir. Ancak devam eden gereksiz yan etkilerin sayısını azaltabilir ve kontrol akışları çok karmaşık olduğunda ortaya çıkan yan etkilerin sayısını azaltabiliriz ve eğer yaparsak hataları önlemenin çok daha kolay olacağını düşünüyorum.
Bu kötü değil. Kanımca, yan etkileri olan ve olmayan iki fonksiyon tipini ayırt etmek gerekir. Yan etkisi olmayan işlev: - her zaman aynı argümanlarla aynı olur, yani herhangi bir argüman içermeyen bu işlev bir anlam ifade etmez. - Bu aynı zamanda, bu gibi bazı işlevlerin adlandırılma sırasının hiçbir rol oynamaması anlamına gelir - başka bir kod olmadan sadece tek başına (!) Çalıştırılabilir ve hata ayıklanabilir. Ve şimdi, lol, bak JUnit'in ne yaptığı. Yan etkileri olan bir fonksiyon: - otomatik olarak vurgulanabilen bir çeşit "sızıntı" vardır - genellikle yan etkilerden kaynaklanan hataların ayıklanması ve aranması çok önemlidir. - Yan etkileri olan herhangi bir fonksiyon, yan etkileri olmayan, ayrıca otomatik olarak ayrılabilen "bir parçasına" sahiptir. Yani kötülük bu yan etkilerdir.