เหตุใดต้นแบบฟังก์ชันของ Perl 5 จึงไม่ดี


116

ในคำถาม Stack Overflow อื่น Leon Timmermansยืนยันว่า:

ฉันขอแนะนำว่าอย่าใช้ต้นแบบ พวกเขามีการใช้งาน แต่ไม่ใช่สำหรับกรณีส่วนใหญ่และไม่แน่นอนในกรณีนี้

เหตุใดจึงอาจเป็นจริง (หรืออย่างอื่น) ฉันมักจะจัดหาต้นแบบสำหรับฟังก์ชัน Perl ของฉันและฉันไม่เคยเห็นใครพูดอะไรไม่ดีเกี่ยวกับการใช้งานมาก่อน


ฉันก็อยากรู้เหมือนกัน ครั้งเดียวที่ฉันไม่ใช้มันคือเมื่อฉันเรียกด้วยอาร์กิวเมนต์ที่แปรผัน
Paul Tomblin

7
ผมขอแนะนำให้คุณอ่านบทความ“Perl ต้นแบบพิจารณาอันตราย” ?
tchrist

คำตอบ:


121

ต้นแบบไม่เลวหากใช้อย่างถูกต้อง ปัญหาคือต้นแบบของ Perl ไม่ได้ผลอย่างที่คนทั่วไปคาดหวัง ผู้ที่มีพื้นฐานในภาษาโปรแกรมอื่น ๆ มักจะคาดหวังให้ต้นแบบมีกลไกในการตรวจสอบว่าการเรียกฟังก์ชันนั้นถูกต้องนั่นคือพวกเขามีจำนวนและประเภทของอาร์กิวเมนต์ที่ถูกต้อง ต้นแบบของ Perl ไม่เหมาะกับงานนี้ เป็นการใช้ผิดวิธีที่ไม่ดี ต้นแบบของ Perl มีจุดประสงค์ที่เป็นเอกพจน์และแตกต่างกันมาก:

ต้นแบบช่วยให้คุณสามารถกำหนดฟังก์ชันที่ทำงานเหมือนฟังก์ชันในตัว

  • วงเล็บเป็นทางเลือก
  • บริบทถูกกำหนดให้กับอาร์กิวเมนต์

ตัวอย่างเช่นคุณสามารถกำหนดฟังก์ชันดังนี้:

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

และเรียกมันว่า

mypush @array, 1, 2, 3;

โดยไม่จำเป็นต้องเขียน\เพื่ออ้างอิงถึงอาร์เรย์

สรุปได้ว่าต้นแบบช่วยให้คุณสร้างน้ำตาลสังเคราะห์ของคุณเองได้ ตัวอย่างเช่น Moose framework ใช้เพื่อเลียนแบบไวยากรณ์ OO ทั่วไป

สิ่งนี้มีประโยชน์มาก แต่ต้นแบบมีข้อ จำกัด มาก:

  • ต้องมองเห็นได้ในเวลาคอมไพล์
  • สามารถข้ามได้
  • การเผยแพร่บริบทไปยังอาร์กิวเมนต์อาจทำให้เกิดพฤติกรรมที่ไม่คาดคิด
  • พวกเขาสามารถเรียกใช้ฟังก์ชันโดยใช้สิ่งอื่นนอกเหนือจากรูปแบบที่กำหนดไว้อย่างเคร่งครัดได้ยาก

ดูต้นแบบใน perlsub สำหรับรายละเอียดที่เต็มไปด้วยเลือดทั้งหมด


2
ฉันยอมรับคำตอบนี้เพราะฉันรู้สึกว่ามันตอบคำถามได้ดีที่สุด - ต้นแบบไม่ได้เลวร้ายอย่างแท้จริง แต่เป็นเพียงวิธีที่คุณใช้
Alnitak

2
Moose ต้นแบบในทางกลับกัน are / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method:: ลายเซ็น
Kent Fredric


ดังนั้นพวกเขาจึงเรียกชื่อผิด?
Peter Mortensen

69

ปัญหาคือต้นแบบฟังก์ชันของ Perl ไม่ได้ทำในสิ่งที่คนทั่วไปคิด วัตถุประสงค์ของพวกเขาคือเพื่อให้คุณสามารถเขียนฟังก์ชันที่จะแยกวิเคราะห์เช่นฟังก์ชันในตัวของ Perl

ประการแรกการเรียกเมธอดละเว้นต้นแบบโดยสิ้นเชิง หากคุณกำลังเขียนโปรแกรม OO ไม่สำคัญว่าต้นแบบของคุณจะมีวิธีการใด (ดังนั้นจึงไม่ควรมีต้นแบบใด ๆ )

ประการที่สองไม่ได้บังคับใช้ต้นแบบอย่างเคร่งครัด หากคุณเรียกรูทีนย่อยด้วย&function(...)ต้นแบบจะถูกละเว้น ดังนั้นจึงไม่ได้ให้ความปลอดภัยทุกประเภท

ประการที่สามพวกมันเป็นแอ็คชั่นในระยะไกลที่น่ากลัว (โดยเฉพาะอย่างยิ่ง$ต้นแบบซึ่งทำให้พารามิเตอร์ที่เกี่ยวข้องถูกประเมินในบริบทสเกลาร์แทนที่จะเป็นบริบทรายการเริ่มต้น)

โดยเฉพาะอย่างยิ่งทำให้ส่งผ่านพารามิเตอร์จากอาร์เรย์ได้ยาก ตัวอย่างเช่น:

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]);

พิมพ์:

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

พร้อมกับ 3 คำเตือนเกี่ยวกับmain::foo() called too early to check prototype(หากเปิดใช้งานคำเตือน) ปัญหาคืออาร์เรย์ (หรือส่วนอาร์เรย์) ที่ประเมินในบริบทสเกลาร์จะส่งกลับความยาวของอาร์เรย์

หากคุณต้องการเขียนฟังก์ชันที่ทำหน้าที่เหมือนในตัวให้ใช้ต้นแบบ มิฉะนั้นอย่าใช้ต้นแบบ

หมายเหตุ: Perl 6 จะได้รับการปรับปรุงใหม่ทั้งหมดและต้นแบบที่มีประโยชน์มาก คำตอบนี้ใช้ได้กับ Perl 5 เท่านั้น


แต่พวกเขายังคงให้การตรวจสอบที่เป็นประโยชน์ว่าผู้โทรของคุณและผู้ย่อยใช้อาร์กิวเมนต์จำนวนเท่ากันแล้วเกิดอะไรขึ้น?
Paul Tomblin

2
ไม่มี ฉันทามติทั่วไปคือต้นแบบฟังก์ชัน Perl ไม่ให้ประโยชน์ใด ๆ คุณอาจไม่รำคาญพวกเขาเช่นกันอย่างน้อยใน Perl 5 Perl 6 อาจเป็นเรื่องราวที่แตกต่าง (ดีกว่า)
Jonathan Leffler

5
มีวิธีที่ดีกว่าในการตรวจสอบอาร์กิวเมนต์เช่น Params :: Validate module: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo

10
การแก้ไข: การแบ่งอาร์เรย์จะส่งคืนรายการดังนั้นส่วนอาร์เรย์ในบริบทสเกลาร์จะส่งกลับองค์ประกอบสุดท้ายของรายการ การเรียกfoo()พิมพ์ครั้งที่สองถึงสุดท้ายของคุณเนื่องจากเป็นองค์ประกอบสุดท้ายในชิ้นส่วนสององค์ประกอบของคุณ เปลี่ยนเป็นmy @array = qw(foo bar baz)แล้วคุณจะเห็นความแตกต่าง (นอกจากนี้นี่คือสาเหตุที่ฉันไม่เริ่มต้นอาร์เรย์ / รายการเป็นลำดับตัวเลข 0 หรือ 1 ในรหัสการสาธิตแบบโยนทิ้งความสับสนระหว่างดัชนีการนับและองค์ประกอบในบริบทได้กัดฉันมากกว่าหนึ่งครั้ง โง่ แต่จริง)
pilcrow

2
@pilcrow: ฉันแก้ไขคำตอบเพื่อใช้a b cเพื่อให้ประเด็นของคุณชัดเจนขึ้น
Flimm

30

ฉันเห็นด้วยกับสองโปสเตอร์ข้างต้น โดยทั่วไป$ควรหลีกเลี่ยงการใช้ ต้นแบบที่มีประโยชน์เฉพาะเมื่อใช้ข้อโต้แย้งบล็อก ( &) globs ( *) หรือต้นแบบอ้างอิง ( \@, \$, \%, \*)


โดยทั่วไปอาจเป็นไปได้ แต่ฉันต้องการพูดถึงข้อยกเว้นสองประการ: ประการแรก($)ต้นแบบสร้างตัวดำเนินการยูนารีที่มีชื่อซึ่งมีประโยชน์ (แน่นอนว่า Perl พบว่ามีประโยชน์ฉันก็มีเช่นกันในบางครั้ง) ประการที่สองเมื่อลบล้างบิวด์อิน (ไม่ว่าจะผ่านการนำเข้าหรือใช้ CORE :: GLOBAL: :) โดยทั่วไปคุณควรยึดติดกับต้นแบบใด ๆ ก็ตามที่มีอยู่ในตัวแม้ว่าจะมี a $หรือคุณอาจทำให้โปรแกรมเมอร์แปลกใจ (ตัวคุณเอง แม้) ด้วยบริบทรายการที่บิวท์อินจะให้บริบทสเกลาร์
The Sidhekin

4

บางคนเมื่อดูต้นแบบรูทีนย่อย Perl คิดว่ามันหมายถึงสิ่งที่ไม่ได้:

sub some_sub ($$) { ... }

สำหรับ Perl นั่นหมายความว่า parser คาดหวังสองอาร์กิวเมนต์ เป็นวิธีของ Perl ในการให้คุณสร้างรูทีนย่อยที่ทำงานเหมือนบิวท์อินซึ่งทั้งหมดนี้รู้ว่าจะเกิดอะไรขึ้นจากโค้ดที่สำเร็จ คุณสามารถอ่านเกี่ยวกับต้นแบบได้ในperlsub

หากไม่อ่านเอกสารผู้คนก็เดาได้ว่าต้นแบบอ้างถึงการตรวจสอบอาร์กิวเมนต์เวลารันหรือสิ่งที่คล้ายกันที่พวกเขาเคยเห็นในภาษาอื่น เช่นเดียวกับสิ่งที่คนส่วนใหญ่คาดเดาเกี่ยวกับ Perl พวกเขากลายเป็นคนผิด

อย่างไรก็ตามเริ่มต้นด้วย Perl v5.20 Perl มีคุณลักษณะทดลองตามที่ฉันเขียนสิ่งนี้ซึ่งให้สิ่งที่ผู้ใช้คาดหวังและสิ่งที่มากกว่า ลายเซ็นรูทีนย่อยของ Perl รันการนับอาร์กิวเมนต์เวลาการกำหนดตัวแปรและการตั้งค่าเริ่มต้น:

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

นี่คือคุณสมบัติที่คุณอาจต้องการหากคุณกำลังพิจารณาต้นแบบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.