Saf fonksiyonel vs söyle, sorma?


14

"Bir işlev için ideal argüman sayısı sıfırdır" oldukça yanlıştır. İdeal argüman sayısı, fonksiyonunuzun yan etkisiz olmasını sağlamak için gereken sayıdır. Bundan daha az ve gereksiz yere işlevlerinizin saf olmamasına neden olursunuz, böylece sizi başarı çukurundan uzaklaşmaya ve acı eğimine tırmanmaya zorlarsınız. Bazen "Bob Amca" onun tavsiyesi ile yerinde. Bazen olağanüstü derecede yanlıştır. Sıfır argüman tavsiyesi, ikincisinin bir örneğidir

( Kaynak: @David Arno'nun bu sitedeki başka bir sorunun altındaki yorumu )

Yorum, 133 upvotes muhteşem bir miktar kazandı, bu yüzden onun değerine biraz daha dikkat etmek istiyorum.

Bildiğim kadarıyla, programlamada iki ayrı yol var: saf fonksiyonel programlama (bu yorumun neyi teşvik ettiği) ve söylemeyin, sormayın (bu web sitesinde de zaman zaman tavsiye edilir). AFAIK bu iki ilke birbirinin zıtlığına yakın olarak temel olarak uyumsuzdur: saf fonksiyonel "sadece geri dönüş değerleri, hiçbir yan etkisi yoktur" olarak özetlenebilir, söyleme, sorma "hiçbir şeyi iade etme, msgstr "sadece yan etkileri var" + + msgid ". Ayrıca, biraz şaşırdım çünkü söyle, sorma OO paradigmasının çekirdeği olarak düşünülürken saf funcitonlar fonksiyonel paradigmanın çekirdeği olarak kabul edildi - şimdi OO'da önerilen saf fonksiyonları görüyorum!

Sanırım geliştiriciler muhtemelen bu paradigmalardan birini seçmeli ve ona bağlı kalmalı mı? İtiraf etmeliyim ki kendimi de asla takip etmeye getiremedim. Çoğu zaman bir değer döndürmek benim için uygun görünüyor ve sadece yan etkilerle elde etmek istediğim şeyi nasıl elde edebileceğimi gerçekten göremiyorum. Çoğu zaman yan etkilere sahip olmak benim için uygun görünüyor ve elde etmek istediklerimi sadece değerler döndürerek nasıl başarabildiğimi gerçekten göremiyorum. Ayrıca, çoğu zaman (sanırım bu korkunç) Her ikisini de yapan yöntemlerim var.

Ancak, bu 133 upvotes, şu anda saf fonksiyonel programlamanın "kazanmak" olduğunu düşünüyorum, çünkü anlatmak daha üstün bir fikir birliği haline gelir, sormayın. Bu doğru mu?

Bu nedenle, bu antipattern-basmış oyun örneği üzerinde yapmaya çalışıyorum : Eğer saf fonksiyonel paradigmaya uygun hale getirmek istersem - NASIL ?!

Bir savaş devletine sahip olmak benim için makul görünüyor. Bu sıra tabanlı bir oyun olduğu için savaş durumlarını sözlükte tutuyorum (çok oyunculu - aynı anda birçok oyuncu tarafından oynanan birçok savaş olabilir). Bir oyuncu ne zaman dönerse, (a) durumu buna göre değiştiren ve (b) JSON'a serileştirilen ve temel olarak sadece yazı tahtası. Sanırım bu, hem HEM prensiplerinin hem de aynı zamanda çirkin bir ihlalidir.

Tamam - gerçekten isteseydim, yerinde bir değişiklik yapmak yerine bir savaş devletini İADE yapabilirim. Fakat! O zaman savaş durumundaki her şeyi, yerine değiştirmek yerine tamamen yeni bir durumu geri döndürmek için gereksiz yere kopyalamak zorunda kalacak mıyım?

Belki de hamle bir saldırı ise, güncellenmiş HP karakterleri iade edebilir miyim? Sorun şu ki, bu kadar basit değil: oyun kuralları, bir hamle, oyuncunun HP'sinin bir kısmını kaldırmaktan çok daha fazla etkiye sahip olabilir ve genellikle daha fazla etkiye sahip olacaktır. Örneğin, karakterler arasındaki mesafeyi artırabilir, özel efektler vb. Uygulayabilir.

Durumu yerinde değiştirip güncellemeleri iade etmem çok daha basit görünüyor ...

Ancak deneyimli bir mühendis bununla nasıl başa çıkabilir?


9
Herhangi bir paradigmayı izlemek başarısızlığın kesin bir yoludur. Politika asla istihbarattan vazgeçmemelidir. Bir sorunun çözümü, problem çözme hakkındaki dini inançlarınıza değil, soruna bağlı olmalıdır.
John Douma

1
Burada daha önce söylediğim bir şey hakkında hiç bir soru sormadım. Onur duydum. :)
David Arno

Yanıtlar:


14

Çoğu programlama aforizması gibi, "söyle, sorma" kısalık elde etmek için açıklığı feda eder. Tüm soran karşı tavsiye amaçlanan en düşmez sonuçlarının bir hesaplama, bu soran karşı tavsiye edilir girişlerin bir hesaplama. "Almayın, sonra hesaplayın, sonra ayarlayın, ama bir hesaplamadan bir değer döndürmek sorun değil," özlü değil.

İnsanların bir alıcı çağırması, üzerinde bazı hesaplamalar yapması ve ardından sonuçla birlikte bir ayarlayıcı çağırması için oldukça yaygındı. Bu, hesaplamanızın aslında alıcı olarak adlandırdığınız sınıfa ait olduğu açık bir işarettir. "Söyle, sorma" insanlara bu anti-model için uyanık olmalarını hatırlatmak için yaratıldı ve o kadar iyi çalıştı ki, şimdi bazı insanlar bu kısmın açık olduğunu düşünüyorlar ve diğer türden elemek. Ancak aforizma bu duruma sadece yararlı bir şekilde uygulanır.

Saf işlevsel programlar hiçbir zaman tam olarak bu anti-kalıptan zarar görmemiştir, çünkü bu tarzda ayarlayıcıların olmaması basit bir nedendir. Ancak, aynı anlamdaki farklı anlamsal soyutlama düzeylerini karıştırmama konusunda daha genel (ve daha zor görülen) problem her paradigma için geçerlidir.


"Söyle, sorma" ifadesini doğru bir şekilde açıkladığınız için teşekkür ederiz.
user949300

13

Hem Bob Amca hem de David Arno (sahip olduğunuz alıntı yazarı) yazdıklarından öğrenebileceğimiz önemli dersler var. Sanırım dersi öğrenmeye ve sonra bunun sizin ve projeniz için gerçekten ne anlama geldiğini tahmin etmeye değer.

İlk: Bob Amca'nın Dersi

Bob Amca, fonksiyonunuzda / yönteminizde ne kadar çok argümanınız varsa, onu kullanan geliştiricilerin de o kadar çok geliştirici olduğunu anlamak zorundadır. Bu bilişsel yük ücretsiz olarak gelmez ve eğer argümanların sırası vb. İle tutarlı değilseniz bilişsel yük sadece artar.

Bu insan olmanın bir gerçeği. Bence Bob Amca'nın Temiz Kod kitabındaki en önemli hata, "Bir fonksiyon için ideal argüman sayısı sıfırdır" ifadesidir . Minimalizm olmadıkça harika. Tıpkı Matematik'teki sınırlarınıza asla ulaşmadığınız gibi, asla "ideal" koda ulaşmayacaksınız - ne de yapmalısınız.

Albert Einstein'ın dediği gibi, “Her şey olabildiğince basit olmalı, ancak daha basit olmamalı”.

İkincisi: David Arno'nun Dersi

David Arno'yu geliştirmenin yolu, nesne yönelimli olmaktan daha işlevsel stil geliştirmedir . Ancak, işlevsel kod geleneksel nesne yönelimli programlamaya göre çok daha iyi ölçeklendirilir. Neden? Kilitleme yüzünden. Bir nesnede herhangi bir zaman durumu değişebilirse, yarış koşulları veya çekişmeyi kilitleme riskiyle karşı karşıya kalırsınız.

Simülasyonlarda ve diğer sunucu tarafı uygulamalarında kullanılan yüksek eşzamanlı sistemler yazılan fonksiyonel model harikalar yaratıyor. Yaklaşımın yaptığı gelişmeleri kanıtlayabilirim. Bununla birlikte, farklı gereksinimler ve deyimler ile çok farklı bir gelişim tarzıdır.

Kalkınma bir dizi değiş tokuş

Başvurunuzu bizden daha iyi biliyorsunuz. İşlevsel stil programlama ile birlikte gelen ölçeklenebilirliğe ihtiyacınız olmayabilir. Yukarıda listelenen iki ideal arasında bir dünya var. Yüksek verim ve gülünç paralellikle başa çıkması gereken sistemlerle uğraşanlarımız fonksiyonel programlama idealine doğru yönelecektir.

Bununla birlikte, bir yönteme iletmeniz gereken bilgi kümesini tutmak için veri nesnelerini kullanabilirsiniz. Bu, David Arno'nun ele aldığı fonksiyonel ideali desteklerken Bob Amca'nın ele aldığı bilişsel yük problemine yardımcı olur.

Hem sınırlı paralellik gerektiren masaüstü sistemlerinde hem de yüksek verimli simülasyon yazılımlarında çalıştım. Çok farklı ihtiyaçları var. Bildiğiniz veri gizleme kavramı etrafında tasarlanmış iyi yazılmış nesne yönelimli kodu takdir edebilirsiniz. Birkaç uygulama için çalışır. Ancak, hepsi için işe yaramaz.

Kim haklı? David, bu durumda Bob Amca'dan daha haklıdır. Ancak, burada vurgulamak istediğim temel nokta, bir yöntemin mantıklı olduğu kadar çok argümana sahip olması gerektiğidir.


Paralellik var. Farklı savaşlar paralel olarak işlenebilir. Ancak evet: işlenirken tek bir savaşın kilitlenmesi gerekiyor.
gaazkam

Evet, demek istediğim, okuyucuların (analojinizdeki orak makineleri) (ekici) yazılarından arınmış olacaktı. Bununla birlikte, geçmişte yazdığım bazı şeylere bakmaya geri döndüm ve ya bir şeyleri yeniden öğrendim ya da eski kendime katılmıyorum. Hepimiz öğreniyoruz ve gelişiyoruz ve bu, öğrendiğiniz bir şeyi nasıl ve uygularsanız daima akıl yürütmeniz için bir numaralı nedendir.
Berin Loritsch

8

Tamam - gerçekten isteseydim, yerinde bir değişiklik yapmak yerine bir savaş devletini İADE yapabilirim.

Evet, fikir bu.

Daha sonra, tamamen yeni bir eyaleti değiştirmek yerine savaş eyaletindeki her şeyi kopyalamak zorunda kalacak mıyım?

Hayır. "Savaş durumunuz", yapı taşları olarak diğer değiştirilemez veri yapılarını içeren değişmez bir veri yapısı olarak modellenebilir, belki de değişmez veri yapılarının bazı hiyerarşilerinde yuvalanmış olabilir.

Dolayısıyla savaş durumunun bir turda değiştirilmesi gerekmeyen kısımları ve diğerleri de değiştirilmesi gereken kısımları olabilir. Değişmeyen parçaların kopyalanması gerekmez, çünkü değişmezler, yan etkilere girme riski olmadan bu parçalara sadece bir referans kopyalamak gerekir. Çöp toplanan dil ortamlarında en iyi sonucu verir.

Google "Verimli Bağlanabilir Veri Yapıları" için ve bunun genel olarak nasıl çalıştığına dair bazı referanslar bulacaksınız.

Durumu yerinde değiştirip güncellemeleri iade etmem çok daha basit görünüyor.

Bazı problemler için, bu gerçekten daha basit olabilir. Oyun durumunun büyük bir bölümü bir turdan diğerine değiştiği için oyunlar ve yuvarlak tabanlı simülasyonlar bu kategoriye girebilir. Bununla birlikte, gerçekten "daha basit" olanın algılanması bir dereceye kadar özneldir ve aynı zamanda insanların alışık oldukları şeye de bağlıdır.


8

Yorumun yazarı olarak, sanırım burada açıklığa kavuşturmalıyım, tabii ki yorumumun sunduğu basitleştirilmiş versiyondan daha fazlası var.

AFAIK bu iki ilke birbirinin zıtlığına yakın olarak temel olarak uyumsuzdur: saf fonksiyonel "sadece geri dönüş değerleri, hiçbir yan etkisi yoktur" olarak özetlenebilir, söyleme, sorma "hiçbir şeyi iade etme, msgstr "sadece yan etkileri var" + + msgid ".

Dürüst olmak gerekirse, bunu "anlat, sorma" teriminin gerçekten garip bir kullanımı olarak görüyorum. Birkaç yıl önce Martin Fowler'ın konu hakkında söylediklerini okudum, bu aydınlatıcıydı . Garip bulmamın nedeni, "söyle sorma" nın kafamdaki bağımlılık enjeksiyonu ile eşanlamlı olması ve bağımlılığın en saf şekli, bir fonksiyonun parametreleri aracılığıyla ihtiyaç duyduğu her şeyi geçirmesidir.

Ancak "söyleme sorma" için kullandığım anlamın, Fowler'in OO odaklı tanımını alıp daha paradigmayı agnostik hale getirmesinden kaynaklandığı anlaşılıyor. Bu süreçte, kavramı mantıklı sonuçlarına götürdüğüne inanıyorum.

Basit başlangıçlara geri dönelim. "Mantık topakları" mız var (prosedürler) ve küresel verilerimiz var. Yordamlar, erişmek için bu verileri doğrudan okur. Basit bir "sor" senaryomuz var.

Biraz ileri sar. Şimdi nesnelerimiz ve yöntemlerimiz var. Bu verinin artık küresel olması gerekmiyor, kurucu aracılığıyla aktarılabilir ve nesnenin içinde bulunabilir. Ve sonra bu verilere göre hareket eden yöntemlerimiz var. Şimdi Fowler'ın açıkladığı gibi "anlat, sorma" diyoruz. Nesneye verileri söylenir. Bu yöntemlerin artık verilerini küresel kapsamdan istemesi gerekmiyor. Ama işte sürtünme: bu hala doğru değil "söyle, sorma" çünkü bu yöntemler hala nesne kapsamını sormak zorunda. Bu daha çok bir "söyle sonra sor" senaryosu hissediyorum.

Bu yüzden modern güne doğru rüzgar, "OO tamamen aşağı" yaklaşımını dökün ve fonksiyonel programlamadan bazı ilkeleri ödünç alın. Şimdi bir yöntem çağrıldığında, tüm veriler parametreleri aracılığıyla sağlanır. "Kural nedir, sadece kodu karmaşık hale getiren nedir?" Ve evet, parametrelerin içinden geçerek, nesnenin kapsamı üzerinden erişilebilen veriler koda karmaşıklık katar. Ancak bu verileri küresel olarak erişilebilir hale getirmek yerine bir nesnede saklamak da karmaşıklık getirir. Ancak çok azı küresel değişkenlerin her zaman daha iyi olduğunu çünkü daha basit olduklarını iddia eder. Mesele şu ki, "anlat, sorma" faydaları kapsamı azaltmanın karmaşıklığından daha ağır basar. Bu, kapsamı nesneye sınırlamaktan ziyade parametrelerin içinden geçmek için geçerlidir.private staticve parametreler aracılığıyla ihtiyaç duyduğu her şeyi iletir ve şimdi bu yönteme, yapmaması gereken şeylere kolayca erişememesi için güvenilebilir. Ayrıca, yöntemi küçük tutmayı teşvik eder, aksi takdirde parametre listesi kontrolden çıkar. Ve "saf işlev" kriterlerine uyan yazma yöntemlerini teşvik eder.

Bu yüzden "saf işlevsel" ve "söyle, sorma" ifadelerini birbirine zıt görmüyorum. Birincisi, ikincisinin endişelendiğim kadarıyla tek tam uygulamasıdır. Fowler'in yaklaşımı tam değil "söyle, sorma".

Ancak bu "tam anlamıyla sorma" uygulamasının gerçekten ideal olduğunu hatırlamak önemlidir, yani pragmatizmin daha az devreye girmesi gerekir, böylece idealist oluruz ve bu nedenle yanlış olana kadar tek doğru yaklaşım olarak davranırız. Çok az uygulama, gerçekten yan etki ücretsiz olsaydı yararlı bir şey yapmamaları için% 100 yan etki ücretsiz olmaya bile yaklaşabilir. Biz durumu değiştirmek gerekir, biz app yararlı olması için IO vb gerekir. Ve bu gibi durumlarda, yöntemler yan etkilere neden olmalıdır ve bu yüzden saf olamaz. Ancak buradaki temel kural, bu "saf olmayan" yöntemleri minimumda tutmaktır; sadece yan etkileri var çünkü norm olarak değil, ihtiyaçları var.

Bir savaş devletine sahip olmak benim için makul görünüyor. Bu sıra tabanlı bir oyun olduğu için savaş durumlarını sözlükte tutuyorum (çok oyunculu - aynı anda birçok oyuncu tarafından oynanan birçok savaş olabilir). Bir oyuncu ne zaman dönerse, (a) durumu buna göre değiştiren ve (b) JSON'a serileştirilen ve temel olarak sadece yazı tahtası.

Bana bir savaş hali vermek mantıklı değil; gerekli görünüyor. Bu kodun amacı, durumu değiştirme, bu durum değişikliklerini yönetme ve geri bildirme isteklerini işlemektir. Bu durumu küresel olarak ele alabilir, bireysel oynatıcı nesnelerinin içinde tutabilir veya bir dizi saf fonksiyonun etrafından geçirebilirsiniz. Hangisini seçtiğiniz, hangi senaryosunuz için en iyi olanı seçer. Global durum, kodun tasarımını basitleştirir ve hızlıdır, bu da çoğu oyunun temel gereksinimidir. Ancak bu, kodun bakımını, testini ve hata ayıklamasını zorlaştırır. Bir dizi saf işlev, kodun uygulanmasını daha karmaşık hale getirecek ve aşırı veri kopyalama nedeniyle çok yavaş olma riskini doğuracaktır. Ancak test etmek ve korumak en kolayı olacaktır. "OO yaklaşımı" arasında yarıya oturur.

Anahtar: her zaman çalışan mükemmel bir çözüm yoktur. Saf fonksiyonların amacı "başarı çukuruna düşmenize" yardımcı olmaktır. Ancak bu çukur çok sığsa, koda getirebileceği karmaşıklık nedeniyle, üzerine yolculuk olarak o kadar fazla düşmezsiniz, o zaman sizin için doğru yaklaşım değildir. İdeal olanı hedefleyin, ancak pragmatik olun ve bu idealin bu sefer gitmek için iyi bir yer olmadığında durun.

Ve son nokta olarak, sadece tekrarlamak için: saf işlevler ve "söyle, sorma" hiç de karşıt değildir.


5

Herhangi bir şey için, şimdiye kadar, bu ifadeyi koyabileceğiniz, onu saçma hale getirecek bir bağlam var.

resim açıklamasını buraya girin

Gerekirse sıfır argüman tavsiyesini alırsanız Bob Amca tamamen yanlıştır. Her ek argümanın kodu okumayı zorlaştırdığı anlamına gelirse, tamamen haklıdır. Bir bedeli var. İşlevlere argüman eklemezsiniz, çünkü bu onların okunmasını kolaylaştırır. İşlevlere bağımsız değişkenler eklersiniz çünkü bu bağımsız değişkene bağımlılığı açıkça gösteren iyi bir ad düşünemezsiniz.

Örneğin pi(), olduğu gibi mükemmel bir işlevdir. Neden? Çünkü bunun nasıl hesaplanıp hesaplanmadığı umurumda değil. Veya döndürdüğü sayıya ulaşmak için e veya sin () kullandıysa. Bu konuda iyiyim çünkü isim bana bilmem gereken her şeyi anlatıyor.

Ancak, her isim bilmem gereken her şeyi söylemiyor. Bazı isimler, ifşa edilen argümanların yanı sıra işlevin davranışını kontrol eden bilgileri anlamak için önemli değildir. İşte bu, işlevsel programlama tarzını akla getirmeyi kolaylaştıran şeydir.

Tamamen OOP tarzında değişmez ve yan etkileri serbest tutabilirim. Dönüş, bir sonraki prosedür için değerleri yığın üzerinde bırakmak için kullanılan bir tamircidir. İnsanların okuyabilmesini istiyorsanız bir şeyi değiştirmesi gereken son çıkış bağlantı noktasına varana kadar değerleri diğer değişmez şeylere iletmek için çıkış bağlantı noktalarını kullanarak değişmez kalabilirsiniz. Bu her dil için geçerli, işlevsel olsun ya da olmasın.

Bu yüzden lütfen Fonksiyonel Programlama ve Nesneye Yönelik Programlamanın "temel olarak uyumsuz" olduğunu iddia etmeyin. Fonksiyonel programlarımdaki nesneleri ve OO programlarımdaki saf fonksiyonları kullanabilirim.

Ancak, bunları karıştırmanın bir maliyeti vardır: Beklentiler. Her iki paradigmanın mekaniğine sadık kalarak hala karışıklığa neden olabilirsiniz. İşlevsel bir dil kullanmanın yararlarından biri, yan etkilerin herhangi bir çıktı elde etmek için var olmaları gerekirken öngörülebilir bir yere yerleştirilmesidir. Elbette değişken bir nesneye disiplinsiz bir şekilde erişilmediği sürece. O zaman bu dilde verilmiş olan şey birbirinden ayrılır.

Benzer şekilde, saf işlevlere sahip nesneleri destekleyebilir, değiştirilemez nesneleri tasarlayabilirsiniz. Sorun, eğer fonksiyonların saf veya nesnelerin değişmez olduğunu göstermezseniz, insanlar kodu okumak için çok zaman harcayana kadar bu özelliklerden hiçbir muhakeme faydası elde etmezler.

Bu yeni bir konu değil. Yıllarca insanlar “OO dilleri” kullandıklarından dolayı OO yaptıklarını düşünerek prosedürel olarak “OO dilleri” kodladılar. Birkaç dil, kendinizi ayağınızdan çekmekten alıkoymaz. Bu fikirlerin işe yaraması için içinizde yaşamak zorundalar.

Her ikisi de iyi özellikler sunar. Her ikisini de yapabilirsiniz. Bunları karıştıracak kadar cesursanız, lütfen bunları net bir şekilde etiketleyin.


0

Zaman zaman çeşitli paradigmaların tüm kurallarını anlamak için mücadele ediyorum. Bazen bu durumda oldukları gibi birbirleriyle çelişiyorlar.

OOP, tehlikeli şeylerin gerçekleştiği dünyada makasla koşmakla ilgili zorunlu bir paradigmadır.

FP, saf hesaplamada mutlak güvenlik bulduğu işlevsel bir paradigmadır. Burada hiçbir şey olmuyor.

Ancak, tüm programlar faydalı olabilmek için zorunlu dünyaya köprü kurmalıdır. Böylece, fonksiyonel çekirdek, zorunlu kabuk .

Değişmez nesneleri (komutları gerçekte mutasyondan ziyade değiştirilmiş bir kopya döndüren) tanımlamaya başladığınızda işler kafa karıştırır. Kendinize "Bu OOP" ve "Nesne davranışını tanımlıyorum" dersiniz. Denenmiş ve test edilmiş Söyle, Sorma prensibine geri dönersin. Sorun şu ki, yanlış bölgeye uyguluyorsunuz.

Diyarlar tamamen farklıdır ve farklı kurallara uyarlar. İşlevsel alem, dünyaya yan etkileri bırakmak istediği noktaya kadar gelişir. Bu etkilerin serbest bırakılması için, zorunlu bir nesnede (bu şekilde yazılmış olsaydı!) Kapsüllenmiş olan tüm verilerin zorunlu kabuğun boşluğunda olması gerekir. Farklı bir dünyada kapsülleme yoluyla gizlenmiş olan bu verilere erişim olmadan, işi yapamaz. Hesaplama imkansız.

Bu nedenle, değişmez nesneler (Clojure'un kalıcı veri yapıları olarak adlandırdığı şey) yazarken, işlevsel etki alanında olduğunuzu unutmayın. Söyle, Pencereden Sormayın ve sadece zorunlu alana yeniden girdiğinizde eve geri bırakın.

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.