Hangi İşlevsel özellikler getirdikleri faydalar için biraz OOP karışıklığına değer?


13

Haskell ve F # 'da fonksiyonel programlamayı öğrendikten sonra, OOP paradigması sınıflar, arayüzler, nesnelerle geriye doğru görünüyor. İş arkadaşımın anlayabileceği işe FP'nin hangi yönlerini getirebilirim? Bunları kullanabilmemiz için patronumla takımımı yeniden eğitme konusunda konuşmaya değer FP stilleri var mı?

FP'nin olası yönleri:

  • değişmezlik
  • Kısmi Uygulama ve Kurutma
  • Birinci Sınıf Fonksiyonlar (fonksiyon göstergeleri / Fonksiyonel Nesneler / Strateji Kalıbı)
  • Tembel Değerlendirme (ve Monadlar)
  • Saf Fonksiyonlar (yan etki yok)
  • İfadeler (İfadelerle - her kod satırı, yan etkilere neden olmak yerine yan etkilere neden olur)
  • özyineleme
  • Örüntü Eşleme

Dilin desteklediği sınıra kadar programlama dilinin desteklediği her şeyi yapabileceğimiz herkes için ücretsiz mi? Yoksa daha iyi bir kılavuz var mı?


6
Buna benzer birşey yaşadım. Yaklaşık 2 aylık bir acıdan sonra “nesnelerle eşleşen şeyler” ve “işlevlerle eşleşen şeyler” arasında oldukça güzel bir denge bulmaya başladım. Her ikisini de destekleyen bir dilde ciddi bir saldırı yapmaya yardımcı olur. Sonunda, hem FP hem de OOP becerilerim büyük ölçüde geliştirildi
Daniel Gratzer

3
FWIW, Linq işlevsel hem de ve tembel ve statik yöntemler kullanarak ve devlet kalıcılığını kaçınarak C # fonksiyonel programlama simüle edebilirsiniz.
Robert Harvey

1
Bu noktada, sicp'i okumalısınız . Ücretsiz ve iyi yazılmış. İki paradigma arasında güzel bir karşılaştırma sunar.
Simon Bergot

4
FP ve OOP aynı zamanda bir anlamda dik ve bir anlamda ikili. OOP veri soyutlamasıyla ilgilidir, FP yan etkilerle ilgilidir (yokluğu). Yan etkilerinizin olup olmadığı, verilerinizi soyutlama biçiminize dikeydir. Lambda Matematik örneğin hem işlevsel hem de nesne yönelimlidir. Evet, FP genellikle nesneleri değil Soyut Veri Türlerini kullanır, ancak daha az FP olmadan nesneleri de kullanabilirsiniz. OTOH, ayrıca derin bir ilişki vardır: bir işlev tek bir yöntemle bir nesneye izomorfiktir (Java'da "bu şekilde taklit edilirler" ve Java8'de uygulanırlar, ...
Jörg W Mittag

3
Sorunuzun en güçlü yönünün okunabilirlikle ilgili olduğunu düşünüyorum. "Nesneye Dayalı bir dükkanda çalışmak için ne kadar İşlevsel Programlama stili uygun?" Veya hangi İşlevsel özelliklerin getirdikleri faydalar için biraz OOP karışıklığına değer olduğunu.
GlenPeterson

Yanıtlar:


13

Fonksiyonel programlama, Nesneye Dayalı programlamadan farklı bir paradigmadır (farklı bir zihniyet ve programlar hakkında farklı bir düşünme şekli). Burada problemler ve çözümleri hakkında düşünmenin birden fazla yolunun (nesne yönelimli) olduğunu fark etmeye başladınız. Başkaları da var (prosedürel ve jenerik programlama akla geliyor). Bu yeni bilgiye nasıl tepki verdiğiniz, bu yeni araçları ve yaklaşımları kabul edip beceri setinize entegre edip etmediğiniz, büyüyüp daha eksiksiz ve yetenekli bir geliştirici olup olmadığınızı belirleyecektir.

Hepimiz belirli bir karmaşıklık seviyesiyle başa çıkmak için eğitildik ve rahatız. Bunu bir kişinin hrair limiti olarak adlandırmayı seviyorum (Watership Down'dan ne kadar sayabilirsin). Zihninizi genişletmek, daha fazla seçenek düşünmek ve sorunlara yaklaşmak ve çözmek için daha fazla araca sahip olmak harika bir şey. Ancak bu bir değişikliktir ve sizi rahatlık alanınızdan çeker.

Karşılaşabileceğiniz bir sorun, "her şey bir nesnedir" kalabalığını takip etmek için daha az içerikli olmanızdır. Yazılım geliştirmeye işlevsel bir yaklaşımın neden bazı problemler için iyi çalıştığını anlamayan (veya anlamak isteyen) insanlarla çalışırken sabır geliştirmeniz gerekebilir. Genel bir programlama yaklaşımı gibi bazı problemler için de işe yarar.

İyi şanslar!


3
Haskell veya Clojure gibi fonksiyonel dillerle çalışırken bazı geleneksel OOP kavramlarını daha iyi anlayabildiğini eklemek isterim. Şahsen, polimorfizmin gerçekten önemli bir kavram olduğunu (Java'daki arayüzler veya Haskell'deki tipler) fark ederken, kalıtım (tanımlayıcı bir kavram olduğunu düşündüğüm) biraz garip bir soyutlamadır.
wirrbel

6

İşlevsel programlama, günlük kod yazımında çok pratik, toprağa bağlı, üretkenlik sağlar: bazı özellikler tersliği destekler, çünkü bu, ne kadar az kod yazarsanız, o kadar az arıza yapar ve daha az bakım gerektirir.

Bir matematikçi olarak, fantezi fonksiyonel şeyleri çok çekici buluyorum, ancak genellikle bir uygulama tasarlarken yararlıdır: bu yapılar, bu değişmezleri değişkenlerle temsil etmeden, program yapısında birçok değişkeni program yapısında kodlayabilir.

En sevdiğim kombinasyon oldukça önemsiz görünebilir, ancak bunun çok yüksek bir verimlilik etkisine sahip olduğuna inanıyorum. Bu kombinasyon, tekrar etiketleyeceğim Kısmi Uygulama ve Kıvrılma ve Birinci Sınıf Fonksiyonlarıdır , bir daha asla for-loop yazmaz : bunun yerine, döngü gövdesini bir yineleme veya haritalama işlevine geçirir. Son zamanlarda bir C ++ işi için işe alındı ​​ve eğlenceli bir şekilde fark ettim, tamamen döngüler yazma alışkanlığını kaybettim !

Özyineleme ve Örüntü Eşleme kombinasyonu, o Ziyaretçi tasarım örüntüsünün ihtiyacını ortadan kaldırır . Boole ifadelerinin bir değerlendiricisini programlamak için ihtiyacınız olan kodu karşılaştırın: herhangi bir fonksiyonel programlama dilinde bu yaklaşık 15 satır olmalıdır, OOP'ta yapılacak doğru şey , o oyuncak örneğini döndüren Ziyaretçi tasarım modelini kullanmaktır . kapsamlı bir deneme. Avantajları açıktır ve herhangi bir rahatsızlığın farkında değilim.


2
Tamamen katılıyorum ama endüstri çapında hemfikir olma eğiliminde olan kişilerden bir itiraz aldım: Ziyaretçi kalıplarını biliyorlar, birçok kez gördüler ve kullandılar, bu yüzden içindeki kod anladıkları ve aşina oldukları bir şey, gülünç daha basit ve daha kolay olsa da diğer yaklaşım yabancıdır ve bu nedenle onlar için daha zordur. Bu, her programcıya 15+ yıllık OOP vurmuş olan endüstrinin talihsiz bir gerçeği, 100+ satırı on yıldan fazla bir süre içinde tekrarladıktan sonra ezberledikleri için 100'den fazla kod satırının 10'dan daha kolay anlaşılmalarıdır.
Jimmy Hoffa

1
-1 - Daha fazla kısa kod, "daha az" kod yazdığınız anlamına gelmez. Aynı kodu daha az karakter kullanarak yazıyorsunuz. Bir şey varsa , kodun okunması daha sık olduğu için daha fazla hata yaparsınız .
Telastyn

8
@Telastyn: Terse okunamaz ile aynı değil. Ayrıca, büyük şişirilmiş kazan plakası kitlelerinin okunamaz olma yolları vardır.
Michael Shaw

1
@Telastyn Ben sadece burada gerçek crux dokundu düşünüyorum, evet terse kötü ve okunamaz olabilir, şişirilmiş de kötü ve okunamaz olabilir, ama anahtar değişken uzunluk ve bilinmeyen yazılı kod değildir. Anahtar operasyonların sayısının yukarıda bahsettiğimiz gibi, ben (açıkça yazılı kodu ile) daha az şeyler yaptığını düşünüyorum, operasyonların bu sayı düzenlenebilirliği değil korelatı yapar katılmıyorum olduğunu yapar fayda okunabilirliği ve yaşatılabilirlik. Açıkçası değil yardım, iyi FP ihtiyacı olacak tek harf fonksiyonu ve değişken adları ile şeylerin aynı sayıda yapıyor ölçüde hala açıkça yazılı az operasyonları
Jimmy Hoffa

2
@ user949300: Tamamen farklı bir örnek istiyorsanız, bu Java 8 örneğine ne dersiniz ?: list.forEach(System.out::println);FP açısından println, bir hedef PrintStreamve bir değer olmak üzere iki argüman alan bir işlevdir, Objectancak Collection' forEachyöntemi yalnızca bir argümanlı bir işlev bekler her öğeye uygulanabilir. Dolayısıyla, ilk argüman, System.outbir argümanla yeni bir işleve ulaşmada bulunan örneğe bağlıdır . Daha basitBiConsumer<…> c=PrintStream::println; PrintStream a1=System.out; list.forEach(a2 -> c.accept(a1, a2));
Holger

5

İşinizde kullandığınız bilginin hangi kısımlarını, Superman'ın normal bir yaşamın tadını çıkarmak için Clark Kent gibi davranması gerektiğini kısıtlamanız gerekebilir. Ama daha fazlasını bilmek sana zarar vermez. Bununla birlikte, Fonksiyonel Programlamanın bazı yönleri Nesneye Dayalı bir dükkan için uygundur ve diğer yönler patronunuzla konuşmaya değer olabilir, böylece mağazanızın ortalama bilgi seviyesini yükseltebilir ve sonuç olarak daha iyi kod yazabilirsiniz.

FP ve OOP birbirini dışlamaz. Scala'ya bak. Bazıları bunun en kötü olduğunu düşünür çünkü saf olmayan FP'dir, ancak bazıları aynı nedenden dolayı en iyisi olduğunu düşünür.

Tek tek, İşte OOP ile harika çalışan bazı yönler:

  • Saf Fonksiyonlar (yan etki yok) - Bildiğim her programlama dili bunu destekliyor. Kodunuzu çok, akıl yürütmek için çok daha kolay hale getirir ve pratik olduğunda kullanılmalıdır. Ona FP demek zorunda değilsiniz. Sadece iyi kodlama uygulamaları deyin.

  • Değiştirilemezlik: String tartışmalı olarak en çok kullanılan Java nesnesidir ve değişmezdir. Ben kapağı Immutable Java Nesneleri ve Immutable Java Koleksiyonlar blogumda. Bunlardan bazıları sizin için geçerli olabilir.

  • Birinci Sınıf İşlevler (işlev işaretçileri / İşlevsel Nesneler / Strateji Kalıbı) - Java, 1.1 sürümünden bu yana, Listener arabirimini uygulayan API sınıflarının çoğunun (ve yüzlerce) olduğu bunun hobbled, mutant bir sürümüne sahiptir. Runnable muhtemelen en sık kullanılan fonksiyonel nesnedir. Birinci Sınıf İşlevleri, onları yerel olarak desteklemeyen bir dilde kodlamak için daha fazla çalışmadır, ancak bazen kodunuzun diğer yönlerini basitleştirdiklerinde ekstra çaba sarf etmeye değer.

  • Özyineleme ağaçları işlemek için kullanışlıdır. Bir OOP dükkanında, bu muhtemelen özyinelemenin birincil uygun kullanımıdır. OOP dillerinde eğlenmek için özyineleme kullanmak, çoğu OOP dilinin varsayılan olarak yığın boşluğuna sahip olmaması durumunda, bunu iyi bir fikir haline getirmek için muhtemelen çatık olmalıdır.

  • İfadeler (İfadelere Karşı - her kod satırı, yan etkilere neden olmak yerine yan etkilere ek olarak bir değer üretir) - C, C ++ ve Java'daki tek değerlendirici operatör Üçlü İşleçtir . Blogumda uygun kullanımı tartışıyorum. Yeniden kullanılabilir ve değerlendirici olan bazı basit fonksiyonlar yazabilirsiniz.

  • Tembel Değerlendirme (ve Monadlar) - çoğunlukla OOP'ta tembel Başlatma ile sınırlıdır. Bunu destekleyecek dil özellikleri olmadan, yararlı olan bazı API'leri bulabilirsiniz, ancak kendinizinkini yazmak zordur. Bunun yerine akış kullanımınızı en üst düzeye çıkarın - örnekler için Yazar ve Okuyucu arabirimlerine bakın.

  • Kısmi Uygulama ve Kurutma - Birinci sınıf fonksiyonlar olmadan pratik değildir.

  • Örüntü Eşleme - genellikle OOP'de önerilmez.

Özetle, çalışmanın, dilin desteklediği sınıra kadar programlama dilinin desteklediği her şeyi yapabileceğiniz herkes için ücretsiz olması gerektiğini düşünmüyorum. İş arkadaşlarınız tarafından okunabilirliğin, işe alınmak için yapılan kod için turnusol testi olması gerektiğini düşünüyorum. Sizi en çok dinlendiren işte, iş arkadaşlarınızın ufkunu genişletmek için iş başında eğitim almaya başlayacağım.


FP öğrenmeye başladığımdan beri, ifadelere benzeyen akıcı arayüzlere sahip şeyler tasarlamayı alışkanlık haline getirdim, bir sürü şey yapan bir ifadeye sahip bir işlev. Gerçekten alacağınız en yakın olanıdır, ancak artık herhangi bir boşluk yönteminiz olmadığını bulduğunuzda C # yardımcılarında statik uzatma yöntemlerini kullanarak doğal olarak saflıktan akan bir yaklaşımdır. Bu şekilde ifade noktanıza katılmıyorum tek nokta, her şey FP öğrenme ve bir .NET günlük iş kendi deneyimlerimle nokta
Jimmy Hoffa

C # 'da beni gerçekten rahatsız eden şey, 2 basit nedenden ötürü tek yöntemli arayüzler yerine delegeleri kullanamam: birleştirici (C # 'da cehennem kadar çirkin). 2. Proje kapsamında kullanabileceğiniz hiçbir tür takma ad bulunmadığından, temsilcilerinizin imzaları hızla yönetilemez hale gelir. Bu yüzden sadece bu 2 aptal nedenden ötürü artık C # 'dan yararlanamıyorum, çünkü çalışmasını sağlayabileceğim tek şey, sadece gereksiz ekstra çalışma olan tek yöntemli arayüzler kullanmaktır.
Trident D'Gao

@bonomo Java 8, C # için yararlı olabilecek genel bir java.util.function.BiConsumer'a sahiptir: public interface BiConsumer<T, U> { public void accept(T t, U u); }java.util.function içinde başka kullanışlı fonksiyonel arayüzler vardır.
GlenPeterson

@bonomo Hey anladım, bu Haskell'in acısı. Birini okuduğunuzda "FP öğrenmek beni OOP'ta daha iyi hale getirdi" der, Ruby veya Haskell gibi saf ve beyan edici olmayan bir şey öğrendikleri anlamına gelir. Haskell, OOP'un yararsız derecede düşük olduğunu açıkça ortaya koyuyor. Karşılaştığınız en büyük acı, HM tipi sistemde olmadığınızda kısıtlama tabanlı tür çıkarımının kararsız olması, bu nedenle koznantrasyon tabanlı tür çıkarımı tamamen yapılmamaktadır: blogs.msdn.com/b/ericlippert/archive /
2012/03/09

1
Most OOP languages don't have the stack space for it Gerçekten mi? İhtiyacınız olan tek şey, dengeli bir ikili ağaçtaki milyarlarca düğümü yönetmek için 30 özyineleme düzeyidir. Yığın alanımın bundan daha fazla seviye için uygun olduğundan eminim.
Robert Harvey

3

Fonksiyonel programlamaya ve nesne yönelimli programlamaya ek olarak, bildirimsel programlama da (SQL, XQuery) vardır. Her stili öğrenmek yeni bilgiler edinmenize yardımcı olur ve iş için doğru aracı seçmeyi öğrenirsiniz.

Ama evet, bir dilde kod yazmak çok sinir bozucu olabilir ve başka bir şey kullanıyorsanız, belirli bir sorun alanı için çok daha üretken olabileceğinizi bilin. Bununla birlikte, Java gibi bir dil kullanıyor olsanız bile, FP'den Java kodunuza kavramları dolambaçlı yollarla da olsa uygulamak mümkündür. Örneğin, Guava çerçevesi bunlardan bazılarını yapar.


2

Bir programcı olarak öğrenmeyi asla bırakmamalısınız. Bununla birlikte, FP öğrenmenin OOP becerilerinizi azaltması çok ilginç. OOP öğrenmeyi bisiklet kullanmayı öğrenmeyi düşünüyorum; nasıl yapılacağını asla unutma.

FP'nin giriş ve çıkışlarını öğrendiğimde, kendimi daha matematiksel düşünerek buldum ve yazılım yazdığım araçlara daha iyi bir bakış açısı kazandım. Bu benim kişisel deneyimim.

Daha fazla deneyim kazandıkça, çekirdek programlama kavramlarını kaybetmek çok daha zor olacaktır. Bu yüzden, OOP kavramları zihninizde tamamen katılaşana kadar FP'de bunu kolaylaştırmanızı öneririm. FP kesin bir paradigma değişimidir. İyi şanslar!


4
OOP öğrenmek, taramayı öğrenmek gibidir. Ancak ayaklarınızı sabit bir şekilde tuttuğunuzda, sadece çok sarhoş olduğunuzda sürünmeye başvurursunuz. Tabii ki nasıl yapılacağını unutamazsınız, ama normalde yapmak istemezsiniz. Ve koşabileceğinizi bildiğinizde tarayıcılarla yürümek acı verici bir deneyim olacaktır.
SK-logic

@ SK-logic, metaforunu seviyorum
Trident D'Gao

@ SK-Logic: Öğrenme zorunluluğu programlama nasıl bir şey? Kendinizi midenize sürüklüyor musunuz?
Robert Harvey

@ RobertHarvey paslı bir kaşık ve bir deste kartpostal ile yeraltında oymaya çalışıyor.
Jimmy Hoffa

0

Şimdiden birçok iyi yanıt var, bu yüzden benimki sorunuzun bir alt kümesini ele alacak; yani, OOP ve fonksiyonel özellikler birbirini dışlamadığından, sorunuzun öncülüne zorlanıyorum.

C ++ 11 kullanıyorsanız, dil / standart kütüphanede yerleşik olan ve OOP ile iyi bir şekilde sinerji yaratan (oldukça) fonksiyonel programlama özelliklerinin birçoğu vardır. Tabii ki, TMP'nin patronunuz veya iş arkadaşlarınız tarafından ne kadar iyi alınacağından emin değilim, ancak mesele şu ki, bu özelliklerin çoğunu C ++ gibi işlevsel olmayan / OOP dillerinde bir şekilde veya başka bir şekilde alabilirsiniz.

Derleme zamanı yinelemeli şablonların kullanılması ilk 3 noktanıza bağlıdır,

  • değişmezlik
  • özyineleme
  • Örüntü Eşleme

Bu şablon değerlerinin değişmez olduğu (derleme zamanı sabitleri), herhangi bir yineleme özyineleme kullanılarak yapılır ve dallanma, aşırı yük çözünürlüğü biçiminde (daha fazla veya daha az) desen eşleştirme kullanılarak yapılır.

Diğer noktaları gelince, kullanma std::bindve std::functionsize kısmi fonksiyon uygulaması verir ve fonksiyon işaretçileri yerleşik olan dile. Çağrılabilir nesneler işlevsel nesnelerdir (kısmi işlev uygulamasının yanı sıra). Çağrılabilir nesnelerle, onları tanımlayanları kastediyorum operator ().

Tembel değerlendirme ve saf işlevler biraz daha zor olurdu; saf işlevler için, yalnızca değere göre yakalanan lambda işlevlerini kullanabilirsiniz, ancak bu ideal değildir.

Son olarak, kısmi işlev uygulamasıyla derleme zamanı özyinelemesinin kullanımına bir örnek. Biraz çelişkili bir örnek, ama yukarıdaki noktaların çoğunu gösteriyor. Belirli bir demet içindeki değerleri belirli bir işleve özyineli olarak bağlar ve (çağrılabilir) bir işlev nesnesi oluşturur

#include <iostream>
#include <functional>

//holds a compile-time index sequence
template<std::size_t ... >
struct index_seq
{};

//builds the index_seq<...> struct with the indices (boils down to compile-time indexing)
template<std::size_t N, std::size_t ... Seq>
struct gen_indices
  : gen_indices<N-1, N-1, Seq ... >
{};

template<std::size_t ... Seq>
struct gen_indices<0, Seq ... >
{
    typedef index_seq<Seq ... > type;
};


template <typename RType>
struct bind_to_fcn
{
    template <class Fcn, class ... Args>
    std::function<RType()> fcn_bind(Fcn fcn, std::tuple<Args...> params)
    {
        return bindFunc(typename gen_indices<sizeof...(Args)>::type(), fcn, params);
    }

    template<std::size_t ... Seq, class Fcn, class ... Args>
    std::function<RType()> bindFunc(index_seq<Seq...>, Fcn fcn, std::tuple<Args...> params)
    {
        return std::bind(fcn, std::get<Seq>(params) ...);
    }
};

//some arbitrary testing function to use
double foo(int x, float y, double z)
{
    return x + y + z;
}

int main(void)
{
    //some tuple of parameters to use in the function call
    std::tuple<int, float, double> t = std::make_tuple(1, 2.04, 0.1);                                                                                                                                                                                                      
    typedef double(*SumFcn)(int,float,double);

    bind_to_fcn<double> binder;
    auto other_fcn_obj = binder.fcn_bind<SumFcn>(foo, t);
    std::cout << other_fcn_obj() << std::endl;
}
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.