Swift : closures et saccharose
Qui n’a jamais rêvé d’ajouter une petite fonction forEach aux tableaux Swift ?
“Pourquoi faire ? j’aime bien les boucles…”
demanderont certains…
“Mais pour s’amuser un peu, bordel !”
leur répondrai-je poliment.
Et ce ne sera pas long. J’en profiterais pour m’intéresser aux extensions de classes et à quelques “sucres syntaxiques” proposés par Swift; efficaces, mais un peu déroutants pour le noob distrait qui débarque.
Par exemple :
func add(a:Int, b:Int)->Int{
return a + b
}
let r = apply( 3,5, add)
peut être écrit :
let r = apply( 3, 5 ){ $0 + $1 }
// voire même
let r = apply( 3, 5, + )
Mais ne trainons pas : pour commencer il nous faut créer une extension de Array, et y déclarer une fonction forEach.
extension Array{
func forEach(){
}
}
Et que puis-je attendre d’une fonction forEach ?
- de pouvoir lui passer une fonction qui acceptera un élément du tableau en paramètre, et qui ne renverra rien.
On peut noter
(Element)->Void
le type de la fonction attendue ( attend un paramètre Element générique et renvoie Void ).
extension Array{
func forEach( f:(Element)->Void ){
}
}
… qu’elle “appliquera” la fonction à ses éléments du tableau, ou plutôt elle appliquera chacun de ses élements à la fonction.
extension Array{
func forEach( f:(Element)->Void ){
for e in self{
f(e)
}
}
}
Et voilà ! Voyons comment l’utiliser. Pour commencer la version bavarde :
let names = ["Luke", "Yoda", "Leila"]
func log(item:String)->Void{
print(item)
}
names.forEach( log )
Mais on peut faire plus “swift”, avec une closure par exemple. Pour faire court, une closure est une “fonction anonyme”, passée comme argument à une fonction. Leur écriture dans Swift est un peu particulière ( en gros on remplace func par l’accolade d’ouverture qu’on remplace par in )
names.forEach( {(n:String)->Void in
print(n)
})
Le type de la valeur renvoyée par la fonction étant déclaré dans l’extension ( ->Void ), il est inférable, “déductible”, donc facultatif dans l’appel (Inferred return value type).
names.forEach( {(n:String) in
print(n)
})
De même, le paramètre étant déclaré “de type générique” (Element), pas besoin de le préciser pour l’appel.
names.forEach( { (n) in print(n) })
//Au passage on peut aussi supprimer les parenthèses.
names.forEach( { n in print(n) })
Ensuite, Swift propose une syntaxe particulière lorsque le dernier paramètre d’une méthode est une closure ( trailing closure ), on peut sortir le bloc des paramètres et le chainer à la fonction Par exemple une méthode déclarée :
func apply( a:Int, b:Int, op:(c,d)->Int ){
...
}
peut être appelée
apply( 5,6,{ c,d in c + d });
// ou en chainant la closure
apply( 8,1 ){ c,d in c + d };
Pour notre forEach donc :
names.forEach(){ n in print(n) }
// On peut même omettre les parenthèses si il n'y pas d'autres arguments.
names.forEach{ n in print(n) }
Pour finir, on peut supprimer le nom d’argument n, et utiliser un “raccourci” (shorthand arguments names) d’accès aux arguments transmis à la closure via $0, $1…
names.forEach{ print($0) }
Voilà qui est un peu plus concis !
cf. la doc sur les closures