9
votes

Comment revenir d'un sous anonyme récursif en perl6?

Cela fait ce à quoi je m'attendais. fib (13) renvoie 233.

my $fib = -> Int $x --> Int {
    0 if $x == 0;
    1 if $x == 1;
    $fib($x - 1) + $fib($x - 2); 
}

say $fib(13);

J'ai essayé d'implémenter fib () en utilisant un sous anonyme

my $fib = -> Int $x --> Int {
    return 0 if $x == 0;
    return 1 if $x == 1;
    return $fib($x - 1) + $fib($x - 2); 
}

$fib(13) 

J'obtiens l'erreur suivante lors de l'exécution cela avec des retours explicites.

Tentative de retour en dehors de toute routine en bloc à test.p6 ligne 39

Donc je me suis débarrassé des valeurs de retour.

sub fib(Int $a --> Int) {
    return 0 if $a == 0;
    return 1 if $a == 1;

    return fib($a -1) + fib($a -2);
}

my $square = -> $x { $x * 2 };   # this works with no return value
my @list = <1 2 3 4 5 6 7 8 9>.map( $square );
# returns [2 4 6 8 10 12 14 16 18]

Cette dernière version ne revient jamais. Existe-t-il un moyen d'écrire une fonction récursive anonyme sans valeurs de retour?


1 commentaires

Notez que vous pouvez utiliser &? BLOCK pour faire référence au bloc actuel afin de pouvoir l'appeler. -> Int $ _ -> Int {quand 0 {1}; quand 1 {1}; &? BLOC ($ _- 1) + &? BLOC ($ _- 2)}


3 Réponses :


9
votes

D'après la documentation :

Les blocs qui ne sont pas de type Routine (qui est une sous-classe de Block) sont transparent pour retourner.

my $fib = -> Int $x --> Int {
    if ( $x == 0 ) {
        0;  # <-- Implicit return value
    }
    elsif ( $x == 1 ) {
        1;  # <-- Implicit return value
    }
    else {
        $fib($x - 1) + $fib($x - 2);  # <-- Implicit return value
    }
}

La dernière instruction est la valeur de retour implicite du bloc

Vous pouvez donc essayer:

sub f() {
say <a b c>.map: { return 42 };
               #   ^^^^^^   exits &f, not just the block  }


1 commentaires

La forme implicite est jolie, même si une autre option qui mérite peut-être d'être mentionnée est d'écrire sub (Int $ x -> Int) {...} - c'est-à-dire un véritable sous-anonyme - et le reste fonctionne comme il a été écrit à l'origine.



4
votes

Les blocs n'ont pas besoin de déclarer le type de retour . Cependant, vous pouvez toujours rendre ce que vous voulez. Le problème n'est pas d'utiliser return, c'est dans la déclaration de l'Int.

use v6;

my $fib = -> Int $x  {
    if $x == 0 {
        0;
    } elsif $x == 1 {
        1;
    } else {
        $fib($x - 1) + $fib($x - 2);
    }
}

say $fib(13) ;

Le problème est que la valeur de retour doit être la dernière exécutée. De la façon dont vous l'avez fait, s'il trouve 0 ou 1, il continue de fonctionner, atteignant la dernière instruction, quand il recommencera. Vous pouvez également utiliser given au lieu des ifs en cascade . Tant que ce qu'il renvoie est le dernier émis, c'est OK.


1 commentaires

"Les blocs ne déclarent pas le type de retour. ... Le problème n'est pas d'utiliser return , c'est dans la déclaration de Int ." Votre code fonctionnera avec le -> Int laissé dans (et échouera s'il est changé, par exemple, -> Str ).



7
votes

Trois autres options:

sub

Vous pouvez écrire des routines anonymes en utilisant sub sans nom :

my $fib = -> Int $_ --> Int {
  when 0 { 0 }
  when 1 { 1 }
  $fib($_ - 1) + $fib($_ - 2)
}

say $fib(13); # 233

Voir la réponse de @ Hà ¥ konHægland pour savoir pourquoi cela (délibérément) ne fonctionne pas avec des blocs non routiniers.

quitter

Le design a anticipé votre question:

say $fib(13);

compile. J'espère que vous pouvez deviner que ce qu'il fait - ou plutôt est censé faire - est exactement ce que vous vouliez faire.

Malheureusement, si vous suivez ce qui précède avec:

my $fib = -> Int $x --> Int {
  leave 0 if $x == 0;
  leave 1 if $x == 1;
  leave $fib($x - 1) + $fib($x - 2); 
}

Vous obtenez une erreur d'exécution "laissez pas encore implémenté ".

Je suppose que cela sera mis en œuvre dans les prochaines années et le message d'erreur" Tentative de retour en dehors de toute routine "mentionnera alors congé . Mais sa mise en œuvre a une très faible priorité car il est facile d'écrire sub comme ci-dessus, ou d'écrire du code comme @ Hà ¥ konHægland l'a fait, ou d'utiliser une construction d'instruction case / switch comme suit, et c'est très bien assez pour le moment.

case / switch ( when / default )

Vous pouvez spécifier le paramètre comme $ _ au lieu de $ x et vous êtes prêt à utiliser des constructions qui font référence au sujet:

my $fib = sub (Int $x --> Int) {
  return 0 if $x == 0;
  return 1 if $x == 1;
  return $fib($x - 1) + $fib($x - 2); 
}

say $fib(13); # 233

Voir quand .


0 commentaires