Comprendre les optionnelles : quand et comment les utiliser ?

Alexandros Salazar

Traducteur

Mishulyna

Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. Commentez .

- Titre : Comprendre les optionnelles : quand et comment les utiliser ?

- Auteur : Alexandros Salazar

- Traducteur : Mishulyna

- Parution : 17 octobre 2014

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 Alexandros Salazar - Developpez. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Des optionnelles ? S'il le faut

- I-A. Les optionnelles : c'est quoi ?

- I-B. Conclusion

- II. Comprendre les liaisons optionnelles

- II-A. Comprendre les liaisons optionnelles

- II-B. Conclusion

- III. Les optionnelles implicites en détail

- III-A. Les optionnelles implicites

- III-B. Conclusion

- IV. Remerciements Developpez

I - Des optionnelles ? S'il le faut

Comme Rob Napier l'a déclaré, nous ne connaissons pas Swift. C'est bien - en fait, c'est génial : nous allons décider maintenant, quand le monde est jeune, à quoi nous voulons qu'il ressemble. Nous pouvons et devons rechercher des idées dans les langages similaires, mais beaucoup de bonnes pratiques sont plutôt des préférences de la communauté que des vérités objectives. Et quand il s'agit d'optionnelles, en partie à cause de discussions longues et complexes sur le forum des développeurs concernant la meilleure façon de les utiliser, ma préférence devient rapidement celle de les éviter.

I-A - Les optionnelles : c'est quoi ?

Les optionnelles sont un outil comme un autre, et chaque outil a un usage. Mais quand l'on vient d'Objective-C comme nous, nous avons l'habitude d'utiliser nil partout : nil comme paramètre, nil comme valeur de départ, nil comme valeur logique, etc. Avec la belle syntaxe optionnelle que Swift nous offre, nous pouvons pratiquement transformer tout en optionnelle et obtenir un comportement très similaire. Dans le cas des optionnelles implicites, les choses sont encore plus faciles : nous pouvons les utiliser sans jamais en être conscients. La question est : est-ce judicieux ?

Je dirais que non. Même l'apparente facilité d'utilisation est une illusion : Swift a été conçu comme un langage où nil n'était pas pris en charge et le support pour néant est ajouté par une énumération : nil n'est pas une construction de première classe. En plus, l'essai de gérer plusieurs optionnelles dans le même corps de méthode finit généralement en larmes. Lorsque quelque chose de commun et fondamental dans Objective-C est explicitement relégué en deuxième classe, cela vaut la peine de se demander pourquoi.

Permettez-moi de commencer par un exemple où les optionnelles ont du sens. Voici notre ancien ami gestionnaire d'erreur d'Objective-C, adapté à partir d'un exemple du livre Swift :

NSError *writeError;
BOOL written = [myString writeToFile:path atomically:NO
encoding:NSUTF8StringEncoding
error:&writeError]

if (!written) {
if (writeError) {
NSLog(@"write failure: %@",
[writtenError localizedDescription])
}
}

C'est une situation confuse, que les optionnelles clarifient. En Swift, nous pourrions améliorer les choses avec une API dans les lignes de code suivantes :

// Annotation de type ajoutée par souci de clarté
var writeError:NSError? = myString.writeToFile(path,
atomically:NO,
encoding:NSUTF8StringEncoding)

if let error = writeError {
println("write failure: \(error.localizedDescription)")
}

Ici, l'optionnelle décrit parfaitement la situation : soit il y avait une erreur, soit il n'y avait rien. Ou alors ?

« Il n'y avait rien » n'est pas vraiment le résultat de l'opération. Le vrai résultat c'est que l'opération a réussi. En utilisant l'énumération Result au sujet de laquelle j'ai écrit précédemment, le code prend tout son sens :

// Annotation de type ajoutée par souci de clarté
var outcome:Result<()> = myString.writeToFile(path,
atomically:NO,
encoding:NSUTF8StringEncoding)

switch outcome {
case .Error(reason):
println("Error: \(reason)")
default:
break
}

C'est un peu plus verbeux, mais beaucoup plus clair : nous vérifions un résultat et il peut être un succès ou un échec.(1) Encore et encore, quand je vois une optionnelle et je cesse de penser en Objective-C, je trouve que l'optionnelle, étant tellement générique, obscurcit la nature de ce qui se passe.

Je vois le typage comme un moyen d'ajouter un sens à l'état. Chaque type nous donne de l'information : un tableau nous dit qu'il y a un ordre des données, un dictionnaire nous dit qu'il y a une correspondance entre une représentation et une autre, etc. Vues ainsi, les optionnelles modélisent un cas très spécifique : une situation où la présence ou l'absence de quelque chose sont intrinsèquement significatives. Les Entrées/Sorties interactives sont un bon exemple : l'utilisateur peut fournir des entrées ou aucune entrée, et les deux situations sont tout aussi significatives. Mais souvent, quand il y a présence ou absence de quelque chose, l'absence a une signification au-delà d'elle-même.(2)

Alors, en raison de leur nature attrape-tout, la réaction correcte à la pensée « je dois utiliser une optionnelle » me semble « dois-je vraiment ? ». Il se peut que le néant que je pense représenter ne signifie vraiment quelque chose de plus que « rien » et le code bénéficie de cette signification incorporée dans un type dédié.

Cela dit, Swift interagit avec l'Objective-C, où passer nil est toujours une option, interdite seulement par convention, par documentation et par des explosions à l'exécution. Donc, nous allons nous retrouver souvent face à des méthodes et des fonctions Objective-C qui acceptent et retournent des optionnelles. C'est ainsi, et le fait d'avoir accès aux étonnantes bibliothèques Cocoa compense les difficultés, mais cela ne signifie pas que nous devrions propager arbitrairement les optionnelles au-delà de cette couche d'interaction.

Prenez par exemple l'idée d'écrire une extension pour NSManagedObjectContext, qui permettra le chargement des entités à base de leur nom. En Objective-C, elle pourrait avoir la signature suivante :

- (NSArray *)fetchObjectsForEntityName:(NSString *)newEntityName
sortedOn:(NSString *)sortField
sortAscending:(BOOL)sortAscending
withFilter:(NSPredicate)predicate;

Essayer d'arriver au même résultat en Swift, donnerait cette signature :

func fetchObjectsForEntityName(name:String?,
sortedOn:String?,
sortAscending:Bool?,
filter:NSPredicate?) -> [AnyObject]?

C'est absurde comme signature. Pour la rendre plus propre, commençons par deux hypothèses :

1. Nous voudrons toujours un nom d'entité.

2. Nous obtiendrons toujours un résultat, même s'il est vide.

Nous pouvons combiner cela avec le fait de savoir que nous obtenons toujours des NSManagedObjects à partir de Core Data pour écrire la plus raisonnable :

func fetchObjectsForEntityName(name:String,
sortedOn:String?,
sortAscending:Bool?,
filter:NSPredicate?) -> [NSManagedObject]

Ensuite, penchons-nous sur les deux paramètres de tri. Les deux optionnelles sortedOn et SortAscending sont en fait une représentation déplorable de ce que nous voulons, car elles sont corrélées. Si nous n'avons pas besoin d'objets triés, nous n'avons pas besoin de spécifier si le tri doit être croissant ou décroissant. Alors nous nous tournons vers notre vieille amie l'énumération et définissons :

enum SortDirection {
case Ascending
case Descending
}

enum SortingRule {
case SortOn(String, SortDirection)
case SortWith(String, NSComparator, SortDirection)
case Unsorted
}

Cela nous permet de réécrire la déclaration comme :

func fetchObjectsForEntityName(name:String,
sortingRule:SortingRule,
filter:NSPredicate?) -> [NSManagedObject]

Nous sommes partis de cinq optionnelles et arrivés à une seule. En plus, la règle de tri est plus expressive, puisqu'elle nous permet également de passer une fermeture. Quant à la dernière optionnelle restante, elle comporte tout son sens : soit il y a un filtre, soit il n'y en a pas. On pourrait créer un type dédié (et initialement, j'ai fait exactement cela), mais les avantages sont minces et la syntaxe optionnelle serait plus un fardeau qu'une aide.

Pourtant, en fin de compte, en réévaluant nos hypothèses, nous avons pris une méthode à cinq optionnelles et l'avons transformée pour n'en garder qu'une seule, dans le processus de clarification du problème. C'est un gain incontestable.

I-B - Conclusion

Alors voilà : je pense que les optionnelles sont utiles, mais dans moins de cas que leur facilité d'utilisation laisse entendre. Je pense que la facilité existe parce que Swift doit interagir avec l'Objective-C et d'utiliser chaque fois les optionnelles comme des énumérations complètes serait à la limite insupportable ; cela dit, nous ne devrions pas conclure que les optionnelles sont faites pour amener en Swift la façon de gérer nil de l'Objective-C.

En effet, nous ne devrions pas écrire de l'Objective-C en Swift, mais prendre le meilleur que nous avons appris de l'Objective-C et l'optimiser avec tout ce que Swift a à offrir. Ensuite, nous aurons quelque chose de vraiment spécial et puissant - avec des optionnelles utilisées au besoin, mais sans exagérer.

II - Comprendre les liaisons optionnelles

II-A - Comprendre les liaisons optionnelles

Les optionnelles de Swift sont un spécimen intéressant. D'une part, elles sont absolument essentielles pour gérer des méthodes Objectif-C et C, qui peuvent renvoyer nil et NULL à volonté. D'autre part, elles sont en fait un concept assez avancé, difficile à laisser tomber sur les développeurs qui ne se doutent de rien.

Swift fournit la liaison optionnelle comme un pont entre la nature abstraite du type Optional et la sémantique bien comprise du nil de l'Objective-C. Les utilisateurs doivent savoir quand ils ont affaire à des types optionnels, mais à part cette reconnaissance, ils peuvent les utiliser presque comme types réguliers jusqu'à ce que la décapsulation soit nécessaire. Pour rendre les choses encore plus faciles, les optionnelles qui sont connues pour avoir une valeur peuvent être déclarées comme optionnelles implicites et utilisées comme si elles n'étaient pas optionnelles du tout.

Le problème c'est qu'avec ces couches utiles de sucre syntaxique, il est difficile de discerner ce qui se passe réellement. Comment fonctionne ?. exactement ? Qu'est-ce que ! fait ? Je vais examiner ces questions. Soyez avertis, nous allons plonger en profondeur et cela nécessitera deux chapitres. Mais on va s'amuser.

Nous devons établir une distinction lorsque nous pensons aux optionnelles. D'une part, il y a la mécanique de ce qui se passe. D'autre part, la syntaxe impliquée. Puisque Swift n'a pas de système de macros, je vais me concentrer sur la mécanique : les choses que nous pouvons représenter dans le code, même si ce n'est pas avec la même syntaxe. Parce qu'à la base, la liaison optionnelle n'est pas magique, c'est juste un appel de méthode.

Commençons par considérer ?. comme étant un opérateur. Quelles sont ses propriétés ?

- Il prend deux paramètres : une optionnelle et une fonction qui prend une valeur décapsulée et renvoie un résultat.

- Il retourne une optionnelle.

Donc tout de suite, nous pouvons écrire une signature pour un opérateur similaire, disons |- :

operator infix |- { associativity left }

@infix |-<T,U> (T?, f: T -> U?) -> U?

Notez que nous exigeons que f retourne une optionnelle. Cela semble une limitation, mais nous verrons plus tard que Swift a dans sa manche un moyen astucieux pour contourner ce problème. En supposant que cette fonction existe, nous devrions être en mesure d'écrire ce qui suit :

public class Demo {
public let subDemo:SubDemo?
init(subDemo sDemo:SubDemo? = nil) {
self.subDemo = sDemo
}
}

public class SubDemo {
public let count:Int = 1
}

let aDemo:Demo? = nil
let bDemo:Demo? = Demo()
let cDemo:Demo? = Demo(subDemo: SubDemo())

let aCount = aDemo |- { $0.subDemo } |- { $0.count } // {None}
let bCount = bDemo |- { $0.subDemo } |- { $0.count } // {None}
let cCount = cDemo |- { $0.subDemo } |- { $0.count } // {Some 1}

Il est clair que |- se comporte comme une version plus maladroite de ?.. Maintenant, tout ce dont nous avons besoin est la mise en œuvre.

La chose à retenir à propos d'Optional c'est que ce n'est pas un type magique et ésotérique ; c'est une simple énumération, comme le montre Jameson Quave dans sa réimplémentation. Et armés de cette connaissance, nous pouvons mettre en œuvre notre fonction comme ceci :

@infix |-<T,U> (opt:T?, f: T -> U?) -> U? {
switch opt {
case .Some(let x):
return f(x)
case .None:
return .None
}
}

Si vous testez ce code dans un playground, vous verrez qu'il fonctionne parfaitement avec l'exemple ci-dessus - ce qui est un peu surprenant, parce que la dernière fermeture, l'appel à count, ne retourne pas une optionnelle.

C'est une fonctionnalité si commune de Swift que nous avons tendance à l'ignorer : les optionnelles peuvent être créées sur demande. Par exemple, nous n'avons jamais besoin d'encapsuler les valeurs de retour dans une optionnelle lorsque la signature l'exige ; il suffit de retourner nil ou la valeur et ils sont encapsulés comme par magie. La même chose se passe ici : le résultat de la fermeture est implicitement encapsulé dans une optionnelle car f:T->U? l'exige.(3)

Si vous avez suivi ce blog, vous pouvez vous souvenir de mon deuxième article sur la gestion des erreurs. Et si c'est le cas, la définition de |- peut vous sembler terriblement familière. La signature de type n'est pas tout à fait la même, mais la mise en œuvre est presque identique à celle de Result.flatMap :

extension Result {
func flatMap<P>(f:T -> Result<P>) -> Result<P> {
switch self {
case .Success(let value):
return .Success(f(value))
case .Error(let msg):
return .Error(msg)
}
}
}

Et en effet, nous pouvons mettre en œuvre flatMap avec Optional comme suit :

extension Optional {
func flatMap<Z>(f:T->Z?) -> Z? {
switch self {
case .Some(let a):
return f(a)
case .None:
return .None
}
}
}

Ce qui à son tour nous permet d'écrire :

let aCount = aDemo.flatMap { $0.subDemo }.flatMap { $0.count } // {None}
let bCount = bDemo.flatMap { $0.subDemo }.flatMap { $0.count } // {None}
let cCount = cDemo.flatMap { $0.subDemo }.flatMap { $0.count } // {Some 1}

Nous avons donc trouvé une autre façon de faire ce que ?. et |- font, mais ce n'est pas tout. Puisque Optional<()> est un type valide, ces deux expressions sont équivalentes :

let a:Int? = … // une optionnelle

if let a = a {
println("\(a)")
}

a.flatMap { println("\($0)") }

C'est exact. Profondément ancrée dans la mécanique des optionnelles, cachée sous des monticules de sucre syntaxique, nous trouvons flatMap.

Intéressant, non ?

II-B - Conclusion

Ce n'est pas pour entretenir le mystère (vous ne vous demandez pas quoi d'autre fait cette fonction flatMap ?), mais nous n'avons pas encore fini avec les optionnelles. Le fonctionnement de ?. et if-let n'est qu'une partie de l'histoire. Nous devons encore regarder comment fonctionnent les optionnelles implicites, et que fait le symbole !, parce que ce sont des sources de confusion, aggravées par le fait qu'elles sont conçues pour fonctionner comme si elles n'existaient pas. Je vais parler de cela dans le chapitre suivant.

Mais ne vous inquiétez pas. Je reviendrai à flatMap.

III - Les optionnelles implicites en détail

III-A - Les optionnelles implicites

Dans le chapitre précédent, j'ai examiné en détail le fonctionnement d'une liaison optionnelle Swift et (en passant) la mécanique de la construction if-let. Cela représente environ la moitié de l'histoire des optionnelles. L'autre moitié est l'histoire des optionnelles implicites.

Pour rappel, les optionnelles implicites nous permettent de les traiter comme si elles n'étaient pas optionnelles du tout. En regardant notre exemple du dernier article, si nous modifions les optionnelles en optionnelles implicites, nous pouvons écrire :

public class Demo {
public let subDemo:SubDemo!
init(subDemo sDemo:SubDemo = nil) {
self.subDemo = sDemo
}
}

public class SubDemo {
public let count:Int = 1
}

let aDemo:Demo! = nil
let bDemo:Demo! = Demo()
let cDemo:Demo! = Demo(subDemo: SubDemo())

let aCount = aDemo.subDemo.count // PLANTAGE
let bCount = bDemo.subDemo.count // PLANTAGE
let cCount = cDemo.subDemo.count // 1

La règle est que les optionnelles implicites ne doivent pas être nil au moment où elles sont utilisées ; si elles sont nil, le programme lancera une erreur d'exécution et plantera. Cela est particulièrement utile dans les schémas d'initialisation en deux parties, comme lorsque les vues et les contrôleurs de vue sont chargés à partir de fichiers nib et leur accès est préparé après l'instanciation de la classe, mais avant que nous essayons d'y accéder.

Ce qui est bien beau, mais nous avons une fois de plus une syntaxe très pratique qui peut occulter ce qui se passe réellement.

Il y a quelques jours, quelqu'un a posté sur les forums des développeurs un fragment de code qui ne fonctionne pas dans Xcode 6 Beta 4, équivalant essentiellement à ceci :

let arr:[String]! = ["hello", "world"]
let val = find(arr, "hello")

L'erreur est :

type ‘[String]!' does not conform to protocol ‘Collection'

Ceci, cependant, fonctionne :

let val = find(arr!, "hello") // val = {Some 0}

Alors, quelle en est la cause ?

Pour être clair, le fait qu'il y avait une erreur est presque certainement un bogue de Swift, puisque Swift promet que les optionnelles implicites peuvent être utilisées n'importe où comme si elles étaient des types non optionnels. Mais cela met en évidence un point très important :

Les optionnelles explicitement décapsulées sont toujours des optionnelles.

Quand nous écrivons let arr = [String]!, nous ne trichons pas en disant sournoisement au compilateur « arr est de type [String], fais-moi confiance ». Nous sommes en train de déclarer arr comme un ImplicitlyUnwrappedOptional <[String]>. Le point d'exclamation n'est qu'une abréviation - tous les comportements sont implémentés comme des méthodes, comme pour n'importe quel type.

Compte tenu de cela, il est clair que sauf si ImplicitlyUnwrappedOptional est conforme au protocole de Collection, ce qui n'est pas le cas, il va déclencher une erreur de type dans cet extrait de code. Inversement, comme l'appel d'arr! décapsule explicitement l'optionnelle, l'erreur de type est résolue. La seule chose qui ne se produit pas est la conversion implicite promise par Swift.

Cela signifie que ! est une abréviation dans une déclaration de type, mais que lorsqu'il suit un objet, c'est un opérateur. Et puisque Swift nous permet de déclarer des opérateurs, nous pouvons les implémenter nous-mêmes, d'une manière qui ne surprendra absolument personne à ce stade :(4)

postfix operator ! {}
@postfix func !<T>(opt:T!) -> T {
switch opt {
case .Some(let x):
return x
case .None
abort()
}
}

La seule chose qui peut être inattendue est l'appel d'abort(). La fonction abort est étiquetée @noreturn, ce qui signifie qu'elle ne retourne pas de valeur ; par conséquent, le système de type accepte le fait qu'elle ne retourne pas un paramètre générique T. La vraie implémentation utilise une fonction différente, qui affiche aussi un message expliquant la raison du plantage, mais le résultat final est le même : si l'optionnelle est nil, l'appel de l'opérateur ! sur elle conduit à une erreur d'exécution.

Et avec cette implémentation, on comprend pourquoi le code corrigé a compilé : arr! retourne un type [String], qui implémente Collection. Bogue quand même, tout a un sens.

Bon, nous avons compris comment fonctionnent ?. et !. Il reste encore une question : pourquoi les appels de méthode réguliers fonctionnent sur une optionnelle implicite ? Pour répondre à cette question, nous avons besoin de l'éclairage apporté par Ole Begemann : les méthodes sont des fonctions appliquées partiellement. En d'autres termes, a, b et c sont toutes des expressions valides dans le code ci-dessous :

let str = "hello"

let a = str.stringByAppendingString("!") \\ "hello!"
let b = String.stringByAppendingString(str)("!") \\ "hello!"

let method = String.stringByAppendingString(str)
let c = method("!") \\ "hello!"

Maintenant, réfléchissons aux appels de méthode. ?. est du sucre syntaxique pour flatMap. Imaginons une fonction, disons +-, qui ferait le travail d'exécuter un appel de méthode :

operator infix +- { associativity left }
@infix func +-<T,Z>(obj:T, f:T->Z) -> Z {
return f(obj)
}

Nous pourrions appeler n'importe quelle méthode avec cet opérateur comme ceci :(5)

let happyHello = "hello" +- {
String.stringByAppendingString($0)("!")
}

Deux secondes, là. Cela ressemble exactement à notre appel à |- pour les optionnelles régulières. En fait, si nous redéfinissons +-…

operator infix +- { associativity left }
@infix func +-<T,Z>(obj:ImplicitlyUnwrappedOptional<T>, f:T->Z)
-> ImplicitlyUnwrappedOptional<Z> {
switch obj {
case .Some(let x):
return f(x)
case .None:
abort()
}
}

... nous avons une autre version de |-, qui peut être utilisé exactement de la même manière :

let maybeHello:String! = "hello"
let happyHello = maybeHello +- {
String.stringByAppendingString($0)("!")
} +- {
String.stringByAppendingString($0)("!")
}
// happyHello = "hello!!"

Cependant, il se comporterait correctement pour des optionnelles implicites : si maybeHello était nil, le code échouerait, comme il s'impose.

Mais cela signifie aussi que l'opérateur +- peut être transformé en un appel à une implémentation de flatMap sur un ImplicitlyUnwrappedOptional. Ainsi, tout comme ?. est du sucre syntaxique pour flatMap sur Optional, l'opérateur point ordinaire que nous utilisons pour appeler des méthodes est en fin de compte du sucre syntaxique pour flatMap sur ImplicitlyUnwrappedOptional.(6)

N'est-elle pas intéressante, la méthode flatMap ?

III-B - Conclusion

La conclusion finale est donc que chaque aspect des optionnelles peut être mis en œuvre en Swift pur, même si le code est plus tacot que la syntaxe spécialisée. C'est génial qu'on nous donne des outils pour simplifier leur utilisation, vu leur importance pour l'interopérabilité avec l'Objective-C : j'ai l'intention de les utiliser à leur maximum. Mais j'espère aussi que ces deux derniers articles aident à clarifier ce qui se passe sous le capot.

Alors si jamais vous constatez un comportement incompréhensible des optionnelles, examinez de plus près la sémantique réelle ; vous allez probablement détecter la source de l'erreur. Et qui sait, il pourrait même s'agir d'un bogue du langage. Combien de fois nous arrive-t-il de dire cela ?

Mais, vraiment, n'est-elle pas intéressante, la méthode flatMap ?

IV - Remerciements Developpez

Nous remercions Alexandros Salazar de nous avoir aimablement autorisé à publier ses articles. Les textes originaux des articles d'Alexandros Salazar se trouvent sur http://nomothetis.svbtle.com.

Nous remercions aussi Mishulyna pour sa traduction, Seelass et LeBzul pour leur relecture technique ainsi que ClaudeLELOUP pour sa relecture orthographique.

(1)(NDA) Le projet Swiftz a créé pour l'interaction avec Cocoa un meilleur type Result, qui encapsule un objet NSError, plutôt que juste une chaîne de caractères. J'ergote sur l'utilisation de Value au lieu de Success, que je trouve moins significative, mais si vous cherchez à écrire du vrai code, c'est probablement ce que vous souhaitez utiliser.

(2)(NDA) Dans ce contexte, les optionnelles implicites pour IBOutlets sont un excellent modèle : leur absence conduit à une erreur application-ending, comme il se doit quand les IBOutlets ne sont pas définies. Le fait de les utiliser comme si elles n'étaient pas du tout optionnelles est donc approprié.

(3)(NDA) Attention, cependant : cette encapsulation automatique se produit uniquement avec les fermetures.

(4)(NDA) Cependant, cette implémentation ne fonctionne pas en fait, parce que ! est un opérateur réservé.

(5)(NDA) Dans ce cas, le . dans String.stringByAppendingString n'est pas un appel de méthode, mais plutôt une recherche d'enregistrement. Puisqu'elle a une sémantique différente, nous ne trichons pas réellement quand nous l'utilisons à l'intérieur d'une fonction destinée à remplacer l'opérateur d'appel de méthode ..

(6)(NDA) En fait, c'est un peu plus compliqué que cela. L'opérateur point doit fonctionner sur les types ordinaires ainsi que sur des ImplicitlyUnwrappedOptional, alors la vraie implémentation du sucre syntaxique n'est pas si simple. Cependant, l'idée de base est la même.