Mengapa prototipe fungsi Perl 5 buruk?

116

Dalam pertanyaan Stack Overflow lainnya, Leon Timmermans menegaskan:

Saya akan menyarankan Anda untuk tidak menggunakan prototipe. Mereka memiliki kegunaannya sendiri, tetapi tidak untuk kebanyakan kasus dan jelas tidak dalam kasus ini.

Mengapa ini mungkin benar (atau sebaliknya)? Saya hampir selalu menyediakan prototipe untuk fungsi Perl saya, dan saya belum pernah melihat orang lain mengatakan hal buruk tentang penggunaannya.

Alnitak
sumber
Saya juga penasaran. Satu-satunya saat saya tidak menggunakannya adalah ketika saya menelepon dengan sejumlah variabel argumen.
Paul Tomblin
7
Bolehkah saya merekomendasikan agar Anda membaca artikel, "Perl Prototypesanggap Berbahaya" ?
tchrist

Jawaban:

121

Prototipe tidak buruk jika digunakan dengan benar. Kesulitannya adalah bahwa prototipe Perl tidak bekerja seperti yang sering diharapkan orang. Orang dengan latar belakang dalam bahasa pemrograman lain cenderung mengharapkan prototipe menyediakan mekanisme untuk memeriksa apakah pemanggilan fungsi sudah benar: yaitu, bahwa mereka memiliki jumlah dan jenis argumen yang tepat. Prototipe Perl tidak cocok untuk tugas ini. Itu penyalahgunaan yang buruk. Prototipe Perl memiliki tujuan tunggal dan sangat berbeda:

Prototipe memungkinkan Anda menentukan fungsi yang berperilaku seperti fungsi bawaan.

  • Tanda kurung adalah opsional.
  • Konteks dipaksakan pada argumen.

Misalnya, Anda dapat mendefinisikan fungsi seperti ini:

sub mypush(\@@) { ... }

dan menyebutnya sebagai

mypush @array, 1, 2, 3;

tanpa perlu menulis \untuk mengambil referensi ke array.

Singkatnya, prototipe memungkinkan Anda membuat gula sintaksis Anda sendiri. Misalnya kerangka kerja Moose menggunakannya untuk meniru sintaks OO yang lebih umum.

Ini sangat berguna tetapi prototipe sangat terbatas:

  • Mereka harus terlihat pada waktu kompilasi.
  • Mereka bisa dilewati.
  • Menyebarkan konteks ke argumen dapat menyebabkan perilaku yang tidak terduga.
  • Mereka dapat mempersulit panggilan fungsi menggunakan apa pun selain formulir yang ditentukan secara ketat.

Lihat Prototipe di perlsub untuk semua detail berdarah.

Michael Carman
sumber
2
Saya telah menerima jawaban ini karena saya merasa paling tepat menjawab pertanyaan - prototipe tidak buruk secara intrinsik, hanya bagaimana Anda menggunakannya.
Alnitak
2
Di sisi lain, prototipe Moose adalah / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method::Signatures
Kent Fredric
Jadi mereka keliru, lalu?
Peter Mortensen
69

Masalahnya adalah bahwa prototipe fungsi Perl tidak melakukan apa yang orang pikir mereka lakukan. Tujuannya adalah untuk memungkinkan Anda menulis fungsi yang akan diurai seperti fungsi bawaan Perl.

Pertama-tama, panggilan metode mengabaikan prototipe sepenuhnya. Jika Anda melakukan pemrograman OO, tidak masalah prototipe apa yang dimiliki metode Anda. (Jadi mereka seharusnya tidak memiliki prototipe apa pun.)

Kedua, prototipe tidak ditegakkan secara ketat. Jika Anda memanggil subrutin dengan &function(...), prototipe akan diabaikan. Jadi mereka tidak benar-benar memberikan keamanan jenis apa pun.

Ketiga, mereka adalah aksi seram dari jarak jauh. (Terutama $prototipe, yang menyebabkan parameter terkait dievaluasi dalam konteks skalar, bukan konteks daftar default.)

Secara khusus, mereka mempersulit penerusan parameter dari array. Sebagai contoh:

my @array = qw(a b c);

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

sub foo ($;$$) { print "@_\n" }

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

cetakan:

a b c
a b
a b c
3
b
a b c

bersama dengan 3 peringatan tentang main::foo() called too early to check prototype(jika peringatan diaktifkan). Masalahnya adalah bahwa sebuah array (atau potongan array) yang dievaluasi dalam konteks skalar mengembalikan panjang dari array tersebut.

Jika Anda perlu menulis fungsi yang berfungsi seperti built-in, gunakan prototipe. Jika tidak, jangan gunakan prototipe.

Catatan: Perl 6 akan memiliki prototipe yang sepenuhnya dirubah dan sangat berguna. Jawaban ini hanya berlaku untuk Perl 5.

cjm
sumber
Tetapi mereka tetap memberikan pemeriksaan yang berguna bahwa pemanggil dan sub-sub Anda menggunakan jumlah argumen yang sama, jadi apa yang salah dengan itu?
Paul Tomblin
2
Tidak; konsensus umum adalah bahwa prototipe fungsi Perl pada dasarnya tidak memberikan manfaat. Anda mungkin juga tidak peduli dengan mereka, setidaknya di Perl 5. Perl 6 mungkin cerita yang berbeda (lebih baik).
Jonathan Leffler
5
Ada cara yang lebih baik untuk memvalidasi argumen, seperti Params :: Validate module: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo
10
Koreksi: pemotongan array mengembalikan daftar , jadi potongan array dalam konteks skalar mengembalikan elemen akhir dari daftar. Permintaan kedua dari terakhir Anda untuk foo()cetakan 2 karena itu adalah elemen terakhir dalam dua potongan elemen Anda. Ubah ke my @array = qw(foo bar baz)dan Anda akan melihat perbedaannya. (Sebagai tambahan, inilah mengapa saya tidak menginisialisasi array / daftar ke urutan numerik berbasis 0- atau 1 dalam kode demonstratif yang dibuang. Kebingungan antara indeks, jumlah, dan elemen dalam konteks telah menggigit saya lebih dari sekali. Konyol tapi benar.)
pilcrow
2
@pilcrow: Saya mengedit jawaban untuk digunakan a b cagar maksud Anda lebih jelas.
Flimm
30

Saya setuju dengan dua poster di atas. Secara umum, penggunaan $harus dihindari. Prototip hanya berguna ketika menggunakan blok argumen ( &), gumpalan ( *), atau prototipe referensi ( \@, \$, \%, \*)

Leon Timmermans
sumber
Secara umum, mungkin, tetapi saya ingin menyebutkan dua pengecualian: Pertama, ($)prototipe membuat operator bernama unary, yang dapat berguna (tentu saja Perl menganggapnya berguna; saya juga, kadang-kadang). Kedua, ketika mengganti built-in (baik melalui import atau menggunakan CORE :: GLOBAL: :), Anda secara umum harus tetap menggunakan prototipe apa pun yang ada di dalamnya, bahkan jika itu termasuk $, atau Anda mungkin akan mengejutkan programmer (Anda sendiri, bahkan) dengan konteks daftar di mana built-in akan menyediakan konteks skalar.
The Sidhekin
4

Beberapa orang, melihat prototipe subrutin Perl, berpikir itu berarti sesuatu yang tidak:

sub some_sub ($$) { ... }

Bagi Perl, itu berarti parser mengharapkan dua argumen. Ini adalah cara Perl untuk membiarkan Anda membuat subrutin yang berperilaku seperti built-in, yang semuanya tahu apa yang diharapkan dari kode berikutnya. Anda dapat membaca tentang prototipe di perlsub

Tanpa membaca dokumentasi, orang menebak bahwa prototipe merujuk pada pemeriksaan argumen waktu proses atau sesuatu yang serupa yang mereka lihat dalam bahasa lain. Seperti kebanyakan hal yang ditebak orang tentang Perl, mereka ternyata salah.

Namun, dimulai dengan Perl v5.20, Perl memiliki fitur, eksperimental saat saya menulis ini, yang memberikan sesuatu yang lebih seperti apa yang diharapkan pengguna dan apa. Tanda tangan subrutin Perl menjalankan penghitungan argumen waktu, penetapan variabel, dan pengaturan default:

use v5.20;
use feature qw(signatures);
no warnings qw(experimental::signatures);

animals( 'Buster', 'Nikki', 'Godzilla' );

sub animals ($cat, $dog, $lizard = 'Default reptile') { 
    say "The cat is $cat";
    say "The dog is $dog";
    say "The lizard is $lizard";
    }

Ini adalah fitur yang mungkin Anda inginkan jika Anda mempertimbangkan prototipe.

brian d foy
sumber