Fonksiyonel tarzda programlama yaparken, uygulama mantığından geçtiğiniz tek bir uygulama durumunuz var mı?


12

Aşağıdakilerin tümüne sahip bir sistemi nasıl kurarım :

  1. Değişmez nesnelerle saf fonksiyonların kullanılması.
  2. Sadece ihtiyaç duyduğu işlevin bir işlev verilerine aktarın, daha fazla değil (yani büyük uygulama durumu nesnesi yok)
  3. İşlevler için çok fazla argüman bulundurmaktan kaçının.
  4. Fonksiyonlara çok fazla parametrenin aktarılmasını önlemek için, sadece işlevlere parametrelerin paketlenmesi ve paketlerinin açılması amacıyla yeni nesneler oluşturmaktan kaçının. Bir işleve tek bir nesne olarak birden çok öğe paketleyeceğim, bu nesnenin geçici olarak oluşturulmuş bir şey değil, söz konusu verilerin sahibi olmasını istiyorum

Bana öyle geliyor ki, Devlet monad kuralı 2 numaralı kuralı ihlal ediyor, ancak bu açık olmasa da, monad boyunca dokunmuş.

Lensleri bir şekilde kullanmam gerektiğini hissediyorum, ancak İşlevsel olmayan diller için çok az şey yazıldı.

Arka fon

Bir alıştırma olarak, var olan uygulamalarımdan birini nesne yönelimli bir stilden işlevsel bir stile dönüştürüyorum. Yapmaya çalıştığım ilk şey, uygulamanın iç çekirdeğini olabildiğince yapmak.

Duyduğum bir şey, "Devlet" i tamamen işlevsel bir dilde nasıl yöneteceğimi ve Devlet monadsları tarafından yapıldığına inandığım şey, mantıksal olarak, saf bir işlev olarak adlandırdığınız, dünya olduğu gibi ", sonra işlev geri döndüğünde, değiştikçe dünyanın durumuna geri döner.

Örnek olarak, "merhaba bir dünya" yı tamamen işlevsel bir şekilde yapma şekliniz, sanki ekranınızın bu durumunu programınıza aktarır ve üzerine "merhaba dünya" yazılı ekranın durumunu geri alırsınız. Yani teknik olarak, saf bir işlevi çağırıyorsunuz ve hiçbir yan etkisi yok.

Buna dayanarak başvurumu inceledim ve: 1. İlk olarak tüm uygulama durumumu tek bir global nesneye (GameState) koy 2. İkinci olarak GameState'i değişmez hale getirdim. Değiştiremezsiniz. Bir değişikliğe ihtiyacınız varsa, yeni bir değişiklik yapmanız gerekir. Bunu, isteğe bağlı olarak değişen bir veya daha fazla alanı alan bir kopya oluşturucu ekleyerek yaptım. 3. Her uygulamaya GameState'i parametre olarak iletiyorum. İşlev içinde, ne yapacağını yaptıktan sonra, yeni bir GameState oluşturur ve geri döndürür.

Nasıl saf bir fonksiyonel çekirdek ve dış GameState uygulamanın ana iş akışı döngü besleyen bir döngü var.

Benim sorum:

Şimdi, benim sorunum, GameState'in yaklaşık 15 farklı değişmez nesneye sahip olmasıdır. En düşük seviyedeki işlevlerin çoğu, puan tutma gibi bu nesnelerin yalnızca birkaçında çalışır. Diyelim ki skoru hesaplayan bir fonksiyonum var. Bugün, GameState yeni bir skorla yeni GameState oluşturarak skoru değiştiren bu fonksiyona aktarılıyor.

Bununla ilgili bir şeyler yanlış görünüyor. İşlev için GameState'in tamamı gerekmez. Sadece Score nesnesine ihtiyacı var. Bu yüzden Skoru geçmek ve sadece Skoru döndürmek için güncelledim.

Bu mantıklı görünüyordu, bu yüzden diğer işlevlerle daha ileri gittim. Bazı işlevler GameState'ten 2, 3 veya 4 parametre geçirmemi gerektiriyor, ancak deseni uygulamanın dış çekirdeğine kadar kullandığım için, uygulama durumunu giderek daha fazla geçiyorum. İş akışı döngüsünün en üstünde, bir yöntemi çağırırdım, bu yöntemi bir yöntemi çağıracak yöntemi çağırırdı, puanın hesaplandığı yere kadar. Bu, en alttaki bir fonksiyonun skoru hesaplayacağı için mevcut skorun tüm bu katmanlardan geçtiği anlamına gelir.

Şimdi bazen onlarca parametreyle fonksiyonum var. Parametrelerin sayısını azaltmak için bu parametreleri bir nesneye koyabilirim, ancak daha sonra, sadece geçmemek için sadece çağrı sırasında inşa edilen bir nesne yerine, bu sınıfın durum uygulama durumunun ana konumu olmasını isterim. birden çok parametrede ve ardından paketinden çıkarın.

Şimdi merak ediyorum, sorunum fonksiyonlarımın çok derin yuvalanmış mı? Bu, küçük işlevlere sahip olmak istemenin sonucudur, bu nedenle bir işlev büyüdüğünde yeniden düzenler ve birden çok küçük işleve bölerim. Ancak bunu yapmak daha derin bir hiyerarşi üretir ve dış işlev doğrudan bu nesneler üzerinde çalışmasa bile iç işlevlere iletilen herhangi bir şeyin dış işleve iletilmesi gerekir.

Bu sorundan kaçınan yol boyunca GameState'i geçmek gibi görünüyordu. Ama işleve ihtiyaç duyduğundan daha fazla bilgi aktarma konusundaki asıl probleme geri döndüm.


1
Tasarım konusunda uzman değilim ve özel olarak işlevsel değilim, ancak doğası gereği oyununuz gelişen bir duruma sahip olduğundan, işlevsel programlamanın uygulamanızın tüm katmanlarına uyan bir paradigma olduğundan emin misiniz?
Walfrat

Walfrat, bence fonksiyonel programlama uzmanlarıyla konuşursanız, muhtemelen fonksiyonel programlama paradigmasının gelişen durumu yönetmek için çözümleri olduğunu söyleyeceklerini göreceksiniz.
Daisha Lynn

Sorunuz benim için daha geniş gibi görünüyordu. Sadece burada devletleri yönetmekle ilgili bir başlangıç ​​ise: stackoverflow.com/questions/1020653/…
adresindeki

2
@DaishaLynn Soruyu silmeniz gerektiğini düşünmüyorum. Kaldırıldı ve kimse onu kapatmaya çalışmıyor, bu yüzden bu site için kapsam dışı olduğunu düşünmüyorum. Şimdiye kadar bir cevabın olmaması, sadece nispeten niş bir uzmanlık gerektirdiği için olabilir. Ancak bu, sonunda bulunamayacağı ve cevaplanmayacağı anlamına gelmez.
Ben Aaronson

2
Değişken durumu, önemli bir dil yardımı olmadan karmaşık bir saf fonksiyonel programda yönetmek büyük bir acıdır. Haskell'de monadlar, kısa sözdizimi, çok iyi tür çıkarımları nedeniyle yönetilebilir, ancak yine de çok can sıkıcı olabilir. C # 'da çok daha fazla sorun yaşayacağınızı düşünüyorum.
Monica

Yanıtlar:


2

İyi bir çözüm olup olmadığından emin değilim. Bu bir cevap olabilir veya olmayabilir, ancak bir yorum yapmak için çok uzun. Benzer bir şey yapıyordum ve aşağıdaki hileler yardımcı oldu:

  • GameStateHiyerarşik olarak bölün , böylece 15 yerine 3-5 daha küçük parça elde edersiniz.
  • Arayüzleri uygulamasına izin verin, böylece yöntemleriniz sadece gerekli parçaları görür. Gerçek tür hakkında kendinize yalan söyleyeceğiniz gibi onları asla geri atmayın.
  • Ayrıca parçaların arayüzleri uygulamasına izin verin, böylece ne geçtiğinizi iyi kontrol edebilirsiniz.
  • Parametre nesnelerini kullanın, ancak bunu dikkatli bir şekilde yapın ve bunları kendi davranışlarıyla gerçek nesnelere dönüştürmeye çalışın.
  • Bazen gerekenden biraz daha fazla geçmek uzun bir parametre listesinden daha iyidir.

Şimdi merak ediyorum, sorunum fonksiyonlarımın çok derin yuvalanmış mı?

Ben öyle düşünmüyorum. Küçük işlevlere yeniden bakmak doğru, ancak belki onları daha iyi yeniden gruplandırabilirsiniz. Bazen mümkün değildir, bazen soruna ikinci (veya üçüncü) bir bakış gerekir.

Tasarımınızı değiştirilebilir tasarımla karşılaştırın. Yeniden yazmada kötüleşen şeyler var mı? Eğer öyleyse, onları ilk yaptığınız gibi daha iyi hale getiremez misiniz?


Birisi bana tasarımımı değiştirmemi söyledi, böylece fonksiyon sadece bir parametre alıyor, böylece körili kullanabiliyorum. Bu bir işlevi denedim, bu yüzden DeleteEntity (a, b, c) çağırmak yerine, şimdi DeleteEntity (a) (b) (c) çağırıyorum. Yani bu sevimli ve işleri daha kolay yapılabilir hale getirmesi gerekiyordu, ama henüz elde edemiyorum.
Daisha Lynn

@DaishaLynn Java kullanıyorum ve körelmek için tatlı sözdizimsel şeker yok, bu yüzden (benim için) denemeye değmez. Bizim durumumuzda daha üst düzey işlevlerin olası kullanımı konusunda şüpheliyim, ancak sizin için işe yarayıp yaramadığını bana bildirin.
maaartinus

2

C # ile konuşamam, ama Haskell'de bütün devleti geçeceksin. Bunu açık bir şekilde veya bir Devlet monad ile yapabilirsiniz. İhtiyaç duydukları zaman daha fazla bilgi alan fonksiyonlar sorununu çözmek için yapabileceğiniz bir şey Has karakter sınıflarını kullanmaktır. (Eğer alışık değilseniz, Haskell karakteristikleri C # arayüzlerine benzer.) E-devletin her elemanı için, E değerini döndüren getE işlevi gerektiren bir tipeclass HasE tanımlayabilirsiniz. bütün bu tip örneklerin bir örneğini oluşturdu. Daha sonra gerçek işlevlerinizde, Devlet monad'ınızı açıkça istemek yerine, ihtiyacınız olan öğeler için Has tip sınıflarına ait herhangi bir monad'a ihtiyacınız vardır; bu fonksiyonun kullandığı monad ile yapabileceklerini kısıtlar. Bu yaklaşım hakkında daha fazla bilgi için, bkz. Michael SnoymanReaderT tasarım deseni üzerine yazı .

Etrafta geçirilen durumu nasıl tanımladığınıza bağlı olarak, muhtemelen böyle bir şeyi C # 'da çoğaltabilirsiniz. Gibi bir şeyin varsa

public class MyState
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
}

Eğer arabirimleri tanımlayabilir IHasMyIntve IHasMyStringyöntemlerle GetMyIntve GetMyStringsırasıyla. Devlet sınıfı daha sonra şöyle görünür:

public class MyState : IHasMyInt, IHasMyString
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
    public double MyDouble {get; set; }

    public int GetMyInt () 
    {
        return MyInt;
    }

    public string GetMyString ()
    {
        return MyString;
    }

    public double GetMyDouble ()
    {
        return MyDouble;
    }
}

yöntemleriniz için uygun şekilde IHasMyInt, IHasMyString veya tüm MyState gerekebilir.

Daha sonra, durum nesnesini iletebilmeniz için işlev tanımındaki where kısıtlamasını kullanabilirsiniz, ancak çift değil, yalnızca string ve int'e ulaşabilir.

public static T DoSomething<T>(T state) where T : IHasMyString, IHasMyInt
{
    var s = state.GetMyString();
    var i = state.GetMyInt();
    return state;
}

İlginç. Yani şu anda, nerede bir fonksiyonu çağırmak ve değere göre 10 parametre geçmek, ben "gameSt'ate" 10 kez geçmek, ama "IHasGameScore", "IHasGameBoard", vb gibi 10 farklı parametre türleri, ben orada istiyorum işlevin gösterebileceği tek bir parametreyi geçirmenin bir yoludur ve bu türdeki tüm arabirimleri uygulamak zorundadır. Acaba bir "genel kısıtlama" ile yapabilir miyim .. Bunu deneyeyim.
Daisha Lynn

1
İşe yaradı. İşte çalışıyor: dotnetfiddle.net/cfmDbs .
Daisha Lynn

1

Sanırım Redux veya Elm ve bu soruyu nasıl ele aldıkları hakkında bilgi edinmeniz iyi olur.

Temel olarak, tüm durumu ve kullanıcının gerçekleştirdiği eylemi alan ve yeni durumu döndüren bir saf fonksiyonunuz vardır.

Bu işlev daha sonra her biri belirli bir devlet parçasını işleyen diğer saf işlevleri çağırır. Eyleme bağlı olarak, bu işlevlerin çoğu orijinal durumunu değiştirmeden başka bir şey yapamaz.

Daha fazla bilgi edinmek için Google Karaağaç Mimarisi veya Redux.js.org.


Elm'i tanımıyorum, ama Redux'a benzediğine inanıyorum. Redux'da tüm redüktörler her eyalet değişikliği için çağrılmıyor mu? Kulağa son derece verimsiz geliyor.
Daisha Lynn

Düşük seviyeli optimizasyonlar söz konusu olduğunda, varsaymayın, ölçün. Uygulamada yeterince hızlı.
Daniel T.

Teşekkürler Daniel, ama benim için işe yaramayacak. Herhangi bir veri değişikliği olduğunda, kontrolün kontrolü önemsemesine bakılmaksızın, kullanıcı arayüzündeki her bileşeni bildirmediğini bilmek için yeterince geliştirme yaptım.
Daisha Lynn

-2

Bence yapmaya çalıştığınız şey nesneye yönelik bir dili, sanki saf işlevsel bir dilmiş gibi kullanılmaması gereken bir şekilde kullanmaktır. OO dilleri tüm kötülükler gibi değil. Her iki yaklaşımın da avantajları vardır, bu yüzden şimdi OO stilini işlevsel stille karıştırabilir ve bazı kod parçalarını işlevsel hale getirme şansına sahip olurken, diğerleri yeniden kullanılabilirlik, kalıtım veya polimofizmin avantajlarından yararlanabilmemiz için nesne odaklı kalır. Neyse ki artık sadece iki yaklaşıma da bağlı değiliz, o zaman neden kendinizi bunlardan biriyle sınırlamaya çalışıyorsunuz?

Sorunuzu cevaplama: hayır, uygulama mantığı yoluyla belirli bir durumu örmem, ancak mevcut kullanım durumu için uygun olanı kullanmam ve mevcut teknikleri en uygun şekilde uygulama.

C # (henüz) istediğiniz gibi işlevsel olarak kullanılmaya hazır değil.


3
Bu cevaptan veya tondan heyecan duymadım. Hiçbir şeyi kötüye kullanmıyorum. Ben daha Fonksiyonel bir dil olarak kullanmak için C # sınırlarını bastırıyorum. Bu nadir bir şey değildir. Felsefi olarak buna karşı görünüyorsunuz, bu iyi, ama bu durumda bu soruya bakmayın. Yorumunuzun kimseye bir faydası yok. Devam et.
Daisha Lynn

@DaishaLynn yanılıyorsunuz, ona hiçbir şekilde karşı değilim ve aslında çok kullanıyorum ... ama doğal ve mümkün olduğu ve bir OO dilini işlevsel bir dile dönüştürmeye çalışmadığından sadece kalça olduğu için böyle yaparak. Cevabımı kabul etmek zorunda değilsiniz, ancak araçlarınızı doğru bir şekilde kullanmadığınız gerçeğini değiştirmez.
t3chb0t

Yapmıyorum çünkü bunu yapmak kalça. C # işlevsel bir stil kullanarak doğru ilerliyor. Anders Hejlsberg'in kendisi böyle belirtti. Sadece dilin ana akım kullanımı ile ilgilendiğinizi anlıyorum ve bunun neden ve ne zaman uygun olduğunu anlıyorum. Senin gibi birinin neden bu konuda olduğunu bilmiyorum .. Gerçekten nasıl yardım ediyorsun?
Daisha Lynn

@DaishaLynn sorunuzu veya yaklaşımınızı eleştiren cevaplarla başa çıkamıyorsanız, muhtemelen burada soru sormamalısınız veya bir dahaki sefere sadece fikrinizi% 100 destekleyen cevaplarla ilgilendiğinizi söyleyen bir sorumluluk reddi beyanı eklemelisiniz çünkü gerçeği duymak ister, daha çok destekleyici görüşler alırlar.
t3chb0t

Lütfen birbirlerine biraz daha samimi olun. Dili küçümsemeden eleştiri vermek mümkündür. İşlevsel bir tarzda C # programlamaya çalışmak kesinlikle "kötüye kullanım" veya bir uç durum değildir. Birçok C # geliştiricisi tarafından diğer dillerden öğrenmek için kullanılan yaygın bir tekniktir.
zumalifeguard
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.