2010-07-10 16 views
7

juste vu une possibilité intéressante pour initialiser les blocs de code à Scala pour des fonctions d'ordre supérieur tels que foreach ou carte:scala foreach et carte initializers

(1 to 3) map { 
    val t = 5 
    i => i * 5 
} 


(1 to 3) foreach { 
    val line = Console.readLine 
    i => println(line) 
} 

Est-ce une caractéristique documentée ou devrais-je éviter de telles constructions? Je peux imaginer, le bloc "initialisation" arrive dans le constructeur et la fermeture elle-même devient une méthode apply()?

Merci Pat pour la question initiale (http://extrabright.com/blog/2010/07/10/scala-question-regarding-readline)

Répondre

12

Bien que les fonctionnalités utilisées ne sont pas rares, je dois admettre que c'est une combinaison assez bizarre de fonctionnalités. Le truc de base est que n'importe quel bloc dans Scala est une expression, avec le même type que la dernière expression dans le bloc. Si cette dernière expression est une fonction, cela signifie que le bloc a un type fonctionnel, et peut donc être utilisé comme argument pour "map" ou "foreach". Ce qui se passe dans ces cas est que lorsque "map" ou "foreach" est appelé, le bloc est évalué. Le bloc évalue à une fonction (i => i * 5 dans le premier cas), et cette fonction est ensuite mappée sur la gamme.

Une utilisation possible de cette construction est que le bloc définisse des variables mutables et que la fonction résultante mute les variables chaque fois qu'elles sont appelées. Les variables seront initialisées une fois, refermées par la fonction et mises à jour à chaque fois que la fonction est appelée.

Par exemple, voici une manière quelque peu surprenante de calcul des 6 premiers nombres factoriels

(1 to 6) map { 
     var total = 1 
     i => {total *= i;total} 
    } 

(BTW, désolé pour l'utilisation factoriel comme un exemple. Il était ça ou fibonacci. fonctionnels Progamming règles Guild. Vous avez un problème avec cela, prenez avec les garçons dans le couloir.)

Une raison moins impérative pour qu'un bloc retourne une fonction est de définir des fonctions d'aide plus tôt dans le bloc. Par exemple, si votre deuxième exemple était à la place

(1 to 3) foreach { 
    def line = Console.readLine 
    i => println(line) 
} 

Le résultat serait que trois lignes ont été lu et repris une fois chacun, tandis que votre exemple avait la ligne une fois lu et repris trois fois.

+0

Réponse beaucoup plus précise que la mienne. +1 – VonC

+0

Dans l'exemple factoriel, vous devez utiliser 'total * = i' au lieu d'introduire une deuxième variable nommée' counter' –

+0

Oui, je m'en suis rendu compte plus tard. Éditera –

1

D'abord, le commentaire du blog original "Scala Question Regarding readLine" mention après

Le « line » est une valeur et ne peut être exécutée, il est affecté une seule fois à partir du résultat de l'exécution de la méthode "Console.readLine".
Il est utilisé moins de trois fois dans votre fermeture.
Mais si vous définissez comme une méthode, il sera exécuté trois fois:

(1 to 3) foreach { 
    def line = Console.readLine 
    i => println(line) 
} 

Le blog Scala for Java Refugees Part 6: Getting Over Java a une section intéressante sur la fonction d'ordre supérieur, y compris:

Scala offre encore plus de flexibilité dans la syntaxe pour ces choses de fonction d'ordre supérieur.
Dans l'appel itérationnel, nous créons une méthode anonyme complète juste pour effectuer un autre appel à la méthode println(String). Considérant println(String) est lui-même une méthode qui prend un String et renvoie Unit, on pourrait penser que nous pourrions compresser un peu. Comme il se trouve, nous pouvons:

iterate(a, println) 

En omettant les parenthèses et juste en spécifiant le nom de la méthode, nous racontons le compilateur Scala que nous voulons utiliser println comme valeur fonctionnelle, en lui transmettant à la méthode iterate.
Ainsi, au lieu de créer une nouvelle méthode pour gérer un seul ensemble d'appels, nous transmettons une ancienne méthode qui fait déjà ce que nous voulons.
Ceci est un modèle couramment vu en C et C++. En fait, la syntaxe pour passer une fonction en tant que valeur fonctionnelle est exactement la même. Il semble que certaines choses ne changent jamais ...