2013-11-04 52 views
5

前面的_fact的確切功能/目的是什麼,它如何可以等效寫入?神祕的*在嵌套的子

sub fact { 
    my ($n) = @_; 

    local *_fact = sub { 
     my ($n, $prod) = @_; 
     return $prod if $n == 0; 
     return _fact($n-1, $n*$prod); 
    }; 

    return _fact($n, 1); 
} 

fact($n); 

回答

2

檢查typeglob aliases

上面的例子應該使用匿名子/關閉寫:

sub fact { 
    my ($n) = @_; 

    my $_fact; 
    $_fact = sub { 
     my ($n, $prod) = @_; 
     return $prod if $n == 0; 
     return __SUB__->($n-1, $n*$prod); 
    }; 

    return $_fact->($n, 1); 
} 
+0

「?什麼是_fact前*的確切功能/目的」 –

+0

@ikegami http://codepad.org/80KSqye4怎麼樣? –

+0

它不會泄漏,但這是一個糟糕的解決方案。它消除了尾部遞歸,這顯然是該子設計的一個指導因素。 '$ n? $ n * fact($ n-1):1'否則就足夠了。消除尾部遞歸使其無處不在。用'my $ rv = $ _fact(...);更改外部子結束符undef $ _fact; $ rv'會更合適。 – ikegami

0

它所謂的typeglob和用於創建表的別名。有關更多信息,請參見perldoc reference

1

看起來,這是一個時髦的嘗試,創建一個閉包,方法是將代碼引用分配給名爲_fact的typeglob,然後以僞遞歸方式調用它。 (注意:typeglob是具有特定名稱的所有變量的容器)。

一個幾乎相等(以及更多的標準)的方式來寫,這將是:

sub fact { 
    my ($n) = @_; 

    my $_fact; 

    $fact = sub { .... }; # Assigning code-ref to scalar variable. 

    return $_fact->($n, 1); # Note the arrow syntax to deref the code-ref 
} 

...但是,正如好心指出的,在它的內存泄漏......等等,我說的只是轉儲乾脆關閉它寫像這樣:

sub fact { 
    my($n,$prod) = @_; 

    return((defined $prod) ? (($n == 0) ? $prod : fact($n-1, $n * $prod)) : fact($n,1)); 
} 

(記住,唯一比無限遞歸更糟糕的是...無限遞歸)

+0

@ikegami,實際上,這段代碼沒有內存泄漏。相反,'$ _fact'在sub中不可訪問,所以它不能用於遞歸調用。 – cjm

11

理想的情況下,該功能的作者也喜歡使用

sub fact { 
    my ($n) = @_; 

    my $_fact; $_fact = sub { 
     my ($n, $prod) = @_; 
     return $prod if $n == 0; 
     return $_fact->($n-1, $n*$prod); 
    }; 

    return $_fact->($n, 1); 
} 

不幸的是,有內存泄漏。 anon子對$_fact有引用,該引用持有對匿名子的引用。需要清除$_fact才能退出參考。

sub fact { 
    my ($n) = @_; 

    my $_fact; 
    $_fact = sub { 
     my ($n, $prod) = @_; 
     return $prod if $n == 0; 
     return $_fact->($n-1, $n*$prod); 
    }; 

    my $rv; 
    my $e = eval { $rv = $_fact->($n, 1); 1 } ? undef : ([email protected] || 'Unknown'); 
    $_fact = undef; 
    die $e if $e 
    return $rv;  
} 

但這是醜陋的!避免此問題的一種方法是使用Y combinator。避免這個問題的一個更簡單的方法是將代碼引用存儲在一個包變量中而不是一個詞法變量中(因爲只有詞法變量被subs引用)。這就是你發佈的代碼所做的。請記住,

*_fact = sub { ... }; 

基本上是

sub _fact { ... } 

運行時版本都分配給子符號_fact代碼插槽。

這就是說,5.16推出了更好的修復:

use feature qw(current_sub); 

sub fact { 
    my ($n) = @_; 

    my $_fact = sub { 
     my ($n, $prod) = @_; 
     return $prod if $n == 0; 
     return __SUB__->($n-1, $n*$prod); 
    }; 

    return $_fact->($n, 1); 
} 
+1

看到了Y combinator。點擊+1。 – memowe

+0

@ikegami你能解釋爲什麼eval部分是必需的嗎?只是爲了防止拋出內存泄漏的錯誤? –

+1

@Nate Glenn,即使在出錯時也要打破記憶週期(除非你知道程序將退出)。由於我沒有預見到任何運行時錯誤,因此'eval'可能不需要,因爲我沒有預見任何運行時錯誤,這顯然是一個學習練習。在Perl中使用遞歸階乘是非常浪費的。 – ikegami