2013-11-04 12 views
5

Jaka jest dokładna funkcja/cel * przed fakty i jak można je napisać w taki sam sposób?tajemniczy * przed zagnieżdżonym sub

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

Odpowiedz

2

Sprawdź typeglob aliases

Przykład powyżej powinny być napisane za pomocą anonimowej procedury/zamknięcie:

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

"Jaki jest dokładny Funkcja/cel * przed _fact" –

+0

@ikegami jak o http://codepad.org/80KSqye4? –

+0

Nie przecieka, ale jest kiepskim rozwiązaniem. Usuwa rekursję ogonową, która była oczywiście czynnikiem przewodnim w projektowaniu subwoofera. '$ n? $ n * fakt ($ n-1): 1 "wystarczałby inaczej. Wyeliminowanie rekursji ogona sprawia, że ​​wszędzie jest ona nieefektywna. Zmiana zewnętrznej części na koniec za pomocą 'my $ rv = $ _fact (...); undef $ _fact; $ rv' byłoby bardziej odpowiednie. – ikegami

0

To tzw typeglob i służy do tworzenia aliasów tabel. Aby uzyskać więcej informacji, zobacz perldoc reference.

1

Wygląda na to, że jest to ostra próba utworzenia zamknięcia poprzez przypisanie kodu do kufla o nazwie _fact, a następnie wywołanie go pseudorekursywnie. (uwaga: typeglob jest kontenerem dla wszystkich zmiennych o określonej nazwie).

Praktycznie równoważne (i znacznie bardziej standardowe) sposób napisać to byłoby:

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 
} 

... ale, jak to uprzejmie zauważył, że ma przeciek pamięci w nim ... tak, mówię po prostu zrzucić zamknięcie całkowicie i napisać to tak:

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

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

(pamiętaj, że jedyną rzeczą, gorzej niż nieskończonej rekurencji jest ... nieskończonej rekurencji)

+0

@ikegami, w rzeczywistości ten kod nie ma wycieku pamięci. Zamiast tego '$ _fact' nie jest dostępny wewnątrz sub, więc nie może być użyty do wywołań rekursywnych. – cjm

11

Idealnie, autor funkcji wolałby użyć

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

Niestety, ma wyciek pamięci. Anon sub ma odniesienie do $_fact, które zawiera odniesienie do anonimowego sub. $_fact musiałaby zostać wyczyszczona, aby zerwać referencję przy wyjściu.

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

Ale to jest UGLY! Jednym ze sposobów uniknięcia problemu jest użycie Y combinator. Znacznie prostszym sposobem na uniknięcie problemu jest przechowywanie odwołania do kodu w zmiennej pakietowej zamiast zmiennej leksykalnej (ponieważ tylko zmienne leksykalne są przechwytywane przez subs). Tak właśnie zrobiłeś kod. Należy pamiętać, że

*_fact = sub { ... }; 

jest w zasadzie wersji run-time z

sub _fact { ... } 

Zarówno przypisać sub Kodeksu szczelinie symbolu _fact.

Powiedział, 5.16 wprowadzono lepszą Fix:

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

widział kombinator Y. kliknięto +1. – memowe

+0

@ikegami możesz wyjaśnić, dlaczego część eval jest niezbędna? Czy jest to po to, aby zapobiec wyświetlaniu się błędu, który spowodował wyciek pamięci? –

+1

@Nate Glenn, Przerwij cykl pamięci nawet w przypadku błędu (chyba, że ​​wiesz, że program zostanie zrestartowany.) '' Eval' prawdopodobnie nie jest tutaj potrzebny, ponieważ nie przewiduję żadnego błędu podczas działania, ale zawarłem go, ponieważ jest to oczywiście ćwiczenie edukacyjne. Używanie rekursji dla silni w Perlu jest szalenie nieekonomiczne. – ikegami

Powiązane problemy