Apakah rekursi merupakan fitur tersendiri?


116

... atau itu hanya latihan?

Saya menanyakan hal ini karena berdebat dengan profesor saya: Saya kehilangan kredit karena memanggil fungsi secara rekursif atas dasar bahwa kami tidak mencakup rekursi di kelas, dan argumen saya adalah bahwa kami mempelajarinya secara implisit dengan pembelajaran returndan metode.

Saya bertanya di sini karena saya curiga seseorang memiliki jawaban pasti.

Misalnya, apa perbedaan antara dua metode berikut:

public static void a() {
    return a();
    }

public static void b() {
    return a();
    }

Selain " aberlanjut selamanya" (dalam program sebenarnya, ini digunakan dengan benar untuk meminta pengguna lagi saat diberikan masukan yang tidak valid), apakah ada perbedaan mendasar antara adan b? Untuk kompiler yang tidak dioptimalkan, bagaimana mereka ditangani secara berbeda?

Pada akhirnya turun ke apakah dengan belajar return a()dari byang kami juga untuk itu belajar return a()dari a. Apakah kita?


24
Debat yang bagus. Saya ingin tahu apakah Anda menjelaskannya seperti ini kepada profesor Anda. Jika Anda melakukannya, saya pikir dia harus memberi Anda kredit yang hilang.
Michael Yaworski

57
Rekursi bahkan bukan konsep eksklusif untuk ilmu komputer. Fungsi Fibonacci, operator faktorial dan banyak hal lainnya dari matematika (dan mungkin bidang lain) (atau setidaknya dapat) diekspresikan secara rekursif. Apakah profesor menuntut Anda untuk melupakan hal-hal ini juga?
Theodoros Chatzigiannakis

34
Sebaliknya, profesor harus memberinya penghargaan ekstra, karena memberikan cara yang elegan untuk memecahkan masalah, atau untuk mengatakan pemikiran di luar kotak.

11
Apa tugasnya? Ini adalah masalah yang sering saya tanyakan, ketika Anda mengirimkan tugas pemrograman, apa yang ditandai, kemampuan Anda untuk memecahkan masalah atau kemampuan Anda untuk menggunakan apa yang telah Anda pelajari. Keduanya belum tentu sama.
Leon

35
FWIW, meminta masukan sampai benar bukanlah tempat yang baik untuk menggunakan rekursi, terlalu mudah untuk meluap-luap tumpukan. Untuk contoh khusus ini, akan lebih baik menggunakan sesuatu seperti a() { do { good = prompt(); } while (!good); }.
Kevin

Jawaban:


113

Untuk menjawab pertanyaan spesifik Anda: Tidak, dari sudut pandang belajar bahasa, rekursi bukanlah fitur. Jika profesor Anda benar-benar memberi Anda nilai karena menggunakan "fitur" yang belum dia ajarkan, itu salah.

Membaca yang tersirat, salah satu kemungkinannya adalah dengan menggunakan rekursi, Anda menghindari penggunaan fitur yang seharusnya menjadi hasil pembelajaran untuk kursusnya. Misalnya, mungkin Anda tidak menggunakan iterasi sama sekali, atau mungkin Anda hanya menggunakan forloop daripada menggunakan keduanya fordan while. Merupakan hal yang umum bahwa sebuah tugas bertujuan untuk menguji kemampuan Anda dalam melakukan hal-hal tertentu, dan jika Anda menghindarinya, profesor Anda tidak dapat memberi Anda nilai yang disisihkan untuk fitur tersebut. Namun, jika itu benar-benar penyebab nilai Anda hilang, profesor harus menganggap ini sebagai pengalaman belajarnya sendiri - jika menunjukkan hasil pembelajaran tertentu adalah salah satu kriteria untuk sebuah tugas, yang harus dijelaskan dengan jelas kepada siswa .

Karena itu, saya setuju dengan sebagian besar komentar dan jawaban lain bahwa iterasi adalah pilihan yang lebih baik daripada rekursi di sini. Ada beberapa alasan, dan sementara orang lain telah menyinggungnya sampai batas tertentu, saya tidak yakin mereka telah sepenuhnya menjelaskan pemikiran di baliknya.

Stack Overflows

Yang lebih jelas adalah bahwa Anda berisiko mendapatkan kesalahan stack overflow. Secara realistis, metode yang Anda tulis sangat tidak mungkin benar-benar mengarah ke metode tersebut, karena pengguna harus memberikan masukan yang salah berkali-kali untuk benar-benar memicu luapan tumpukan.

Namun, satu hal yang perlu diingat adalah bahwa tidak hanya metode itu sendiri, tetapi metode lain yang lebih tinggi atau lebih rendah dalam rantai panggilan akan ada di tumpukan. Karena itu, dengan santai melahap ruang tumpukan yang tersedia adalah hal yang sangat tidak sopan untuk dilakukan metode apa pun. Tidak ada yang ingin terus-menerus khawatir tentang ruang tumpukan kosong setiap kali mereka menulis kode karena risiko kode lain mungkin telah menggunakan banyak darinya secara tidak perlu.

Ini adalah bagian dari prinsip yang lebih umum dalam desain perangkat lunak yang disebut abstraksi. Pada dasarnya, saat Anda menelepon DoThing(), yang perlu Anda perhatikan hanyalah bahwa Sesuatu sudah selesai. Anda tidak perlu khawatir tentang detail implementasi tentang cara melakukannya. Tetapi penggunaan stack yang serakah melanggar prinsip ini, karena setiap bit kode harus mengkhawatirkan tentang berapa banyak stack yang dapat diasumsikannya dengan aman oleh kode di tempat lain di rantai panggilan.

Keterbacaan

Alasan lainnya adalah keterbacaan. Ideal yang harus dicita-citakan oleh kode adalah menjadi dokumen yang dapat dibaca manusia, di mana setiap baris mendeskripsikan apa yang dilakukannya. Ambil dua pendekatan ini:

private int getInput() {
    int input;
    do {
        input = promptForInput();
    } while (!inputIsValid(input))
    return input;
}

melawan

private int getInput() {
    int input = promptForInput();
    if(inputIsValid(input)) {
        return input;
    }
    return getInput();
}

Ya, keduanya berfungsi, dan ya keduanya cukup mudah dipahami. Tetapi bagaimana kedua pendekatan tersebut dapat dijelaskan dalam bahasa Inggris? Saya pikir itu akan menjadi seperti:

Saya akan meminta masukan sampai masukan tersebut valid, dan kemudian mengembalikannya

melawan

Saya akan meminta input, kemudian jika inputnya valid saya akan mengembalikannya, jika tidak saya mendapatkan input dan mengembalikan hasilnya sebagai gantinya

Mungkin Anda dapat memikirkan kata-kata yang tidak terlalu kikuk untuk yang terakhir, tetapi saya pikir Anda akan selalu menemukan bahwa yang pertama akan menjadi deskripsi yang lebih akurat, secara konseptual, tentang apa yang sebenarnya Anda coba lakukan. Ini tidak berarti rekursi selalu kurang terbaca. Untuk situasi di mana ia bersinar, seperti penjelajahan pohon, Anda dapat melakukan analisis berdampingan yang sama antara rekursi dan pendekatan lain dan Anda hampir pasti akan menemukan rekursi memberikan kode yang lebih jelas mendeskripsikan dirinya sendiri, baris demi baris.

Dalam isolasi, keduanya adalah poin kecil. Sangat tidak mungkin hal ini akan benar-benar menyebabkan stack overflow, dan peningkatan keterbacaan kecil. Tetapi program apa pun akan menjadi kumpulan dari banyak keputusan kecil ini, jadi meskipun terpisah itu tidak terlalu penting, penting untuk mempelajari prinsip di balik melakukannya dengan benar.


8
Dapatkah Anda memperluas pernyataan Anda bahwa rekursi bukanlah fitur? Saya telah menyatakan dalam jawaban saya bahwa ya, karena tidak semua kompiler mendukungnya.
Harry Johnston

5
Tidak semua bahasa mendukung rekursi, jadi ini bukan hanya soal memilih kompiler yang tepat - tapi Anda benar mengatakan bahwa "fitur" adalah deskripsi yang inheren ambigu, jadi cukup adil. Poin kedua Anda juga adil dari sudut pandang seseorang yang belajar program (seperti biasanya sekarang) tanpa memiliki latar belakang dalam pemrograman kode mesin terlebih dahulu. :-)
Harry Johnston

2
Perhatikan bahwa masalah 'keterbacaan' adalah masalah dengan sintaksis. Tidak ada yang secara inheren "tidak terbaca" tentang rekursi. Faktanya, induksi adalah cara termudah untuk mengekspresikan struktur data induktif, seperti loop dan list dan urutan dan sebagainya. Dan sebagian besar struktur data bersifat induktif.
nomen

6
Saya pikir Anda telah menumpuk dek dengan kata-kata Anda. Anda menjelaskan versi iteratif secara fungsional dan sebaliknya. Saya pikir deskripsi baris demi baris yang lebih adil dari keduanya adalah “Saya akan meminta masukan. Jika input tidak valid, saya akan terus mengulangi prompt sampai saya mendapatkan input yang valid. Lalu aku akan mengembalikannya. " vs “Saya akan meminta masukan. Jika inputnya valid, saya akan mengembalikannya. Jika tidak, saya akan mengembalikan hasil pengabaian. " (Anak-anak saya memahami konsep fungsional do-over ketika mereka berada di prasekolah, jadi saya pikir ini adalah ringkasan bahasa Inggris yang sah dari konsep rekursif di sini.)
pjs

2
@HarryJohnston Kurangnya dukungan untuk rekursi akan menjadi pengecualian untuk fitur yang sudah ada daripada kekurangan fitur baru. Secara khusus, dalam konteks pertanyaan ini, "fitur baru" berarti "perilaku yang berguna kita belum mengajarkan ada", yang tidak benar dari rekursi karena perpanjangan logis dari fitur yang telah diajarkan (yaitu, bahwa prosedur mengandung instruksi, dan panggilan prosedur adalah instruksi). Seolah-olah profesor mengajar penjumlahan siswa, lalu memarahinya karena menambahkan nilai yang sama lebih dari sekali karena "kita belum membahas perkalian".
nmclean

48

Untuk menjawab pertanyaan literal, bukan pertanyaan meta: rekursi adalah fitur, dalam arti tidak semua kompiler dan / atau bahasa mengizinkannya. Dalam praktiknya, ini diharapkan dari semua kompiler modern (biasa) - dan tentunya semua kompiler Java! - tapi itu tidak benar secara universal .

Sebagai contoh yang dibuat-buat mengapa rekursi mungkin tidak didukung, pertimbangkan kompilator yang menyimpan alamat pengembalian untuk suatu fungsi di lokasi statis; ini mungkin terjadi, misalnya, untuk kompiler untuk mikroprosesor yang tidak memiliki tumpukan.

Untuk kompiler seperti itu, ketika Anda memanggil fungsi seperti ini

a();

itu diimplementasikan sebagai

move the address of label 1 to variable return_from_a
jump to label function_a
label 1

dan definisi a (),

function a()
{
   var1 = 5;
   return;
}

diimplementasikan sebagai

label function_a
move 5 to variable var1
jump to the address stored in variable return_from_a

Mudah-mudahan masalah ketika Anda mencoba memanggil a()secara rekursif dalam kompiler seperti itu sudah jelas; kompilator tidak lagi tahu bagaimana untuk kembali dari panggilan luar, karena alamat pengirim telah ditimpa.

Untuk kompiler yang sebenarnya saya gunakan (akhir 70-an atau awal 80-an, menurut saya) tanpa dukungan untuk rekursi, masalahnya sedikit lebih halus daripada itu: alamat pengirim akan disimpan di tumpukan, seperti di kompiler modern, tetapi variabel lokal tidak t. (Secara teoritis ini berarti bahwa rekursi dimungkinkan untuk fungsi tanpa variabel lokal non-statis, tetapi saya tidak ingat apakah kompiler secara eksplisit mendukungnya atau tidak. Mungkin diperlukan variabel lokal implisit karena beberapa alasan.)

Melihat ke depan, saya dapat membayangkan skenario khusus - sistem yang sangat paralel, mungkin - di mana tidak harus menyediakan tumpukan untuk setiap utas dapat menguntungkan, dan oleh karena itu rekursi hanya diizinkan jika kompiler dapat merefaktornya menjadi loop. (Tentu saja kompiler primitif yang saya diskusikan di atas tidak mampu melakukan tugas-tugas rumit seperti kode refactoring.)


Misalnya, praprosesor C tidak mendukung rekursi di makro. Definisi makro berperilaku mirip dengan fungsi, tetapi Anda tidak dapat memanggilnya secara rekursif.
Sth

7
"Contoh yang dibuat-buat" tidak semuanya dibuat-buat: Standar Fortran 77 tidak mengizinkan fungsi memanggil dirinya sendiri secara rekursif-- alasannya cukup sesuai dengan yang Anda gambarkan. (Saya yakin alamat yang akan dituju saat fungsi selesai disimpan di akhir kode fungsi itu sendiri, atau sesuatu yang setara dengan pengaturan ini.) Lihat di sini untuk sedikit tentang itu.
alexis

5
Bahasa shader atau bahasa GPGPU (katakanlah, GLSL, Cg, OpenCL C) tidak mendukung rekursi, sebagai contoh. Sejauh ini, argumen "tidak semua bahasa mendukungnya" memang valid. Rekursi dianggap setara dengan tumpukan (tidak perlu tumpukan , tetapi perlu ada cara untuk menyimpan alamat pengembalian dan bingkai fungsi entah bagaimana ).
Damon

Kompiler Fortran yang saya kerjakan sedikit di awal tahun 1970-an tidak memiliki tumpukan panggilan. Setiap subrutin atau fungsi memiliki area memori statis untuk alamat pengirim, parameter, dan variabelnya sendiri.
Patricia Shanahan

2
Bahkan beberapa versi Turbo Pascal menonaktifkan rekursi secara default, dan Anda harus menyetel direktif kompilator untuk mengaktifkannya.
dan04

17

Guru ingin tahu apakah Anda pernah belajar atau belum. Rupanya Anda tidak menyelesaikan masalah dengan cara dia mengajari Anda ( cara yang baik ; iterasi), dan dengan demikian, menganggap bahwa Anda tidak. Saya mendukung solusi kreatif tetapi dalam kasus ini saya harus setuju dengan guru Anda karena alasan yang berbeda:
Jika pengguna memberikan masukan yang tidak valid terlalu banyak (yaitu dengan terus menekan enter), Anda akan memiliki pengecualian stack overflow dan solusi akan macet. Selain itu, solusi iteratif lebih efisien dan lebih mudah dirawat. Saya pikir itulah alasan guru Anda seharusnya memberi Anda.


2
Kami tidak diberitahu untuk melakukan tugas ini dengan cara tertentu; dan kami belajar tentang metode, bukan hanya iterasi. Selain itu, saya akan meninggalkan mana yang lebih mudah dibaca ke preferensi pribadi: Saya memilih apa yang tampak bagus bagi saya. Kesalahan SO masih baru bagi saya, meskipun gagasan bahwa rekursi adalah fitur dalam dan dari dirinya sendiri masih belum ditemukan.

3
"Saya akan meninggalkan mana yang lebih mudah dibaca sesuai keinginan pribadi". Sepakat. Rekursi bukanlah fitur Java. Ini adalah.
mike

2
@Vality: Penghapusan panggilan ekor? Beberapa JVM mungkin melakukannya, tetapi perlu diingat bahwa JVM juga perlu mempertahankan pelacakan tumpukan untuk pengecualian. Jika memungkinkan penghapusan panggilan ekor, pelacakan tumpukan, yang dibuat secara naif, mungkin menjadi tidak valid, sehingga beberapa JVM tidak melakukan TCE karena alasan itu.
icktoofay

5
Apa pun itu, mengandalkan pengoptimalan untuk mengurangi kerusakan kode Anda, adalah bentuk yang sangat buruk.
cHao

7
+1, lihat bahwa di Ubuntu baru-baru ini layar login rusak ketika pengguna menekan tombol Enter terus menerus, hal yang sama terjadi pada XBox
Sebastian

13

Mengurangi poin karena "kami tidak menutupi rekursi di kelas" sangat buruk. Jika Anda telah mempelajari cara memanggil fungsi A yang memanggil fungsi B yang memanggil fungsi C yang kembali ke B yang kembali ke A yang kembali ke pemanggil, dan guru tidak memberi tahu Anda secara eksplisit bahwa ini pasti fungsi yang berbeda (yang mana akan menjadi kasus di versi FORTRAN lama, misalnya), tidak ada alasan bahwa A, B dan C tidak bisa semuanya memiliki fungsi yang sama.

Di sisi lain, kami harus melihat kode sebenarnya untuk memutuskan apakah dalam kasus khusus Anda menggunakan rekursi benar-benar hal yang benar untuk dilakukan. Tidak banyak detail, tetapi kedengarannya salah.


10

Ada banyak sudut pandang untuk dilihat terkait pertanyaan spesifik yang Anda ajukan, tetapi yang dapat saya katakan adalah bahwa dari sudut pandang belajar bahasa, rekursi bukanlah fitur tersendiri. Jika profesor Anda benar-benar memberi Anda nilai untuk menggunakan "fitur" yang belum dia ajarkan, itu salah tapi seperti yang saya katakan, ada sudut pandang lain yang perlu dipertimbangkan di sini yang sebenarnya membuat profesor benar saat mengurangi poin.

Dari apa yang dapat saya simpulkan dari pertanyaan Anda, menggunakan fungsi rekursif untuk meminta masukan jika terjadi kegagalan masukan bukanlah praktik yang baik karena setiap panggilan fungsi rekursif didorong ke tumpukan. Karena rekursi ini didorong oleh input pengguna, dimungkinkan untuk memiliki fungsi rekursif tak terbatas dan dengan demikian menghasilkan StackOverflow.

Tidak ada perbedaan antara 2 contoh yang Anda sebutkan dalam pertanyaan Anda dalam arti apa yang mereka lakukan (tetapi berbeda dengan cara lain) - Dalam kedua kasus, alamat pengirim dan semua info metode sedang dimuat ke stack. Dalam kasus rekursi, alamat yang dikembalikan hanyalah baris tepat setelah pemanggilan metode (tentu saja ini tidak persis seperti yang Anda lihat dalam kode itu sendiri, melainkan dalam kode yang dibuat kompilator). Di Java, C, dan Python, rekursi cukup mahal dibandingkan dengan iterasi (secara umum) karena memerlukan alokasi stack frame baru. Belum lagi Anda bisa mendapatkan pengecualian stack overflow jika input tidak valid terlalu banyak.

Saya percaya profesor mengurangi poin karena rekursi dianggap sebagai subjeknya sendiri dan tidak mungkin seseorang yang tidak memiliki pengalaman pemrograman akan memikirkan rekursi. (Tentu saja itu tidak berarti mereka tidak akan melakukannya, tetapi itu tidak mungkin).

IMHO, saya pikir profesor benar dengan mengurangi poin Anda. Anda dapat dengan mudah mengambil bagian validasi ke metode lain dan menggunakannya seperti ini:

public bool foo() 
{
  validInput = GetInput();
  while(!validInput)
  {
    MessageBox.Show("Wrong Input, please try again!");
    validInput = GetInput();
  }
  return hasWon(x, y, piece);
}

Jika apa yang Anda lakukan memang bisa diselesaikan dengan cara itu maka apa yang Anda lakukan adalah amalan yang buruk dan harus dihindari.


Tujuan dari metode itu sendiri adalah untuk memvalidasi input, kemudian memanggil dan mengembalikan hasil dari metode lain (itulah mengapa metode tersebut mengembalikan sendiri). Untuk lebih spesifik, ia memeriksa apakah gerakan dalam permainan Tic-Tac-Toe valid, lalu kembali hasWon(x, y, piece)(untuk hanya memeriksa baris dan kolom yang terpengaruh).

Anda dapat dengan mudah mengambil bagian validasi HANYA dan memasukkannya ke dalam metode lain yang disebut "GetInput" misalnya dan kemudian menggunakannya seperti yang saya tulis di jawaban saya. Saya telah mengedit jawaban saya dengan tampilan yang seharusnya. Tentu saja Anda dapat membuat GetInput mengembalikan tipe yang menyimpan informasi yang Anda butuhkan.
Yonatan Nir

1
Yonatan Nir: Kapan rekursi merupakan praktik yang buruk? Mungkin JVM akan meledak karena VM Hotspot tidak dapat dioptimalkan karena keamanan kode byte dan hal-hal akan menjadi argumen yang bagus. Bagaimana kode Anda berbeda selain menggunakan pendekatan yang berbeda?

1
Rekursi tidak selalu merupakan praktik yang buruk tetapi jika dapat dihindari dan menjaga kode tetap bersih dan mudah dirawat, maka hal itu harus dihindari. Di Java, C, dan Python, rekursi cukup mahal dibandingkan dengan iterasi (secara umum) karena memerlukan alokasi stack frame baru. Di beberapa compiler C, seseorang dapat menggunakan flag compiler untuk menghilangkan overhead ini, yang mengubah tipe rekursi tertentu (sebenarnya, tipe tail call tertentu) menjadi lompatan alih-alih panggilan fungsi.
Yonatan Nir

1
Tidak jelas, tetapi jika Anda mengganti perulangan dengan jumlah iterasi yang tidak terbatas dengan rekursi, maka itu buruk. Java tidak menjamin pengoptimalan tail call, jadi Anda mungkin dengan mudah kehabisan ruang stack. Di Java, jangan gunakan rekursi, kecuali Anda dijamin memiliki jumlah iterasi yang terbatas (biasanya logaritmik dibandingkan dengan ukuran total data).
hyde

6

Mungkin dosen Anda belum mengajarkannya, tetapi sepertinya Anda siap mempelajari kelebihan dan kekurangan rekursi.

Keuntungan utama rekursi adalah bahwa algoritma rekursif seringkali lebih mudah dan lebih cepat untuk ditulis.

Kerugian utama dari rekursi adalah bahwa algoritma rekursif dapat menyebabkan luapan tumpukan, karena setiap tingkat rekursi membutuhkan bingkai tumpukan tambahan untuk ditambahkan ke tumpukan.

Untuk kode produksi, di mana penskalaan dapat menghasilkan lebih banyak tingkat rekursi dalam produksi daripada dalam pengujian unit pemrogram, kerugiannya biasanya lebih besar daripada keuntungannya, dan kode rekursif sering dihindari jika praktis.


1
Algoritme rekursif yang berpotensi berisiko selalu dapat dengan mudah ditulis ulang untuk menggunakan tumpukan eksplisit - bagaimanapun, tumpukan panggilan hanyalah tumpukan. Dalam kasus ini, jika Anda menulis ulang solusi untuk menggunakan tumpukan, itu akan terlihat konyol - bukti lebih lanjut bahwa jawaban rekursif tidak terlalu bagus.
Aaronaught

1
Jika stack overflows menjadi masalah, Anda harus menggunakan bahasa / runtime yang mendukung pengoptimalan tail call, seperti .NET 4.0 atau bahasa pemrograman fungsional apa pun
Sebastian

Tidak semua rekursi adalah panggilan ekor.
Warren Dew

6

Mengenai pertanyaan spesifik, apakah rekursi merupakan fitur, saya cenderung mengatakan ya, tetapi setelah menafsirkan ulang pertanyaan tersebut. Ada pilihan desain umum dari bahasa dan kompiler yang memungkinkan rekursi, dan bahasa lengkap Turing memang ada yang tidak mengizinkan rekursi sama sekali . Dengan kata lain, rekursi adalah kemampuan yang diaktifkan oleh pilihan tertentu dalam desain bahasa / kompilator.

  • Mendukung fungsi kelas satu memungkinkan rekursi dengan asumsi yang sangat minimal; lihat menulis loop di Unlambda sebagai contoh, atau ekspresi Python tumpul ini yang tidak berisi referensi sendiri, loop, atau tugas:

    >>> map((lambda x: lambda f: x(lambda g: f(lambda v: g(g)(v))))(
    ...   lambda c: c(c))(lambda R: lambda n: 1 if n < 2 else n * R(n - 1)),
    ...   xrange(10))
    [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
    
  • Bahasa / kompiler yang menggunakan pengikatan akhir , atau yang mendefinisikan deklarasi maju , memungkinkan rekursi. Misalnya, sementara Python mengizinkan kode di bawah ini, itu adalah pilihan desain (pengikatan akhir), bukan persyaratan untuk sistem lengkap Turing . Fungsi yang saling rekursif sering bergantung pada dukungan untuk deklarasi maju.

    factorial = lambda n: 1 if n < 2 else n * factorial(n-1)
    
  • Bahasa yang diketik secara statis yang memungkinkan jenis yang ditentukan secara rekursif berkontribusi untuk memungkinkan rekursi. Lihat implementasi Y Combinator ini di Go . Tanpa tipe yang didefinisikan secara rekursif, masih mungkin untuk menggunakan rekursi di Go, tapi saya yakin kombinator Y secara khusus tidak mungkin.


1
Hal ini membuat kepala saya meledak, terutama Unlambda +1
John Powell

Kombinator titik tetap sulit. Ketika saya memutuskan untuk belajar pemrograman fungsional, saya memaksakan diri untuk mempelajari kombinator Y sampai saya memahaminya, dan kemudian menerapkannya untuk menulis fungsi berguna lainnya. Butuh waktu beberapa saat, tapi itu sangat berharga.
wberry

5

Dari apa yang dapat saya simpulkan dari pertanyaan Anda, menggunakan fungsi rekursif untuk meminta masukan jika terjadi kegagalan masukan bukanlah praktik yang baik. Mengapa?

Karena setiap panggilan fungsi rekursif didorong ke stack. Karena rekursi ini didorong oleh input pengguna, dimungkinkan untuk memiliki fungsi rekursif tak terbatas dan dengan demikian menghasilkan StackOverflow :-p

Memiliki loop non rekursif untuk melakukan ini adalah cara yang tepat.


Sebagian besar metode yang dimaksud, dan tujuan dari metode itu sendiri, adalah untuk memvalidasi masukan melalui berbagai pemeriksaan. Proses dimulai dari awal lagi jika masukan tidak valid hingga masukan benar (seperti yang diinstruksikan).

4
@fay Tetapi jika input tidak valid terlalu banyak, Anda akan mendapatkan StackOverflowError. Rekursi lebih elegan, tetapi di mata saya, biasanya lebih menjadi masalah daripada loop biasa (karena tumpukan).
Michael Yaworski

1
Itu poin yang menarik dan bagus, kalau begitu. Saya tidak mempertimbangkan kesalahan itu. Padahal, dapatkah efek yang sama dapat dicapai melalui while(true)pemanggilan metode yang sama? Jika demikian, saya tidak akan mengatakan ini mendukung perbedaan apa pun antara rekursi, baik untuk diketahui apa adanya.

1
@ay while(true)adalah pengulangan tanpa batas. Kecuali Anda memiliki breakpernyataan, saya tidak mengerti maksudnya, kecuali Anda mencoba merusak program Anda lol. Maksud saya adalah, jika Anda memanggil metode yang sama (itu rekursi), terkadang akan memberi Anda StackOverflowError , tetapi jika Anda menggunakan whileatau forloop, itu tidak akan terjadi. Masalahnya tidak ada dengan loop biasa. Mungkin saya salah paham, tapi jawaban saya untuk Anda adalah tidak.
Michael Yaworski

4
Sejujurnya, bagi saya ini sepertinya mungkin alasan sebenarnya profesor mengambil nilai =) Dia mungkin tidak menjelaskannya dengan baik, tetapi merupakan keluhan yang sah untuk mengatakan bahwa Anda menggunakannya dengan cara yang akan dianggap gaya yang sangat buruk jika tidak secara terang-terangan cacat dalam kode yang lebih serius.
Komandan Ketumbar Salamander

3

Rekursi adalah konsep pemrograman , fitur (seperti iterasi), dan praktik . Seperti yang Anda lihat dari tautan, ada domain besar penelitian yang didedikasikan untuk subjek tersebut. Mungkin kita tidak perlu membahas topik sedalam itu untuk memahami poin-poin ini.

Rekursi sebagai fitur

Sederhananya, Java mendukungnya secara implisit, karena memungkinkan sebuah metode (yang pada dasarnya adalah fungsi khusus) untuk memiliki "pengetahuan" tentang dirinya sendiri dan metode lain yang menyusun kelasnya. Pertimbangkan bahasa di mana ini bukan masalahnya: Anda akan dapat menulis tubuh metode itu a, tetapi Anda tidak akan dapat menyertakan panggilan ke adalamnya. Satu-satunya solusi adalah menggunakan iterasi untuk mendapatkan hasil yang sama. Dalam bahasa seperti itu, Anda harus membuat perbedaan antara fungsi yang menyadari keberadaannya sendiri (dengan menggunakan token sintaks tertentu), dan yang tidak! Sebenarnya, seluruh kelompok bahasa membuat perbedaan itu (lihat keluarga Lisp dan ML misalnya). Menariknya, Perl bahkan mengizinkan fungsi anonim (disebutlambdas ) untuk memanggil dirinya sendiri secara rekursif (sekali lagi, dengan sintaks khusus).

tidak ada rekursi?

Untuk bahasa yang bahkan tidak mendukung kemungkinan rekursi, seringkali ada solusi lain, dalam bentuk kombinator titik tetap , tetapi masih membutuhkan bahasa untuk mendukung fungsi yang disebut objek kelas pertama (yaitu objek yang mungkin dimanipulasi dalam bahasa itu sendiri).

Rekursi sebagai praktik

Memiliki fitur itu tersedia dalam suatu bahasa tidak berarti itu idiomatis. Di Java 8, ekspresi lambda telah disertakan, jadi mungkin akan lebih mudah untuk mengadopsi pendekatan fungsional untuk pemrograman. Namun, ada pertimbangan praktis:

  • sintaksnya masih belum terlalu ramah rekursi
  • kompiler mungkin tidak dapat mendeteksi praktik itu dan mengoptimalkannya

Garis bawah

Untungnya (atau lebih tepatnya, untuk kemudahan penggunaan), Java membiarkan metode menyadari dirinya sendiri secara default, dan dengan demikian mendukung rekursi, jadi ini sebenarnya bukan masalah praktis, tetapi masih tetap bersifat teoritis, dan saya kira itu guru Anda ingin menanganinya secara spesifik. Selain itu, mengingat perkembangan bahasa baru-baru ini, mungkin akan berubah menjadi sesuatu yang penting di masa depan.

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.