162 votes

Quand utiliser une fonction inline en Kotlin ?

Je sais qu'une fonction inline peut améliorer les performances et faire grossir le code généré, mais je ne sais pas quand il est correct de l'utiliser.

lock(l) { foo() }

Au lieu de créer un objet fonction pour le paramètre et de générer un appel, le compilateur pourrait émettre le code suivant. ( Source : )

l.lock()
try {
  foo()
}
finally {
  l.unlock()
}

mais j'ai constaté qu'il n'y a pas d'objet fonction créé par kotlin pour une fonction non-inline. pourquoi ?

/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
    lock.lock();
    try {
        block();
    } finally {
        lock.unlock();
    }
}

8 votes

Il existe deux cas d'utilisation principaux pour cela, l'un concerne certains types de fonctions d'ordre supérieur, et l'autre les paramètres de type réifié. La documentation des fonctions en ligne couvre ces cas : kotlinlang.org/docs/reference/inline-functions.html

5 votes

@zsmb13 merci, monsieur. mais je ne comprends pas que : "Au lieu de créer un objet fonction pour le paramètre et de générer un appel, le compilateur pourrait émettre le code suivant".

5 votes

Je ne comprends pas non plus cet exemple.

386voto

zsmb13 Points 36441

Disons que vous créez une fonction d'ordre supérieur qui prend un lambda de type () -> Unit (pas de paramètres, pas de valeur de retour), et l'exécute comme suit :

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

En langage Java, cela se traduira par quelque chose comme ceci (simplifié !) :

public void nonInlined(Function block) {
    System.out.println("before");
    block.invoke();
    System.out.println("after");
}

Et quand vous l'appelez depuis Kotlin...

nonInlined {
    println("do something here")
}

Sous le capot, une instance de Function sera créé ici, qui englobe le code à l'intérieur du lambda (encore une fois, ceci est simplifié) :

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});

Donc, en fait, appeler cette fonction et lui passer un lambda créera toujours une instance d'un fichier de type Function objet.


D'autre part, si vous utilisez l'option inline mot-clé :

inline fun inlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

Quand tu l'appelles comme ça :

inlined {
    println("do something here")
}

Non Function sera créée, au lieu de cela, le code entourant l'invocation de la fonction block à l'intérieur de la fonction inlined sera copié sur le site d'appel, vous obtiendrez donc quelque chose comme ceci dans le bytecode :

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

Dans ce cas, aucune nouvelle instance n'est créée.

41 votes

Quel est l'avantage d'avoir l'enveloppe de l'objet Function en premier lieu ? c'est-à-dire - pourquoi tout n'est pas inlined ?

20 votes

De cette façon, vous pouvez également passer arbitrairement des fonctions en tant que paramètres, les stocker dans des variables, etc.

0 votes

@zsmb13 mais pouvez-vous aussi passer un inline fun à un autre inline fun comme paramètre ?

81voto

s1m0nw1 Points 21698

Permettez-moi d'ajouter : "Quand ne pas utiliser inline " :

1) Si vous avez une fonction simple qui n'accepte pas d'autres fonctions comme argument, cela n'a pas de sens de les mettre en ligne. IntelliJ vous en avertira :

L'impact attendu de l'inlining '...' sur les performances est insignifiant. L'inlining fonctionne mieux pour les fonctions dont les paramètres sont de type fonctionnel.

2) Même si vous avez une fonction "avec des paramètres de types fonctionnels", vous pouvez rencontrer le compilateur vous disant que l'inlining ne fonctionne pas. Considérez cet exemple :

inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
    val o = operation //compiler does not like this
    return o(param)
}

Ce code ne compile pas avec l'erreur :

Utilisation illégale du paramètre inline 'operation' dans '...'. Ajouter le modificateur 'noinline' à la déclaration du paramètre.

La raison en est que le compilateur est incapable de mettre ce code en ligne. Si operation n'est pas enveloppée dans un objet (ce qui est impliqué par la fonction inline puisque vous voulez éviter cela), comment peut-on l'affecter à une variable ? Dans ce cas, le compilateur suggère de rendre l'argument noinline . Avoir un inline avec une seule fonction noinline Cette fonction n'a aucun sens, ne faites pas ça. Toutefois, s'il existe plusieurs paramètres de types fonctionnels, envisagez l'inlining de certains d'entre eux si nécessaire.

Voici donc quelques suggestions de règles :

  • Vous peut inline lorsque le type fonctionnel param est appelé directement ou passé à autre inline fonction
  • Vous devrait inline lorsque ^ est le cas.
  • Vous ne peut pas inline lorsque le paramètre de la fonction est assigné à une variable à l'intérieur de la fonction
  • Vous devrait envisager l'inlining si au moins un de vos paramètres typés fonctionnels peut être inliné, utiliser noinline pour le reste.
  • Vous ne devrait pas des fonctions énormes en ligne, pensez au code d'octet généré. Il sera copié à tous les endroits où la fonction est appelée.
  • Un autre cas d'utilisation est reified qui vous obligent à utiliser des paramètres de type inline . Lire ici .

7 votes

Techniquement, vous pouvez toujours mettre en ligne des fonctions qui ne prennent pas d'expressions lambda, n'est-ce pas ? l'avantage est que l'appel de fonction est évité dans ce cas. des langages comme Scala le permettent. je ne sais pas pourquoi Kotlin interdit ce type de mise en ligne.

10 votes

@rogue-one Kotlin n'interdit pas cette période d'inlining. Les auteurs du langage affirment simplement que le bénéfice en termes de performances est susceptible d'être insignifiant. Les petites méthodes sont déjà susceptibles d'être inlined par la JVM pendant l'optimisation JIT, surtout si elles sont exécutées fréquemment. Un autre cas où inline peut être préjudiciable est lorsque le paramètre fonctionnel est appelé plusieurs fois dans la fonction inline, par exemple dans différentes branches conditionnelles. Je viens de rencontrer un cas où tout le bytecode des arguments fonctionnels était dupliqué à cause de cela.

2 votes

Quand devrions-nous utiliser crossinline paramètres ?

72voto

Utilisez inline pour empêcher la création d'objets

Les lambdas sont converties en classes

Dans Kotlin/JVM, les types de fonctions (lambdas) sont convertis en classes anonymes/régulières qui étendent l'interface Function . Considérons la fonction suivante :

fun doSomethingElse(lambda: () -> Unit) {
    println("Doing something else")
    lambda()
}

La fonction ci-dessus, après compilation, aura l'aspect suivant :

public static final void doSomethingElse(Function0 lambda) {
    System.out.println("Doing something else");
    lambda.invoke();
}

Le type de fonction () -> Unit est converti en interface Function0 .

Voyons maintenant ce qui se passe lorsque nous appelons cette fonction depuis une autre fonction :

fun doSomething() {
    println("Before lambda")
    doSomethingElse {
        println("Inside lambda")
    }
    println("After lambda")
}

Problème : objets

Le compilateur remplace le lambda par un objet anonyme de type Function type :

public static final void doSomething() {
    System.out.println("Before lambda");
    doSomethingElse(new Function() {
            public final void invoke() {
            System.out.println("Inside lambda");
        }
    });
    System.out.println("After lambda");
}

Le problème ici est que, si vous appelez cette fonction dans une boucle des milliers de fois, des milliers d'objets seront créés et ramassés. Cela affecte les performances.

Solution : inline

En ajoutant le inline avant la fonction, nous pouvons demander au compilateur de copier le code de cette fonction au niveau de l'appel, sans créer les objets :

inline fun doSomethingElse(lambda: () -> Unit) {
    println("Doing something else")
    lambda()
}

Cela a pour conséquence de copier le code du inline ainsi que le code de la fonction lambda() à l'emplacement de l'appel :

public static final void doSomething() {
    System.out.println("Before lambda");
    System.out.println("Doing something else");
    System.out.println("Inside lambda");
    System.out.println("After lambda");
}

Cela double la vitesse d'exécution, si vous comparez avec/sans inline avec un million de répétitions dans un for boucle. Ainsi, les fonctions qui prennent d'autres fonctions comme arguments sont plus rapides lorsqu'elles sont inlined.


Utilisez inline pour empêcher la capture de variables

Lorsque vous utilisez les variables locales à l'intérieur du lambda, cela s'appelle la capture de variable (closure) :

fun doSomething() {
    val greetings = "Hello"                // Local variable
    doSomethingElse {
        println("$greetings from lambda")  // Variable capture
    }
}

Si notre doSomethingElse() La fonction ici n'est pas inline les variables capturées sont passées à la lambda via le constructeur lors de la création de l'objet anonyme que nous avons vu précédemment :

public static final void doSomething() {
    String greetings = "Hello";
    doSomethingElse(new Function(greetings) {
            public final void invoke() {
            System.out.println(this.$greetings + " from lambda");
        }
    });
}

Si vous avez de nombreuses variables locales utilisées à l'intérieur du lambda ou si vous appelez le lambda dans une boucle, le passage de chaque variable locale par le constructeur entraîne une surcharge de mémoire supplémentaire. En utilisant la méthode inline dans ce cas, aide beaucoup, puisque la variable est directement utilisée au niveau de l'appel.

Ainsi, comme vous pouvez le voir dans les deux exemples ci-dessus, la majeure partie de l'avantage en termes de performances de l'option inline est réalisée lorsque les fonctions prennent d'autres fonctions comme arguments. C'est lorsque les inline sont les plus bénéfiques et valent la peine d'être utilisées. Il n'est pas nécessaire de inline d'autres fonctions générales, car le compilateur JIT les met déjà en ligne sous le capot, chaque fois qu'il le juge nécessaire.


Utilisez inline pour un meilleur flux de contrôle

Puisque le type de fonction non-inline est converti en classe, nous ne pouvons pas écrire l'instruction return à l'intérieur du lambda :

fun doSomething() {
    doSomethingElse {
        return    // Error: return is not allowed here
    }
}

C'est ce qu'on appelle non local return parce qu'il n'est pas local à la fonction appelante doSomething() . La raison pour laquelle on n'autorise pas l'utilisation non locale return est que le return existe dans une autre classe (dans la classe anonyme présentée précédemment). En rendant l'instruction doSomethingElse() fonction inline résout ce problème et nous sommes autorisés à utiliser des retours non-locaux car alors la return est copiée dans la fonction appelante.


Utilisez inline pour reified paramètres de type

En utilisant les génériques en Kotlin, nous pouvons travailler avec la valeur de type T . Mais nous ne pouvons pas travailler avec le type directement, nous obtenons l'erreur suivante Cannot use 'T' as reified type parameter. Use a class instead :

fun <T> doSomething(someValue: T) {
    println("Doing something with value: $someValue")               // OK
    println("Doing something with type: ${T::class.simpleName}")    // Error
}

En effet, l'argument de type que nous passons à la fonction est effacé au moment de l'exécution. Nous ne pouvons donc pas savoir exactement à quel type nous avons affaire.

En utilisant un inline ainsi que la fonction reified résout ce problème :

inline fun <reified T> doSomething(someValue: T) {
    println("Doing something with value: $someValue")               // OK
    println("Doing something with type: ${T::class.simpleName}")    // OK
}

L'alignement fait que l'argument de type réel est copié à la place de T . Ainsi, par exemple, le T::class.simpleName devient String::class.simpleName lorsque vous appelez la fonction comme doSomething("Some String") . Le site reified Le mot-clé ne peut être utilisé qu'avec inline fonctions.


Évitez inline lorsque les appels sont répétitifs

Disons que nous avons la fonction suivante qui est appelée de manière répétitive à différents niveaux d'abstraction :

inline fun doSomething() {
    println("Doing something")
}

Premier niveau d'abstraction

inline fun doSomethingAgain() {
    doSomething()
    doSomething()
}

Résultats dans :

public static final void doSomethingAgain() {
    System.out.println("Doing something");
    System.out.println("Doing something");
}

Au premier niveau d'abstraction, le code se développe à : 2 1 \= 2 lignes.

Deuxième niveau d'abstraction

inline fun doSomethingAgainAndAgain() {
    doSomethingAgain()
    doSomethingAgain()
}

Résultats dans :

public static final void doSomethingAgainAndAgain() {
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
}

Au deuxième niveau d'abstraction, le code se développe à : 2 2 \= 4 lignes.

Troisième niveau d'abstraction

inline fun doSomethingAgainAndAgainAndAgain() {
    doSomethingAgainAndAgain()
    doSomethingAgainAndAgain()
}

Résultats dans :

public static final void doSomethingAgainAndAgainAndAgain() {
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
    System.out.println("Doing something");
}

Au troisième niveau d'abstraction, le code se développe à : 2 3 \= 8 lignes.

De même, au quatrième niveau d'abstraction, le code s'accroît de 2 %. 4 \= 16 lignes et ainsi de suite.

Le nombre 2 est le nombre de fois que la fonction est appelée à chaque niveau d'abstraction. Comme vous pouvez le constater, le code croît de manière exponentielle non seulement au dernier niveau mais aussi à chaque niveau, soit 16 + 8 + 4 + 2 lignes. J'ai montré seulement 2 appels et 3 niveaux d'abstraction ici pour rester concis mais imaginez combien de code sera généré pour plus d'appels et plus de niveaux d'abstraction. Cela augmente la taille de votre application. C'est une autre raison pour laquelle vous ne devriez pas inline chaque fonction de votre application.


Évitez inline en cycles récursifs

Évitez d'utiliser le inline pour les cycles récursifs d'appels de fonctions, comme le montre le code suivant :

// Don't use inline for such recursive cycles

inline fun doFirstThing() { doSecondThing() }
inline fun doSecondThing() { doThirdThing() }
inline fun doThirdThing() { doFirstThing() }

Il en résultera un cycle sans fin où les fonctions copieront le code. Le compilateur vous donne une erreur : The 'yourFunction()' invocation is a part of inline cycle .


Ne peut pas utiliser inline lors du masquage de la mise en œuvre

Le public inline ne peuvent pas accéder aux fonctions private et ne peuvent donc pas être utilisés pour le masquage de l'implémentation :

inline fun doSomething() {
    doItPrivately()  // Error
}

private fun doItPrivately() { }

Dans le inline présentée ci-dessus, en accédant à la fonction private fonction doItPrivately() donne une erreur : Public-API inline function cannot access non-public API fun .


Vérification du code généré

Maintenant, concernant la deuxième partie de votre question :

mais j'ai constaté qu'il n'y a pas d'objet fonction créé par kotlin pour une fonction non-inline. Pourquoi ?

El Function est bien créé. Pour voir l'objet créé Function vous devez en fait appeler votre lock() dans la fonction main() comme suit :

fun main() {
    lock { println("Inside the block()") }
}

Classe générée

Le produit Function ne se reflète pas dans le code Java décompilé. Vous devez regarder directement dans le bytecode. Cherchez la ligne qui commence par :

final class your/package/YourFilenameKt$main$1 extends Lambda implements Function0 { }

Il s'agit de la classe qui est générée par le compilateur pour le type de fonction qui est passé à la fonction lock() fonction. Le site main$1 est le nom de la classe qui est créé pour votre block() fonction. Parfois, la classe est anonyme, comme le montre l'exemple de la première section.

Objet généré

Dans le bytecode, recherchez la ligne commençant par :

GETSTATIC your/package/YourFilenameKt$main$1.INSTANCE

INSTANCE est l'objet qui est créé pour la classe mentionnée ci-dessus. L'objet créé est un singleton, d'où le nom de INSTANCE .


C'est ça ! J'espère que vous avez trouvé des informations utiles sur inline fonctions.

23voto

Wini Points 1631

Fonctions d'ordre supérieur sont très utiles et peuvent vraiment améliorer le reusability de code. Cependant, l'efficacité est l'une des principales préoccupations liées à leur utilisation. Les expressions lambda sont compilées en classes (souvent des classes anonymes), et la création d'objets en Java est une opération lourde. Nous pouvons toujours utiliser les fonctions d'ordre supérieur de manière efficace, tout en conservant tous les avantages, en rendant les fonctions inline.

voici le fonction en ligne dans le tableau

Lorsqu'une fonction est marquée comme inline Lors de la compilation du code, le compilateur remplacera tous les appels de fonction par le corps réel de la fonction. De même, les expressions lambda fournies comme arguments sont remplacées par leur corps réel. Elles ne seront pas traitées comme des fonctions, mais comme du code réel.

En bref : - Inline-->plutôt que d'être appelés ,ils sont remplacés par le corps de code de la fonction au moment de la compilation...

En Kotlin, l'utilisation d'une fonction comme paramètre d'une autre fonction (fonctions dites d'ordre supérieur) semble plus naturelle qu'en Java.

L'utilisation des lambdas présente cependant quelques inconvénients. Comme il s'agit de classes anonymes (et donc d'objets), elles ont besoin de mémoire (et peuvent même augmenter le nombre total de méthodes de votre application). Pour éviter cela, nous pouvons mettre nos méthodes en ligne.

fun notInlined(getString: () -> String?) = println(getString())

inline fun inlined(getString: () -> String?) = println(getString())

D'après l'exemple ci-dessus :- Ces deux fonctions font exactement la même chose - imprimer le résultat de la fonction getString. L'une est inlined et l'autre non.

Si vous vérifiez le code java décompilé, vous verrez que les méthodes sont totalement identiques. C'est parce que le mot clé inline est une instruction au compilateur de copier le code dans le site d'appel.

Cependant, si nous passons un type de fonction à une autre fonction comme ci-dessous :

//Compile time error… Illegal usage of inline function type ftOne...
 inline fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
 }

Pour résoudre ce problème, nous pouvons réécrire notre fonction comme suit :

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

Supposons que nous ayons une fonction d'ordre supérieur comme ci-dessous :

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

Ici, le compilateur nous dira de ne pas utiliser le mot clé inline lorsqu'il n'y a qu'un seul paramètre lambda et que nous le passons à une autre fonction. Donc, nous pouvons réécrire la fonction ci-dessus comme suit :

fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
}

Note :- nous avons dû supprimer le mot clé noinline également, car il ne peut être utilisé que pour les fonctions inline !

Supposons que nous ayons une fonction comme celle-ci -->

fun intercept() {
    // ...
    val start = SystemClock.elapsedRealtime()
    val result = doSomethingWeWantToMeasure()
    val duration = SystemClock.elapsedRealtime() - start
    log(duration)
    // ...}

Cela fonctionne bien, mais le cœur de la logique de la fonction est pollué par du code de mesure, ce qui complique la tâche de vos collègues. :)

Voici comment une fonction en ligne peut aider ce code :

 fun intercept() {
    // ...
    val result = measure { doSomethingWeWantToMeasure() }
    // ...
    }
 }

 inline fun <T> measure(action: () -> T) {
   val start = SystemClock.elapsedRealtime()
   val result = action()
   val duration = SystemClock.elapsedRealtime() - start
   log(duration)
   return result
 }

Je peux maintenant me concentrer sur la lecture de l'intention principale de la fonction intercept() sans sauter des lignes de code de mesure. Nous bénéficions également de la possibilité de réutiliser ce code dans d'autres endroits où nous voulons

inline vous permet d'appeler une fonction avec un argument lambda à l'intérieur d'une fermeture ({ ... }) plutôt que de passer le lambda comme measure(myLamda)

Quand cela est-il utile ?

Le mot clé inline est utile pour les fonctions qui acceptent d'autres fonctions, ou lambdas, comme arguments.

Sans le mot clé inline sur une fonction, l'argument lambda de cette fonction est converti au moment de la compilation en une instance d'une interface Function avec une seule méthode appelée invoke(), et le code dans le lambda est exécuté en appelant invoke() sur cette instance Function à l'intérieur du corps de la fonction.

Avec le mot-clé inline sur une fonction, cette conversion au moment de la compilation n'a jamais lieu. Au lieu de cela, le corps de la fonction inline est inséré à l'emplacement de l'appel et son code est exécuté sans la surcharge de la création d'une instance de fonction.

Hmmm ? Exemple dans Android -->

Disons que nous disposons d'une fonction dans une classe de routeur d'activité pour démarrer une activité et appliquer quelques extras.

fun startActivity(context: Context,
              activity: Class<*>,
              applyExtras: (intent: Intent) -> Unit) {
  val intent = Intent(context, activity)
  applyExtras(intent)
  context.startActivity(intent)
  }

Cette fonction crée une intention, applique quelques extras en appelant l'argument de fonction applyExtras, et démarre l'activité.

Si nous regardons le bytecode compilé et le décompilons en Java, cela ressemble à quelque chose :

void startActivity(Context context,
               Class activity,
               Function1 applyExtras) {
  Intent intent = new Intent(context, activity);
  applyExtras.invoke(intent);
  context.startActivity(intent);
  }

Disons que nous l'appelons depuis un écouteur de clic dans une activité :

override fun onClick(v: View) {
router.startActivity(this, SomeActivity::class.java) { intent ->
intent.putExtra("key1", "value1")
intent.putExtra("key2", 5)
}
 }

Le bytecode décompilé pour cet écouteur de clic ressemblerait alors à quelque chose comme ceci :

@Override void onClick(View v) {
router.startActivity(this, SomeActivity.class, new Function1() {
@Override void invoke(Intent intent) {
  intent.putExtra("key1", "value1");
  intent.putExtra("key2", 5);
}
 }
}

Une nouvelle instance de Function1 est créée chaque fois que l'écouteur de clic est déclenché. Cela fonctionne bien, mais ce n'est pas idéal !

Maintenant, ajoutons simplement la méthode inline à notre routeur d'activité :

inline fun startActivity(context: Context,
                     activity: Class<*>,
                     applyExtras: (intent: Intent) -> Unit) {
 val intent = Intent(context, activity)
 applyExtras(intent)
 context.startActivity(intent)
 }

Sans modifier le code de notre écouteur de clics, nous sommes maintenant en mesure d'éviter la création de l'instance Function1. L'équivalent Java du code de l'écouteur de clics ressemblerait maintenant à quelque chose comme ça :

@Override void onClick(View v) {
Intent intent = new Intent(context, SomeActivity.class);
intent.putExtra("key1", "value1");
intent.putExtra("key2", 5);
context.startActivity(intent);
}

C'est tout :)

"Inliner" une fonction signifie essentiellement copier le corps d'une fonction et le coller à l'emplacement de l'appel de la fonction. Cela se produit au moment de la compilation.

6voto

0xalihn Points 5358

Le cas le plus important où l'on utilise le modificateur en ligne est celui où l'on définit des fonctions de type util avec des fonctions paramètres. Le traitement des collections ou des chaînes de caractères (comme filter , ma p ou joinToString ) ou simplement des fonctions autonomes en sont un parfait exemple.

C'est pourquoi le modificateur en ligne est une optimisation importante pour les développeurs de bibliothèques. Ils doivent savoir comment il fonctionne et quelles sont ses améliorations et ses coûts. Nous utiliserons le modificateur en ligne dans nos projets lorsque nous définirons nos propres fonctions utilitaires avec des paramètres de type fonction.

Lorsque nous n'avons pas de paramètre de type fonction, de paramètre de type réifié, et que nous n'avons pas besoin d'un retour non-local, alors nous ne devrions probablement pas utiliser le modificateur en ligne. C'est pourquoi nous aurons un avertissement dans Android Studio ou IDEA IntelliJ.

Il y a aussi le problème de la taille du code. L'insertion d'une fonction de grande taille peut augmenter considérablement la taille du bytecode, car elle est copiée sur chaque site d'appel. Dans ce cas, vous pouvez refactoriser la fonction et extraire le code vers des fonctions régulières.

0 votes

Qu'est-ce que retour non-local ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X