Prosedürel ve işlevsel arasındaki farkı gerçekten anlamak


114

Prosedürel ve fonksiyonel programlama paradigmaları arasındaki farkı anlamakta gerçekten zorlanıyorum .

Fonksiyonel programlamayla ilgili Wikipedia girişinden ilk iki paragraf :

Bilgisayar biliminde, fonksiyonel programlama, hesaplamayı matematiksel fonksiyonların değerlendirilmesi olarak ele alan ve durum ve değişken verilerden kaçınan bir programlama paradigmasıdır. Durumdaki değişiklikleri vurgulayan zorunlu programlama stilinin aksine işlevlerin uygulanmasını vurgular. İşlevsel programlamanın kökleri, 1930'larda işlev tanımını, işlev uygulamasını ve özyinelemeyi araştırmak için geliştirilmiş biçimsel bir sistem olan lambda hesabına dayanır. Birçok işlevsel programlama dili, lambda hesabının ayrıntıları olarak görülebilir.

Pratikte, matematiksel bir fonksiyon ile zorunlu programlamada kullanılan bir "fonksiyon" kavramı arasındaki fark, zorunlu fonksiyonların program durumunun değerini değiştiren yan etkilere sahip olabilmesidir. Bu nedenle, referans şeffaflıktan yoksundurlar, yani aynı dil ifadesi, çalışan programın durumuna bağlı olarak farklı zamanlarda farklı değerlerle sonuçlanabilir. Tersine, işlevsel kodda, bir işlevin çıktı değeri yalnızca işleve girdi olan argümanlara bağlıdır, bu nedenle fbir bağımsız değişken için aynı değerle bir işlevi iki kez çağırmak xaynı sonucu verecektir.f(x)her iki seferde de. Yan etkilerin ortadan kaldırılması, bir programın davranışını anlamayı ve tahmin etmeyi çok daha kolay hale getirebilir ki bu, işlevsel programlamanın geliştirilmesi için temel motivasyonlardan biridir.

2. paragrafta yazdığı yerde

Bunun tersine, işlevsel kodda, bir işlevin çıktı değeri yalnızca işleve girdi olan argümanlara bağlıdır, bu nedenle fbir bağımsız değişken için aynı değere sahip bir işlevi iki kez çağırmak, her iki durumda xda aynı sonucu üretecektir f(x).

Prosedürel programlama için aynı durum aynı değil mi?

Prosedürel ve işlevsel arasında öne çıkan ne aranmalı?


1
Abafei'den "Büyüleyici Python: Python'da İşlevsel Programlama" bağlantısı kesildi. İşte bir dizi iyi bağlantı: ibm.com/developerworks/linux/library/l-prog/index.html ibm.com/developerworks/linux/library/l-prog2/index.html
Chris Koknat

Bunun bir başka yönü de isimlendirmedir. Örneğin. JavaScript ve Common Lisp'de, yan etkilere izin verilse bile fonksiyon terimini kullanırız ve Şema i'de aynı şekilde sürekli olarak prosedürler olarak adlandırılır. Saf olan bir CL işlevi, saf bir işlevsel Şema prosedürü olarak yazılabilir. Scheme ile ilgili hemen hemen tüm kitaplar, standartta kullanılan lastik olduğundan prosedür terimini kullanır ve bunun prosedürel veya işlevsel olmasıyla hiçbir ilgisi yoktur.
Sylwester

Yanıtlar:


276

Fonksiyonel Programlama

Fonksiyonel programlama, fonksiyonları değerler olarak ele alma yeteneğini ifade eder.

"Normal" değerlerle bir analoji düşünelim. İki tamsayı değeri alıp, +yeni bir tamsayı elde etmek için operatörü kullanarak bunları birleştirebiliriz . Ya da bir kayan nokta numarası elde etmek için bir tamsayıyı kayan nokta sayısı ile çarpabiliriz.

Fonksiyonel programlamada, compose veya lift gibi operatörleri kullanarak yeni bir fonksiyon değeri üretmek için iki fonksiyon değerini birleştirebiliriz . Veya harita veya katlama gibi işleçleri kullanarak yeni bir veri değeri üretmek için bir fonksiyon değeri ile bir veri değerini birleştirebiliriz .

Çoğu dilin işlevsel programlama yeteneklerine sahip olduğunu unutmayın - genellikle işlevsel diller olarak düşünülmeyen diller bile. Büyükbaba FORTRAN bile işlev değerlerini destekledi, ancak işlev birleştirme işleçleri yolunda pek bir şey sunmuyordu. Bir dilin "işlevsel" olarak adlandırılması için, işlevsel programlama yeteneklerini büyük bir şekilde kucaklaması gerekir.

Prosedürel Programlama

Prosedürel programlama, ortak bir talimat dizisini bir prosedüre dahil etme yeteneğini ifade eder, böylece bu talimatlar, kopyala ve yapıştır işlemine başvurmadan birçok yerden çağrılabilir. Prosedürler, programlamada çok erken bir gelişme olduğundan, yetenek neredeyse her zaman makine veya montaj dili programlamasının talep ettiği programlama tarzıyla bağlantılıdır: depolama konumları ve bu konumlar arasında veri taşıyan talimatlar kavramını vurgulayan bir stil.

Kontrast

İki stil aslında birbirine zıt değil - sadece birbirinden farklı. Her iki stili de tam olarak kucaklayan diller vardır (örneğin LISP). Aşağıdaki senaryo, iki stilde bazı farklılıklar hakkında fikir verebilir. Bir listedeki tüm kelimelerin tek sayıda karaktere sahip olup olmadığını belirlemek istediğimiz anlamsız bir gereksinim için biraz kod yazalım. İlk olarak, prosedürel tarz:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

Bu örneğin anlaşılabilir olduğunu verili olarak alacağım. Şimdi, işlevsel tarz:

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

İçten dışa doğru çalışarak, bu tanım şunları yapar:

  1. compose(odd, length)bir dizenin uzunluğunun tek olup olmadığını belirleyen yeni bir işlev üretmek için oddve lengthişlevlerini birleştirir .
  2. map(..., words)içindeki her öğe için bu yeni işlevi çağırır words, sonuçta her biri karşılık gelen kelimenin tek sayıda karaktere sahip olup olmadığını gösteren yeni bir boole değerleri listesi döndürür.
  3. apply(and, ...)"ve" operatörünü sonuç listesine uygular ve -son sonucu vermek için tüm boole'ları birlikte uygular .

Bu örneklerden, prosedürel programlamanın, değerleri değişkenler içinde hareket ettirmekle ve nihai sonucu üretmek için gereken işlemleri açık bir şekilde açıklamakla çok ilgilendiğini görebilirsiniz. Buna karşılık, işlevsel stil, ilk girdiyi nihai çıktıya dönüştürmek için gereken işlevlerin kombinasyonunu vurgular.

Örnek aynı zamanda prosedürel kodun işlevsel koduna göre tipik göreceli boyutlarını da göstermektedir. Ayrıca, prosedürel kodun performans özelliklerini görmenin işlevsel kodunkinden daha kolay olabileceğini gösterir. Şunu düşünün: işlevler listedeki tüm kelimelerin uzunluklarını hesaplıyor mu, yoksa her biri ilk çift uzunluktaki kelimeyi bulduktan hemen sonra mı duruyor? Öte yandan, işlevsel kod, açık bir algoritmadan ziyade öncelikle amacı ifade ettiği için oldukça ciddi bir optimizasyon gerçekleştirmesine yüksek kaliteli bir uygulamanın izin verir.

Daha fazla okuma

Bu soru çok ortaya çıkıyor ... örneğin bakınız:

John Backus'un Turing ödülü dersi, fonksiyonel programlamanın motivasyonlarını ayrıntılı olarak açıklıyor:

Programlama von Neumann Tarzından Kurtulabilir mi?

Şu anki bağlamda bu makaleden gerçekten bahsetmemeliyim çünkü oldukça hızlı bir şekilde oldukça teknik hale geliyor. Direnemedim çünkü bunun gerçekten temel olduğunu düşünüyorum.


Ek - 2013

Yorumcular, popüler çağdaş dillerin prosedürel ve işlevsel olmanın ötesinde başka programlama stilleri sunduğuna dikkat çekiyor. Bu tür diller genellikle aşağıdaki programlama tarzlarından birini veya birkaçını sunar:

  • sorgu (ör. liste anlamaları, dile entegre sorgu)
  • veri akışı (ör. örtük yineleme, toplu işlemler)
  • nesne yönelimli (ör. kapsüllenmiş veriler ve yöntemler)
  • dile yönelik (ör. uygulamaya özel sözdizimi, makrolar)

Bu yanıttaki sözde kod örneklerinin diğer stillerde mevcut olan bazı özelliklerden nasıl yararlanabileceğine dair örnekler için aşağıdaki yorumlara bakın. Özellikle, prosedürel örnek hemen hemen her yüksek seviyeli yapının uygulanmasından faydalanacaktır.

Sergilenen örnekler, tartışılan iki stil arasındaki ayrımı vurgulamak için bu diğer programlama stillerini kasıtlı olarak karıştırmaktan kaçınır.


1
Gerçekten güzel cevap, ancak kodu biraz basitleştirebilir misiniz, örneğin: "function allOdd (kelimeler) {foreach (auto word in words) {odd (length (word)? Return false:;} return true;}"
Dainius

Python'daki "işlevsel stil" ile karşılaştırıldığında oradaki işlevsel stili okumak oldukça zor: def odd_words (kelimeler): return [x for x in words if tek (len (x))]
kutulu

@ kutulu: Tanımınız yanıtınkinden odd_words(words)farklı bir şey yapıyor allOdd. Filtreleme ve haritalama için, genellikle liste anlamaları tercih edilir, ancak burada işlevin allOddbir kelime listesini tek bir boole değerine indirmesi beklenir.
ShinNoNoir

@WReach: Fonksiyonel örneğinizi şöyle yazmıştım: function allOdd (kelimeler) {return ve (tek (uzunluk (ilk (kelimeler))), allOdd (dinlenme (kelimeler))); } Örneğinizden daha zarif değildir, ancak kuyruk özyinelemeli bir dilde, emir kipi stiliyle aynı performans özelliklerine sahip olacaktır.
mishoo

@mishoo Varsayımınızın tutması için dilin hem kuyruk özyinelemeli hem de katı ve kısa devreli olması gerektiğine inanıyorum.
kqr

46

İşlevsel ve zorunlu programlama arasındaki gerçek fark zihniyettir - zorunlu programcılar bellek değişkenlerini ve bloklarını düşünürken, işlevsel programcılar " Girdi verilerimi çıktı verilerime nasıl dönüştürebilirim " diye düşünür - "programınız" boru hattıdır ve verileri Girdiden Çıktıya almak için veri üzerinde dönüşümler kümesi . Bu IMO'nun ilginç kısmı, "Değişkenleri kullanmayacaksın" biti değil.

Bu zihniyet bir sonucu olarak, FP programları genellikle açıklamak neyi yerine belirli mekanizmasının, ne olacak nasıl o olacak - açıkça "Nerede" ve "Agrega" ne anlama geldiğini "Seç" devlet ve eğer çünkü bu güçlü, biz Tıpkı AsParallel () ile yaptığımız gibi, uygulamalarını değiştirmekte özgürler ve aniden tek iş parçacıklı uygulamamız n çekirdeğe ölçekleniyor .


kod örneği parçacıklarını kullanarak ikisini karşılaştırmanın herhangi bir yolu var mı? gerçekten minnettarım
Philoxopher

1
@KerxPhilo: İşte çok basit bir görev (1'den n'ye kadar sayılar ekleyin). Zorunlu: Geçerli sayıyı değiştirin, şimdiye kadar toplamı değiştirin. Kod: int i, toplam; toplam = 0; için (i = 1; i <= n; i ++) {toplam + = i; }. Fonksiyonel (Haskell): Tembel bir sayı listesi alın, sıfıra eklerken onları katlayın. Kod: foldl (+) 0 [1..n]. Üzgünüz, yorumlarda biçimlendirme yok.
dirkt

Cevaba +1. Diğer bir deyişle, fonksiyonel programlama, fonksiyonları mümkün olduğunda yan etkiler olmadan yazmakla ilgilidir, yani fonksiyon aynı parametreler verildiğinde her zaman aynı şeyi döndürür - bu temeldir. Bu yaklaşımı en uç noktaya kadar izlerseniz, yan etkileriniz (bunlara her zaman ihtiyaç duyarsınız) izole edilir ve geri kalan işlevler, giriş verilerini çıktı verilerine dönüştürür.
beluchin

12
     Isn't that the same exact case for procedural programming?

Hayır, çünkü prosedürel kodun yan etkileri olabilir. Örneğin, aramalar arasında durumu saklayabilir.

Bununla birlikte, prosedürel olarak kabul edilen dillerde bu kısıtlamayı karşılayan kod yazmak mümkündür. Ayrıca işlevsel olarak kabul edilen bazı dillerde bu kısıtlamayı kıran kod yazmak da mümkündür.


1
Bir örnek ve karşılaştırma gösterebilir misiniz? Mümkünse gerçekten minnettarım.
Philoxopher

8
C'deki rand () işlevi, her çağrı için farklı bir sonuç sağlar. Durumu aramalar arasında saklar. Referans olarak şeffaf değildir. Buna karşılık, C ++ 'daki std :: max (a, b) her zaman aynı argümanlar verildiğinde aynı sonucu döndürür ve hiçbir yan etkisi yoktur (benim bildiğim ...).
Andy Thomas

11

WReach'in cevabına katılmıyorum. Anlaşmazlığın nereden geldiğini görmek için cevabını biraz çözelim.

Birincisi, kodu:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

ve

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

Dikkat edilmesi gereken ilk şey şudur:

  • fonksiyonel
  • İfade odaklı ve
  • Yineleyici merkezli

programlama ve yinelemeli stil programlamanın tipik bir işlevsel stile göre daha açık kontrol akışına sahip olma becerisini kaçırma.

Bunlardan hızlıca bahsedelim.

İfade merkezli stil, şeylerin mümkün olduğunca şeylere göre değerlendirildiği bir stildir . İşlevsel diller, ifade sevgileriyle ünlü olsa da, bir araya getirilebilir ifadeler olmadan işlevsel bir dile sahip olmak aslında mümkündür. Orada nerede, bir tane yapacağım hiçbir ifadeler, sadece ifadeleri.

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

Bu, daha önce verilenle hemen hemen aynıdır, ancak işlevler tamamen ifade ve bağlama zincirleri aracılığıyla zincirlenir.

Yineleyici merkezli bir programlama stili Python tarafından alınmış olabilir. Tamamen yinelemeli, yineleyici merkezli bir stil kullanalım :

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)

Bu işlevsel değildir, çünkü her cümle yinelemeli bir süreçtir ve yığın çerçevelerinin açık bir şekilde duraklatılması ve yeniden başlatılmasıyla birbirine bağlanırlar. Sözdizimi kısmen işlevsel bir dilden esinlenmiş olabilir, ancak tamamen yinelemeli bir düzenlemesine uygulanır.

Tabii ki, bunu sıkıştırabilirsiniz:

def all_odd(words):
    return all(odd(len(word)) for word in words)

Emir artık o kadar da kötü görünmüyor, ha? :)

Son nokta, daha açık kontrol akışı ile ilgiliydi. Bundan yararlanmak için orijinal kodu yeniden yazalım:

function allOdd(words) {
    for (var i = 0; i < length(words); ++i) {
        if (!odd(length(words[i]))) {
            return false;
        }
    }
    return true;
}

Yineleyicileri kullanarak sahip olabileceğiniz:

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}

Öyleyse , eğer fark aşağıdakiler arasındaysa, işlevsel bir dilin anlamı nedir :

return all(odd(len(word)) for word in words)
return apply(and, map(compose(odd, length), words))
for (word : words) { if (!odd(length(word))) { return false; } }
return true;


İşlevsel bir programlama dilinin temel belirleyici özelliği, tipik programlama modelinin bir parçası olarak mutasyonu ortadan kaldırmasıdır. İnsanlar genellikle bunu, işlevsel bir programlama dilinin ifadelere sahip olmadığı veya ifadeler kullanmadığı anlamına gelir, ancak bunlar basitleştirmelerdir. İşlevsel bir dil, açık hesaplamayı bir davranış bildirimiyle değiştirir ve bu, dilin daha sonra üzerinde bir azalma gerçekleştirir.

Kendinizi bu işlevsellik alt kümesiyle sınırlandırmak, programlarınızın davranışları hakkında daha fazla garantiye sahip olmanızı sağlar ve bu, onları daha özgürce oluşturmanıza olanak tanır.

İşlevsel bir diliniz olduğunda, yeni işlevler yapmak genellikle yakından ilişkili işlevler oluşturmak kadar basittir.

all = partial(apply, and)

Bir fonksiyonun küresel bağımlılıklarını açıkça kontrol etmediyseniz, bu basit değildir veya belki de mümkün değildir. İşlevsel programlamanın en iyi özelliği, tutarlı bir şekilde daha genel soyutlamalar oluşturabilmeniz ve bunların daha büyük bir bütün halinde birleştirilebileceklerine güvenebilmenizdir.


Biliyorsunuz, eminim ki applya foldveya ile aynı işlem olmadığından eminim reduce, yine de çok genel algoritmalara sahip olma becerisine katılıyorum.
Benedict Lee

Ben duymadım applydemek foldya reduce, ama buna bir boolean dönmek için bu bağlamda olmak zorunda gibi bana bakıyor.
Veedrac

Ah, tamam, isim konusunda kafam karıştı. Temizlediğiniz için teşekkürler.
Benedict Lee

6

Prosedürel paradigmada (bunun yerine "yapısal programlama" diyeyim mi?), Değişebilir hafıza ve onu bir sırayla (birbiri ardına) okuyan / yazan komutlar paylaştınız.

İşlevsel paradigmada, değişkenlere ve işlevlere sahipsiniz (matematiksel anlamda: değişkenler zamanla değişmez, işlevler yalnızca girdilerine göre bir şeyi hesaplayabilir).

(Bu, aşırı basitleştirilmiştir, örneğin, FPL'ler tipik olarak değiştirilebilir bellekle çalışmak için olanaklara sahiptir, oysa prosedürel diller genellikle daha yüksek seviyeli prosedürleri destekleyebilir, bu nedenle işler o kadar net değildir; ancak bu size bir fikir vermelidir)



2

Fonksiyonel programlamada bir sembolün (değişken veya fonksiyon adı) anlamı hakkında mantık yürütmek için gerçekten sadece 2 şeyi bilmeniz gerekir - mevcut kapsam ve sembolün adı. Değişmezliği olan tamamen işlevsel bir dile sahipseniz, bunların her ikisi de "statik" (aşırı yüklenmiş ad için özür dilerim) kavramlardır, yani hem mevcut kapsamı hem de adı yalnızca kaynak koduna bakarak görebilirsiniz.

Prosedürel programlamada, arkanızdaki değer nedir sorusuna cevap vermek xistiyorsanız, oraya nasıl geldiğinizi de bilmeniz gerekir, kapsam ve isim tek başına yeterli değildir. Ve bu benim en büyük zorluk olarak göreceğim şeydi çünkü bu yürütme yolu bir "çalışma zamanı" özelliğidir ve o kadar çok farklı şeye bağlı olabilir ki, çoğu insan yürütme yolunu denemeyi ve kurtarmayı değil, sadece hata ayıklamayı öğrenir.


1

Son zamanlarda İfade Problemi açısından farkı düşünüyordum . Phil Wadler'in tanımına sık sık atıfta bulunulur, ancak bu sorunun kabul edilen cevabını takip etmek muhtemelen daha kolaydır. Temel olarak, zorunlu diller soruna bir yaklaşım seçme eğilimindeyken, işlevsel diller diğerini seçme eğilimindedir.


0

İki programlama paradigması arasındaki açık bir fark durumdur.

Fonksiyonel Programlamada durumdan kaçınılır. Basitçe ifade etmek gerekirse, bir değer atanan değişken olmayacaktır.

Misal:

def double(x):
    return x * 2

def doubleLst(lst):
    return list(map(double, action))

Ancak, Prosedürel Programlama state'i kullanır.

Misal:

def doubleLst(lst):
    for i in range(len(lst)):
        lst[i] = lst[i] * 2  # assigning of value i.e. mutation of state
    return lst
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.