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