2011-05-23 42 views
17

塊我有一組子程序看起來像這樣的一個Perl子程序:如何創建一個接受的代碼

sub foo_1($) { 
    my $name = shift; 
    my $f; 

    run_something(); 
    open($f, $name) or die ("Couldn't open $name"); 
    while (<$f>) { 
    //Something for foo_1() 
    } 
    close($f); 
    do_something_else(); 

} 

而且我有這樣四個或更多看起來相同的,唯一改變是while塊的主體。我想抽象一下,停止複製粘貼代碼。

  • 有沒有辦法編寫一個接受代碼塊並執行它的子程序?

爲了給出更多的上下文,不同的foo子例程是不同的有限狀態機(FSM),讀取不同文件的內容並將數據提供給散列引用。也許有比我想要完成的更聰明的事情。

+0

一般來說,函數原型是沒有必要的,我覺得它們有點用處。 – jiggy 2011-05-23 18:43:54

+0

@jiggy請你詳細說一下嗎? – 2011-05-23 21:01:58

+1

=>在這種情況下,使用'($)'原型可能並不意味着你的想法。它的意思是「給我一個論點」,但它也意味着「對這個論點強加標量上下文」。所以,如果你有一個數組中有一個元素,並且調用'foo_1 @ array',那麼'foo_1'將被傳遞數字'1',這是數組中元素的個數。要實際獲得第一個參數,您需要將其稱爲'foo_1 $ array [0]'。如果你沒有原型,那麼你可以稱它爲'foo_1 @ array',它可以正常工作。 – 2011-05-23 21:31:47

回答

34

Perl提供了一個調用的子程序的原型,使您可以編寫獲取類似內建函數的方式解析用戶潛艇系統。你想模擬的內建函數是mapgrepsort,它們每個都可以將一個塊作爲它們的第一個參數。

要做到這一點與原型,使用sub name (&) {...}其中&告訴Perl的第一個參數的函數或者是塊(有或沒有sub)或文字子程序\&mysub(&)原型指定了一個且只有一個參數,如果需要在代碼塊之後傳遞多個參數,則可以將其寫爲(&@),這意味着代碼塊後跟一個列表。

sub higher_order_fn (&@) { 
    my $code = \&{shift @_}; # ensure we have something like CODE 

    for (@_) { 
     $code->($_); 
    } 
} 

該子程序將在傳入列表的每個元素上運行傳入的塊。 \&{shift @_}看起來有點神祕,但它所做的是移出列表的第一個元素,它應該是一個代碼塊。 &{...}將該值解引用爲子例程(調用任何重載),然後\立即引用它。如果該值是一個CODE ref,那麼它將被返回不變。如果它是一個重載的對象,它會變成代碼。如果它不能被強制轉換成CODE,則會引發錯誤。

要調用這個子程序,你可以這樣寫:

higher_order_fn {$_ * 2} 1, 2, 3; 
# or 
higher_order_fn(sub {$_ * 2}, 1, 2, 3); 

(&@)的原型,可以讓你寫的參數作爲map/grep像塊使用高階函數的功能時纔會起作用。如果你使用它作爲一種方法,你應該省略原型,並用這種方式寫下來:

sub higher_order_method { 
    my $self = shift; 
    my $code = \&{shift @_}; 
    ... 
    $code->() for @_; 
} 
... 
$obj->higher_order_method(sub {...}, 'some', 'more', 'args', 'here'); 
+1

謝謝你的回答,埃裏克。 – 2011-05-23 21:05:32

+0

...繼續(我錯誤地輸入)我想知道爲什麼你使用\&{shift @_}。爲什麼這與人們給我的其他答案不同?我對Perl很新穎(但我在Haskell,Java,Scheme和C中編寫了一些代碼)。謝謝。 – 2011-05-23 21:15:28

+0

這是驗證參數實際上是代碼引用的簡單方法。擴展它意味着像'do {my $ x = shift; ref $ x eq'CODE'? $ x:overload :: Overloaded($ _ [0],'&{}')? \&$ x:死掉「不是代碼引用」}'。原型是一個編譯時間約束,它爲您檢查代碼引用,但可以通過用'&'sigil(或通過將代碼作爲方法調用)調用sub來繞過它。 '\&{shift @_''是對該旁路的額外檢查。 – 2011-05-23 21:19:40

11
sub bar { 
    my ($coderef) = @_; 
    ⁝ 
    $coderef->($f, @arguments); 
    ⁝ 
} 

bar(sub { my ($f) = @_; while … }, @other_arguments); 

或許與命名CODEREF少了幾分糾結:

my $while_sub = sub { 
    my ($f) = @_; 
    while … 
    ⁝ 
}; 
bar($while_sub, @other_arguments); 

編輯:該Higher-Order Perl本書充滿了這類節目的。

+0

非常感謝。我想我會讀那本書。聽起來很有趣! – 2011-05-23 21:31:21

9

你想要&原型。

sub foo(&@) { 
    my ($callback) = shift; 
    ... 
    $callback->(...); 
    ... 
} 

使得

foo { ... } ...; 

相當於

foo(sub { ... }, ...); 
相關問題