Benar-benar memahami perbedaan antara prosedural dan fungsional


114

Saya benar-benar kesulitan memahami perbedaan antara paradigma pemrograman prosedural dan fungsional .

Berikut adalah dua paragraf pertama dari entri Wikipedia tentang pemrograman fungsional :

Dalam ilmu komputer, pemrograman fungsional adalah paradigma pemrograman yang memperlakukan komputasi sebagai evaluasi fungsi matematika dan menghindari status dan data yang dapat berubah. Ini menekankan penerapan fungsi, berbeda dengan gaya pemrograman imperatif, yang menekankan perubahan status. Pemrograman fungsional berakar pada kalkulus lambda, sistem formal yang dikembangkan pada tahun 1930-an untuk menyelidiki definisi fungsi, aplikasi fungsi, dan rekursi. Banyak bahasa pemrograman fungsional dapat dilihat sebagai elaborasi pada kalkulus lambda.

Dalam praktiknya, perbedaan antara fungsi matematika dan pengertian "fungsi" yang digunakan dalam pemrograman imperatif adalah bahwa fungsi imperatif dapat memiliki efek samping, mengubah nilai status program. Karena itu, mereka kekurangan transparansi referensial, yaitu ekspresi bahasa yang sama dapat menghasilkan nilai yang berbeda pada waktu yang berbeda tergantung pada status program pelaksana. Sebaliknya, dalam kode fungsional, nilai keluaran suatu fungsi hanya bergantung pada argumen yang dimasukkan ke fungsi tersebut, jadi memanggil fungsi fdua kali dengan nilai yang sama untuk sebuah argumen xakan menghasilkan hasil yang sama.f(x)kedua waktu. Menghilangkan efek samping dapat membuat lebih mudah untuk memahami dan memprediksi perilaku suatu program, yang merupakan salah satu motivasi utama untuk pengembangan pemrograman fungsional.

Di paragraf 2 di mana dikatakan

Sebaliknya, dalam kode fungsional, nilai keluaran suatu fungsi hanya bergantung pada argumen yang dimasukkan ke fungsi, jadi memanggil fungsi fdua kali dengan nilai yang sama untuk sebuah argumen xakan menghasilkan hasil yang sama di f(x)kedua waktu.

Bukankah itu kasus yang sama persis untuk pemrograman prosedural?

Apa yang harus dicari dalam prosedural vs fungsional yang menonjol?


1
Tautan "Charming Python: Functional Programming in Python" dari Abafei rusak. Berikut ini sekumpulan tautan yang bagus: ibm.com/developerworks/linux/library/l-prog/index.html ibm.com/developerworks/linux/library/l-prog2/index.html
Chris Koknat

Aspek lain dari ini adalah penamaan. Misalnya. di JavaScript dan Common Lisp kami menggunakan istilah fungsi meskipun mereka diperbolehkan efek samping dan dalam Skema itu sama secara konsisten disebut proseduere. Fungsi CL yang murni dapat ditulis sebagai prosedur Skema fungsional murni. Hampir semua buku tentang Skema menggunakan istilah prosedur karena ini adalah tyerm yang digunakan dalam standar dan tidak ada hubungannya dengan prosedur atau fungsional.
Sylwester

Jawaban:


276

Pemrograman Fungsional

Pemrograman fungsional mengacu pada kemampuan untuk memperlakukan fungsi sebagai nilai.

Mari kita pertimbangkan analogi dengan nilai "reguler". Kita dapat mengambil dua nilai integer dan menggabungkannya menggunakan +operator untuk mendapatkan integer baru. Atau kita bisa mengalikan bilangan bulat dengan angka floating point untuk mendapatkan angka floating point.

Dalam pemrograman fungsional, kita dapat menggabungkan dua nilai fungsi untuk menghasilkan nilai fungsi baru menggunakan operator seperti compose atau lift . Atau kita dapat menggabungkan nilai fungsi dan nilai data untuk menghasilkan nilai data baru menggunakan operator seperti map atau lipat .

Perhatikan bahwa banyak bahasa memiliki kemampuan pemrograman fungsional - bahkan bahasa yang biasanya tidak dianggap sebagai bahasa fungsional. Bahkan Grandfather FORTRAN mendukung nilai fungsi, meskipun tidak menawarkan banyak cara operator penggabung fungsi. Untuk sebuah bahasa yang disebut "fungsional", ia perlu merangkul kapabilitas pemrograman fungsional secara besar-besaran.

Pemrograman Prosedural

Pemrograman prosedural mengacu pada kemampuan untuk merangkum urutan instruksi yang umum ke dalam prosedur sehingga instruksi tersebut dapat dipanggil dari banyak tempat tanpa menggunakan copy-dan-paste. Karena prosedur merupakan pengembangan yang sangat awal dalam pemrograman, kapabilitas hampir selalu terkait dengan gaya pemrograman yang diminta oleh pemrograman bahasa mesin atau perakitan: gaya yang menekankan gagasan lokasi penyimpanan dan instruksi yang memindahkan data di antara lokasi tersebut.

Kontras

Kedua gaya tersebut tidak benar-benar berlawanan - mereka hanya berbeda satu sama lain. Ada bahasa yang sepenuhnya merangkul kedua gaya (LISP, misalnya). Skenario berikut mungkin memberikan gambaran tentang beberapa perbedaan dalam kedua gaya tersebut. Mari tulis beberapa kode untuk persyaratan yang tidak masuk akal di mana kita ingin menentukan apakah semua kata dalam daftar memiliki jumlah karakter ganjil. Pertama, gaya prosedural:

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;
}

Saya akan menganggapnya sebagai mengingat bahwa contoh ini dapat dipahami. Sekarang, gaya fungsional:

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

Bekerja dari dalam ke luar, definisi ini melakukan hal-hal berikut:

  1. compose(odd, length)menggabungkan fungsi odddan lengthuntuk menghasilkan fungsi baru yang menentukan apakah panjang string ganjil.
  2. map(..., words)memanggil fungsi baru itu untuk setiap elemen di words, yang pada akhirnya mengembalikan daftar nilai boolean baru, masing-masing menunjukkan apakah kata yang bersangkutan memiliki jumlah karakter ganjil.
  3. apply(and, ...)menerapkan operator "dan" ke daftar yang dihasilkan, dan menyatukan semua boolean untuk menghasilkan hasil akhir.

Anda dapat melihat dari contoh-contoh ini bahwa pemrograman prosedural sangat memperhatikan nilai-nilai yang dipindahkan di dalam variabel dan secara eksplisit menjelaskan operasi yang diperlukan untuk menghasilkan hasil akhir. Sebaliknya, gaya fungsional menekankan kombinasi fungsi yang diperlukan untuk mengubah masukan awal menjadi keluaran akhir.

Contoh ini juga menunjukkan ukuran relatif yang khas dari kode prosedural versus kode fungsional. Lebih lanjut, ini menunjukkan bahwa karakteristik kinerja kode prosedural mungkin lebih mudah dilihat daripada kode fungsional. Pertimbangkan: apakah fungsi menghitung panjang semua kata dalam daftar, atau apakah masing-masing berhenti segera setelah menemukan kata panjang genap pertama? Di sisi lain, kode fungsional mengizinkan implementasi berkualitas tinggi untuk melakukan beberapa pengoptimalan yang cukup serius karena ini terutama mengekspresikan maksud daripada algoritma eksplisit.

Bacaan lebih lanjut

Pertanyaan ini sering muncul ... lihat, contoh:

Kuliah penghargaan Turing John Backus menjelaskan motivasi untuk pemrograman fungsional dengan sangat rinci:

Dapatkah Pemrograman Dibebaskan dari Gaya von Neumann?

Saya benar-benar tidak boleh menyebutkan makalah itu dalam konteks sekarang karena ini menjadi sangat teknis, cukup cepat. Saya tidak bisa menolak karena saya pikir itu benar-benar mendasar.


Adendum - 2013

Komentator menunjukkan bahwa bahasa kontemporer populer menawarkan gaya pemrograman lain di atas prosedural dan fungsional. Bahasa seperti itu sering kali menawarkan satu atau lebih gaya pemrograman berikut:

  • kueri (mis. pemahaman daftar, kueri terintegrasi bahasa)
  • aliran data (mis. iterasi implisit, operasi massal)
  • berorientasi objek (misalnya data dan metode yang dienkapsulasi)
  • berorientasi bahasa (misalnya sintaks khusus aplikasi, makro)

Lihat komentar di bawah ini untuk mengetahui contoh bagaimana contoh pseudo-code dalam tanggapan ini dapat memanfaatkan beberapa fasilitas yang tersedia dari gaya lain tersebut. Secara khusus, contoh prosedural akan mendapat manfaat dari penerapan hampir semua konstruksi tingkat yang lebih tinggi.

Contoh yang dipamerkan dengan sengaja menghindari pencampuran dalam gaya pemrograman lain ini untuk menekankan perbedaan antara dua gaya yang sedang dibahas.


1
Jawabannya bagus, tapi bisakah Anda menyederhanakan kode sedikit, misalnya: "function allOdd (words) {foreach (auto word in words) {odd (length (word)? Return false:;} return true;}"
Dainius

Gaya fungsional di sana cukup sulit dibaca dibandingkan dengan "gaya fungsional" di python: def odd_words (words): return [x untuk x dalam kata-kata jika ganjil (len (x))]
kotak

@boxed: odd_words(words)Definisi Anda melakukan sesuatu yang berbeda dari jawaban allOdd. Untuk pemfilteran dan pemetaan, pemahaman daftar sering kali lebih disukai, tetapi di sini fungsinya allOdddimaksudkan untuk mengurangi daftar kata menjadi satu nilai boolean.
ShinNoNoir

@WReach: Saya akan menulis contoh fungsional Anda seperti ini: function allOdd (words) {return and (odd (length (first (words))), allOdd (rest (words))); } Ini tidak lebih elegan dari contoh Anda, tetapi dalam bahasa rekursif-ekor itu akan memiliki karakteristik kinerja yang sama seperti gaya imperatif.
mishoo

@mishoo Bahasa harus baik rekursif ekor dan ketat dan hubungan pendek di operator dan agar asumsi Anda berlaku, saya yakin.
kqr

46

Perbedaan nyata antara pemrograman fungsional dan imperatif adalah pola pikirnya - programmer imperatif memikirkan variabel dan blok memori, sedangkan programmer fungsional berpikir, "Bagaimana cara mengubah data masukan saya menjadi data keluaran" - "program" Anda adalah saluran pipa dan set transformasi pada data untuk diambil dari Input ke Output. Itulah IMO bagian yang menarik, bukan bit "Jangan gunakan variabel".

Sebagai konsekuensi dari pola pikir ini, program KB biasanya mendeskripsikan apa yang akan terjadi, alih-alih mekanisme spesifik bagaimana hal itu akan terjadi - ini sangat berguna karena jika kita dapat dengan jelas menyatakan apa artinya "Pilih" dan "Di mana" dan "Agregat", kita bebas menukar implementasinya, seperti yang kita lakukan dengan AsParallel () dan tiba-tiba aplikasi single-threaded kita berskala ke n core.


dengan cara apa Anda dapat membandingkan keduanya menggunakan cuplikan contoh kode? benar-benar menghargainya
Philoxopher

1
@KerxPilo: Ini tugas yang sangat sederhana (tambahkan angka dari 1 hingga n). Imperatif: Ubah angka saat ini, ubah jumlah sejauh ini. Kode: int i, jumlah; jumlah = 0; untuk (i = 1; i <= n; i ++) {sum + = i; }. Fungsional (Haskell): Ambil daftar nomor yang malas, lipat bersama sambil menambahkan ke nol. Kode: foldl (+) 0 [1..n]. Maaf, tidak ada format dalam komentar.
dirkt

+1 untuk jawabannya. Dengan kata lain, pemrograman fungsional adalah tentang menulis fungsi tanpa efek samping bila memungkinkan, yaitu fungsi selalu mengembalikan hal yang sama ketika diberi parameter yang sama - itulah fondasinya. Jika Anda mengikuti pendekatan yang ekstrim, efek samping Anda (Anda selalu membutuhkannya) akan diisolasi dan fungsi lainnya hanya mengubah data masukan menjadi data keluaran.
Beluchin

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

Tidak, karena kode prosedural dapat memiliki efek samping. Misalnya, dapat menyimpan status antar panggilan.

Meskipun demikian, dimungkinkan untuk menulis kode yang memenuhi batasan ini dalam bahasa yang dianggap prosedural. Dan juga mungkin untuk menulis kode yang mematahkan batasan ini dalam beberapa bahasa yang dianggap fungsional.


1
Bisakah Anda menunjukkan contoh dan perbandingan? Sangat hargai jika Anda bisa.
Filsuf

8
Fungsi rand () di C memberikan hasil yang berbeda untuk setiap panggilan. Ini menyimpan status di antara panggilan. Ini tidak transparan secara referensial. Sebagai perbandingan, std :: max (a, b) di C ++ akan selalu mengembalikan hasil yang sama dengan argumen yang sama, dan tidak memiliki efek samping (yang saya tahu ...).
Andy Thomas

11

Saya tidak setuju dengan jawaban WReach. Mari kita dekonstruksi jawabannya sedikit untuk melihat dari mana ketidaksepakatan itu berasal.

Pertama, kodenya:

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;
}

dan

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

Hal pertama yang perlu diperhatikan adalah dia menggabungkan:

  • Fungsional
  • Berorientasi ekspresi dan
  • Sentris iterator

pemrograman, dan kehilangan kemampuan untuk gaya pemrograman iteratif untuk memiliki aliran kontrol yang lebih eksplisit daripada gaya fungsional yang khas.

Mari kita bicarakan ini dengan cepat.

Gaya yang berpusat pada ekspresi adalah gaya di mana hal-hal, sebanyak mungkin, mengevaluasi berbagai hal. Meskipun bahasa fungsional terkenal karena kecintaannya pada ekspresi, sebenarnya mungkin untuk memiliki bahasa fungsional tanpa ekspresi yang bisa disusun. Saya akan membuat satu, di mana tidak ada ekspresi, hanya pernyataan.

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

Ini hampir sama seperti yang diberikan sebelumnya, kecuali fungsi dirantai murni melalui rantai pernyataan dan binding.

Gaya pemrograman sentris iterator mungkin salah satu yang diambil oleh Python. Mari gunakan gaya yang murni iteratif, berpusat pada iterator:

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

Ini tidak berfungsi, karena setiap klausa adalah proses berulang, dan mereka terikat bersama oleh jeda eksplisit dan dimulainya kembali bingkai tumpukan. Sintaksnya mungkin sebagian terinspirasi dari bahasa fungsional, tetapi diterapkan pada perwujudan yang sepenuhnya berulang-ulang.

Tentu saja, Anda dapat mengompres ini:

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

Imperatif tidak terlihat terlalu buruk sekarang, eh? :)

Poin terakhir adalah tentang aliran kontrol yang lebih eksplisit. Mari kita tulis ulang kode aslinya untuk menggunakan ini:

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

Dengan menggunakan iterator, Anda dapat memiliki:

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

Jadi apa adalah titik bahasa fungsional jika perbedaan antara:

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;


Fitur definitif utama dari bahasa pemrograman fungsional adalah menghilangkan mutasi sebagai bagian dari model pemrograman yang khas. Orang sering mengartikan bahwa bahasa pemrograman fungsional tidak memiliki pernyataan atau menggunakan ekspresi, tetapi ini adalah penyederhanaan. Bahasa fungsional menggantikan komputasi eksplisit dengan deklarasi perilaku, yang kemudian dilakukan pengurangan oleh bahasa.

Membatasi diri Anda pada subset fungsionalitas ini memungkinkan Anda memiliki lebih banyak jaminan tentang perilaku program Anda, dan ini memungkinkan Anda untuk menyusunnya dengan lebih bebas.

Jika Anda memiliki bahasa fungsional, membuat fungsi baru umumnya sesederhana membuat fungsi yang terkait erat.

all = partial(apply, and)

Ini tidak sederhana, atau mungkin bahkan tidak mungkin, jika Anda belum mengontrol dependensi global suatu fungsi secara eksplisit. Fitur terbaik dari pemrograman fungsional adalah Anda dapat secara konsisten membuat abstraksi yang lebih umum dan percaya bahwa abstraksi tersebut dapat digabungkan menjadi keseluruhan yang lebih besar.


Anda tahu, saya cukup yakin operasi applyini tidak sama dengan afold atau reduce, meskipun saya setuju dengan kemampuan bagus untuk memiliki algoritme yang sangat umum.
Benediktus Lee

Saya belum pernah mendengar tentang applymean foldor reduce, tetapi bagi saya sepertinya itu harus dalam konteks ini untuk mengembalikan boolean.
Veedrac

Ah, ok, saya bingung dengan penamaannya. Terima kasih telah menyelesaikannya.
Benediktus Lee

6

Dalam paradigma prosedural (haruskah saya mengatakan "pemrograman terstruktur"?), Anda telah berbagi memori dan instruksi yang dapat berubah yang membaca / menulisnya dalam beberapa urutan (satu demi satu).

Dalam paradigma fungsional, Anda memiliki variabel dan fungsi (dalam pengertian matematis: variabel tidak bervariasi dari waktu ke waktu, fungsi hanya dapat menghitung sesuatu berdasarkan masukannya).

(Ini terlalu disederhanakan, misalnya, FPL biasanya memiliki fasilitas untuk bekerja dengan memori yang bisa berubah sedangkan bahasa prosedural seringkali dapat mendukung prosedur tingkat tinggi sehingga hal-hal tidak begitu jelas; tetapi ini akan memberi Anda gambaran)



2

Dalam pemrograman fungsional untuk memahami arti sebuah simbol (variabel atau nama fungsi) Anda hanya perlu mengetahui 2 hal - ruang lingkup saat ini dan nama simbol. Jika Anda memiliki bahasa yang berfungsi murni dengan kekekalan, keduanya adalah konsep "statis" (maaf untuk nama yang kelebihan muatan), artinya Anda dapat melihat keduanya - cakupan saat ini dan nama - hanya dengan melihat kode sumber.

Dalam pemrograman prosedural jika Anda ingin menjawab pertanyaan apa nilai di baliknya xAnda juga perlu tahu bagaimana Anda sampai di sana, ruang lingkup dan nama saja tidak cukup. Dan inilah yang saya anggap sebagai tantangan terbesar karena jalur eksekusi ini adalah properti "runtime" dan dapat bergantung pada begitu banyak hal yang berbeda, sehingga kebanyakan orang belajar untuk hanya men-debug dan tidak mencoba memulihkan jalur eksekusi.


1

Baru-baru ini saya memikirkan perbedaan dalam kaitannya dengan Masalah Ekspresi . Deskripsi Phil Wadler sering dikutip, tetapi jawaban yang diterima untuk pertanyaan ini mungkin lebih mudah diikuti. Pada dasarnya, tampaknya bahasa imperatif cenderung memilih satu pendekatan terhadap masalah tersebut, sedangkan bahasa fungsional cenderung memilih yang lain.


0

Satu perbedaan yang jelas antara dua paradigma pemrograman adalah status.

Dalam Pemrograman Fungsional, status dihindari. Sederhananya, tidak akan ada variabel yang diberi nilai.

Contoh:

def double(x):
    return x * 2

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

Namun, Pemrograman Prosedural menggunakan status.

Contoh:

def doubleLst(lst):
    for i in range(len(lst)):
        lst[i] = lst[i] * 2  # assigning of value i.e. mutation of state
    return lst
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.