Les Closures en C (et donc Objective-C)

psychoh13psychoh13 Mothership DeveloperMembre
décembre 2009 modifié dans API AppKit #1
Bonjour à  tous, je vais vous présenter une nouveauté ajouté par Apple au langage C.
Apple a publié la gestion d'une nouvelle syntaxe dans les compilateurs GCC et surtout LLVM (je n'ai réussi à  le faire marcher qu'avec celui-là ).

Donc les closures, appelées aussi blocks, sont des petits morceaux de code qui peuvent être passé d'une fonction à  une autre et être appelée dans la fonction appelée. Elle permet notamment un remplacement des fonctions imbriquées qui ne sont pas très sécurisées dans GCC.

La syntaxe des blocks est surprenantes au début.
Pour déclarer une variable de type block, ça ressemble beaucoup à  la syntaxe de déclaration d'un pointeur de fonction, il suffit de remplacer l'étoile '*' par un accent circonflexe '^' :

typeDeRetour (^nomDeLaVariable)(typeParam1 nomParam1, typeParam2 *nomParam2, ...);


Le nom des paramètres n'est pas nécessaire dans la déclaration de la variable.
Exemple :
void (^monBlock)(int, char *);


Cette ligne déclare une variable nommée monBlock, de type block, ne retournant rien et prenant en paramètre un entier et un char*.

Pour exécuter un block c'est comme une simple fonction :
nomDeLaVariable(param1, param2, ...);


Exemple :
monBlock(5, "chaine");


C'est ici que ça se corse, voici la syntaxe complète :
^(typeParam1 nomParam1, typeParam2 nomParam2, ...){ | listeDeVariables | code_du_block };


Donc comme on peut le voir ici, on déclare la liste des arguments du block entre parenthèse entre l'accent circonflexe et l'accolade ouvrante, si le block n'a pas de paramètre, il n'est pas utile de mettre cette partie-là .

Ensuite dans le block, on peut avoir une liste de variables séparées par des virgules entre deux pipes '|' (le signe pour le OU logique ou bit-à -bit), ces variables sont des variables disponibles dans la fonction dans laquelle est déclarée le block. Déclarées des variables dans cette partie-là  donne accès à  celles-ci dans le code du block, ce qui est très avantageux, on le verra plus tard.

Enfin, la partie du code du block, là  c'est du code tout ce qu'il y a de plus normal, on a accès aux paramètres déclarées pour le block et à  la liste des variables entre les barres verticales.


Alors, il y a déjà  deux questions à  ce poser ici :
Si notre block retourne une valeur comment donne-t-on son type ?

Et bien ici c'est assez bizarre, en fait le compilateur utilisera le type de la variable utilisé dans l'instruction "return".
Par exemple si j'écris : "return 5;" mon block retournera un "int", en revanche, si j'écris "return (char)5;" alors mon block retournera un "char".

La deuxième question concerne la liste des variables de la fonction appelante... Celle qu'on donne entre les barres verticales. Est-ce que les changements appliqués à  ces variables se répliquent dans la fonction où a été déclaré le block ?

Et bien normalement c'est illégal, la valeur devrait être copiée, pour l'instant cela reste ambiguë. En effet, le problème proviendrait du fait que si on utilise le block hors de la fonction où il a été défini, c'est-à -dire qu'on utilise le block alors que la fonction n'existe plus, on ne devrait plus avoir accès aux variables disponibles. Cependant un mécanisme de références, et de comptage de références pour les blocks, permet de sortir le block et les variables qu'il utilise de la fonction et de les allouer dans le tas et non plus dans la pile comme c'est le cas par défaut.

Il semblerait même que la syntax "| x |" soit abandonnée. En fait, il ne sera pas nécessaire de dire au block quelles sont les variables de la fonction parente auxquelles il a accès. Il sera par contre interdit de modifier ces variables, à  moins qu'on déclare ces variables comme étant "__byref" dans la fonction parente.
Exemple :
int x;<br />int __byref y;<br />int (^monBlock)(int) = ^(int var){<br />&nbsp; &nbsp; x = var; // erreur : la variable est read-only<br />&nbsp; &nbsp; y = var; // fonctionne<br />&nbsp; &nbsp; return x * y + var; // fonctionne<br />};


Voilà  je pense avoir fait le tour de la syntaxe.

Je parlerai des avantages dans un autre post.
«1

Réponses

  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #2
    Maintenant regardons quels sont les avantages.

    Les avantages d'un tel système c'est de permettre notamment de déclarer du code sans créer de nouvelles fonctions et de le passer à  une autre fonction pour qu'elle l'utiliser.
    ç'a en plus l'avantage, comme on l'a vu plus haut, de donner accès aux variables de la fonction dans laquelle est déclaré le block.

    À l'heure actuelle, pour obtenir le même effet, on est obligé de passer une variable "context" dans le callback d'une telle fonction pour pouvoir avoir, depuis l'appel à  notre fonction, accès à  des informations auxquelles on a pas directement accès par la fonction qui appelle.

    Par exemple, pour trier des objets dans un CFMutableArray de chez Apple, nous utilisons cette fonction :
    void CFArraySortValues (CFMutableArrayRef theArray, CFRange range, CFComparatorFunction comparator, void *context);
    


    Ce sont les deux derniers paramètres que les blocks cherchent à  remplacer par un seul. Nous pouvons voir notre context, et la fonction de comparaison qui doit être déclarée ainsi :
    CFComparisonResult MyCallBack(const void *val1, const void *val2, void *context);
    


    Lorsque le CFArray veut comparer deux variables, la fonction va appeler notre callback en lui passant les deux variables qu'il veut comparer et le context, c'est-à -dire la structure qu'on a passé à  la fonction de tri à  l'origine. Dans ce context on peut mettre tout information qui pourrait influencer notre tri, par exemple on peut y mettre un objet sur lequel appeler une méthode qui traitera à  son tour les deux variables.

    C'est une syntaxe plutôt gênante, non seulement on a besoin de déclarer une fonction à  part mais en plus il faut qu'on crée une structure spécifique pour donner accès à  des valeurs importantes pour notre fonction.

    L'avantage des blocks par rapport à  ça, c'est justement de ne pas avoir besoin de structure spécifique pour accéder à  des valeurs, il suffit simplement que les variable existent dans la fonction dans laquelle est déclarée le block, elles sont ensuite rendues disponible lors de l'appel et leur valeur correspond à  celles qui a été donnée dans la fonction parente du block.
    N'est-ce pas merveilleux ?

    Donc maintenant, au lieu d'avoir une fonction de comparaison comme celle-ci :
    int (*comparator)(void *var1, void *var2, void *context)
    

    on aura un block défini comme ça :
    int(^)(void *var1, void *var2)
    


    On a aussi un autre avantage tout aussi enviable avec les blocks lorsqu'il s'agit d'itération.
    Avant, quand on voulait itérer dans un NSArray par exemple, nous étions obligés de procéder de la sorte :
    for(int i = 0; i &lt; [myArray count]; i++)<br />{<br />&nbsp; &nbsp; id obj = [myArray objectAtIndex: i];<br />&nbsp; &nbsp; NSLog(@&quot;%d: obj = %@&quot;, i, obj);<br />}
    


    C'est une technique très simple et pas très optimisée, elle a tout de même pour avantage de fournir dans le for() l'index de l'objet.

    Apple, avec Objective-C 2.0, a cherché a optimisé les itérations avec un foreach() bien à  eux :
    for(id obj in myArray)<br />{<br />&nbsp; &nbsp; NSLog(@&quot;obj = %@&quot;, obj);<br />}
    

    Ici, on perd l'index de l'objet, mais au moins l'itération est très optimisée car sous le capot le foreach() n'envoie pas un message pour récupérer un objet, mais en général un message pour récupérer plusieurs dizaines d'objets.

    Il est aussi possible d'utiliser ce système avec un NSDictionary :
    for(id key in myDict)<br />{<br />&nbsp; &nbsp; id obj = [myDict objectForkey: key];<br />&nbsp; &nbsp; NSLog(@&quot;myDict[%@] = %@.&quot;, key, obj);<br />}
    


    Là , même si on optimise la récupération des clés, la récupération des objets se fait là  aussi un par un ce qui n'est pas bon pour l'optimisation.
    C'est là  que les blocks montrent leur puissance : dans les itérations.
    Pour itérer dans une collection, on donnera à  une méthode spécifique, le block de code que l'on souhaite lui faire exécuter, en clair on va lui passer le code entre les accolades du for().
    Et ce sera à  la méthode appelée de gérer ce qui va être envoyé au block pour être exécuté.

    Par exemple, pour un NSArray, on pourra avoir un block avec pour type :
    void (^myIter)(int index, id obj);
    


    Comme le suggère le typage du block, la méthode d'itération passera au block non seulement l'objet en cours mais aussi son index, ce qui permettra une gestion plus fine des objets, on pourra avoir exactement le même effet que le premier exemple :

    [myArray enumerateWithBlock: ^(int index, id obj){ NSLog(@&quot;%d: %@&quot;, index, obj); }];
    


    On a donc un double avantage, non seulement on sait précisément quel objet est passé et à  quel point on est, mais en plus comme on passe le block de code à  une méthode, en interne beaucoup d'optimisation peuvent être possible, ce qui rendra l'itération extrêmement rapide et efficace.

    Maintenant, regardons notre exemple des dictionaires, on a vu plus haut que pour avoir la clé et la valeur attachée à  cette clé, il était nécessaire d'itérer sur les clés et de récupérer l'objet que la clé indique. Et bien voilà  ce qu'on peut avoir comme block pour régler ce problème :

    void (^myBlock)(id key, id obj);
    


    De la même manière que pour le NSArray, ici la fonction d'énumération passera au bloc la clé et son objet attaché SIMULTANà‰MENT ce qui est très très avantageux, on peut imaginer toute sorte d'optimisation en arrière-plan, rendant l'itération dans un dictionaire toujours plus efficace.

    Pour avoir le même effet que l'exemple plus haut nous pourrions faire comme ça :
    [myDict enumerateKeysAndObjectsWithBlock: ^(id key, id obj){ NSLog(@&quot;myDict[%@] = %@&quot;, key, obj); }];
    


    Encore une fois ça laisse rêveur.

    Maintenant on va voir un petit exemple qui allie l'itération et l'utilisation de variable existante dans la fonction parente :

    NSArray *myArray; // existe déjà  et est déjà  rempli<br />// Ce NSArray contiendra tous les objets aux index pairs de myArray<br />NSMutableArray *unSurDeux = [[NSMutableArray alloc] initWithCapacity: [myArray count] / 2];<br /><br />[myArray enumerateWithBlock: ^(int index, id obj){<br />&nbsp; &nbsp; if(index % 2 == 0) [unSurDeux addObject: obj];<br />}];
    


    Après l'exécution de la méthode, le NSArray "unSurDeux" contiendra tous les objets pairs de "myArray". ;)

    Neat isn't ? ;)
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #3
    Ces nouveautés seront disponibles nativement dans Snow Leopard.
    Cependant, il est possible de les tester aujourd'hui en téléchargeant et compilant les dernières version de LLVM et LLVM-GCC-4.2, la procédure pour compiler correctement et tester les blocks est expliquée dans le dossier de LLVM-GCC dans le fichier "README.LLVM".

    Malheureusement, compiler LLVM-GCC de cette manière ne permet pas d'utiliser le compilateur avec Xcode pour pouvoir compiler des applications Cocoa correctement.
    La technique de compilation d'Apple qui permet d'obtenir un LLVM compatible avec Xcode nativement n'est pas faisable, apparemment, sans les outils réservés aux employés d'Apple. Certaines options sont différentes et nécessitent donc de créer un fichier de configuration spécifique à  ce LLVM... Ce que je n'ai pas encore réussi à  faire.

    PS : Ces posts ne vont pas à  l'encontre d'une quelconque NDA, à  part peut-être la première phrase de ce post-ci, mais le reste est disponible au public notamment sur llvm.org.
  • Philippe49Philippe49 Membre
    23:20 modifié #4
    Merci pour ces infos.
    Penses-tu que cette innovation des blocks puisse rentrer à  terme dans le C standard ?
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #5
    Je sais pas du tout, mais je pense pas... Encore faudrait-il que le comité de standardisation se réveille...
    Mais bon, déjà  il est intégré à  llvm et gcc donc il peut-être utilisé n'importe où.

    As-tu tenter d'essayer par toi-même ?
  • Philippe49Philippe49 Membre
    23:20 modifié #6
    dans 1222716831:

    As-tu tenter d'essayer par toi-même ?

    Non mais ton explication est très claire.
    On sent bien le potentiel d'optimisation qu'il y là  dessous, et en même temps cela correspond aussi à  des moments de codage où on s'est dit "la barbe, on l'a déjà  fait".
    Faire une macro c'est toujours un peu tordu, une fonction static inline pose le pensum du passage des arguments, et les boucles foreach où on peut avoir besoin à  la fois de l'objet, de l'index/clé c'est ultra-courant. Cela répond sans doute à  un vrai besoin.
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #7
    Voici un exemple de closure avec une itération.
    On utilise comme collection les tableaux passés en paramètre de main :

    #include &lt;stdio.h&gt;<br /><br />void *_NSConcreteStackBlock;<br /><br />void iterateCollection(char **collection, void(^block)(int index, char *str));<br /><br />int main (int argc, char** argv, char** env)<br />{<br />&nbsp; &nbsp; void (^myBlock)(int index, char *str) =<br />&nbsp; &nbsp; ^(int index, char *str){ printf(&quot;[%d] = %s&#092;n&quot;, index, str); };<br />&nbsp; &nbsp; <br />&nbsp; &nbsp; iterateCollection(argv, myBlock);<br />&nbsp; &nbsp; iterateCollection(env, myBlock);<br />}<br /><br />void iterateCollection(char **collection, void(^block)(int index, char *str))<br />{<br />&nbsp; &nbsp; int i = 0;<br />&nbsp; &nbsp; while (*collection != NULL)<br />&nbsp; &nbsp; {<br />&nbsp; &nbsp; &nbsp; &nbsp; block(i, *collection);<br />&nbsp; &nbsp; &nbsp; &nbsp; collection++;<br />&nbsp; &nbsp; &nbsp; &nbsp; i++;<br />&nbsp; &nbsp; }<br />}
    


    Ce code affichera dans la console les paramètres passés au programme ainsi que les variables d'environnement.
  • Philippe49Philippe49 Membre
    23:20 modifié #8
    Clair !! 

    Tu mets une option pour compiler ? j'ai essayé gcc-4.2 pgm.c -o pgm -Wall -Wextra mais il n'accepte pas le ^
  • psychoh13psychoh13 Mothership Developer Membre
    septembre 2008 modifié #9
    Non, je ne mets pas d'option, mais je n'ai pas essayé avec GCC, il vaut mieux que tu tentes avec LLVM-GCC.
    J'ai téléchargé LLVM grâce à  cette commande : "svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm"

    Ainsi que llvm-gcc avec celle-ci : svn co http://llvm.org/svn/llvm-project/llvm-gcc-4.2/trunk dst-directory

    La procédure est plus ou moins bien décrite dans le fichier README.LLVM dans gcc-llvm-4.2. Il faut compiler llvm avec en option de la commande make : "make ENABLE_OPTIMIZED=1".
    Ensuite tu crées la variable d'environnement $LLVMOBJDIR qui va contenir le dossier où a atterri l'application LLVM compilée et ses copines.
    Ensuite le reste c'est suffisamment bien expliqué, il faut que tu crées des variables d'environnement à  chaque fois que tu vois quelque chose d'écrit en majuscules suivi d'un égal.
    Et aussi la version de Darwin sous Leopard c'est censé être la 9.

    J'ai galéré pour faire tout ça. lol
  • Philippe49Philippe49 Membre
    23:20 modifié #10
    llvm-gcc-4.2 est désormais disponible avec XCode 3.1.1

    /Developer/usr/llvm-gcc-4.2/bin/llvm-gcc-4.2
    et
    /Developer/usr/llvm-gcc-4.2/bin/i686-apple-darwin9-llvm-gcc-4.2


  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #11
    Et t'as essayé de voir si les blocks marchaient ?
  • Philippe49Philippe49 Membre
    23:20 modifié #12
    Oui, j'ai essayé cela ne marche pas,

    % /Developer/usr/llvm-gcc-4.2/bin/llvm-gcc-4.2 pgm.c -o pgm
    ...
    error: expected ‘)' before ‘^' token

    idem avec l'autre binaire


    Je n'ai pas trouvé le man
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #13
    Pas étonnant, llvm-gcc-4.2 est déjà  disponible dans Xcode 3.1, à  mon avis ils les ont pas recompilé.
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #14
    Voilà , maintenant les closures sont disponibles en C et en C++, de plus tout ce qu'il sera possible de faire sur les fonctions (par exemple mettre des __attribute__((noreturn)) ou des sentinels) sera possible sur les blocks. On peut aussi créer des blocks au niveau global, plus besoin de les mettre dans une fonction, et pour finir, un block sera aussi capable de répondre à  des messages ObjC.
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #15
    Bonjour à  tous,

    Je relance pour la n-ième  fois le sujet... Pour vous annoncer la découverte de deux documents publiés par Apple, depuis déjà  un certain temps, sur les spécifications de l'extension des blocks dans le langage ainsi que de l'implémentation de ceux-ci.

    Voici les liens :
    Language specs
    Implementation specs

    Le premier ne dit pas vraiment plus que ce ce que je dis dans le reste du sujet.

    En revanche, le deuxième donne des informations très intéressantes sur comment est implémenté ce système, et aussi sur comment le compilateur interprète les blocks pour construire des structures permettant de les utiliser.

    Grâce à  ce document, on en sait plus sur le fonctionnement du runtime des blocks, qui est constitué de deux fonctions permettant la copie des structures représentant les blocks depuis la pile vers le tas. Ce système permet l'utilisation de ces structures à  l'extérieur du context dans lequel elles ont été créé tout en conservant la valeur des variables capturées dans ce context.

    C'est à  partir de ce document que j'ai écrit mon propre runtime permettant l'utilisation des blocks avec la plupart des fonctionnalités qu'ils proposent.

    Mon runtime permet aux blocks de recevoir des messages à  la manière d'objet Objective-C et il permet de copier les blocks sur le tas pour les utiliser à  l'extérieur du context de création.

    Il vous est possible, grâce au compilateur Clang, d'utiliser cette fonctionnalité de manière transparante dans vos projets actuels pour Leopard. Vous pouvez suivre les instructions que je donne dans le README.txt du répertoire de mon code. Le runtime est sous license MIT, la moins contraignante des licenses open-source, vous pouvez donc l'utiliser sans problème.

    Voici le lien du répertoire : http://github.com/PsychoH13/C-ObjC-Blocks/

    N'hésitez pas à  me poser des questions, où à  faire des commentaires. Merci.
  • AliGatorAliGator Membre, Modérateur
    23:20 modifié #16
    Un petit lien vers la partie de La Review de Snow Leopard par Ars Technica qui parle des nouveautés de 10.6 sur les API de programmation Mac et surtout les Blocks justement.
  • Paisible.frPaisible.fr Membre
    23:20 modifié #17
    Personnelement je trouve la syntaxe de la chose particulièrement alambiqué.
    Mais c'est certainement parce que c'est nouveau pour moi

    En tout cas, ça à  l'air fort prometteur.
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #18
    Bah c'est un syntaxe un peu bizarre, mais tu devrais être habituée aux syntaxes un peu "bizarre" si tu fais de l'ObjC. ;)

    Mais sinon elle est largement inspirée des pointeurs de fonctions, et puis l'accent circonflexe c'est en quelque sorte le nom de la fonction qu'un block, étant anonyme, ne peut avoir.
  • Philippe49Philippe49 Membre
    septembre 2009 modifié #19
    Effectivement avec Snow Leo, on peut maintenant profiter des blocks.
    Je reprends le code de Psycho quelques posts ci-dessus
    #include &lt;stdio.h&gt;<br /><br />// void *_NSConcreteStackBlock;<br /><br />void iterateCollection(char **collection, void(^block)(int index, char *str));<br /><br />int main (int argc, char** argvi, char** env)<br />{<br />	void (^myBlock)(int index, char *str) =<br />	^(int index, char *str){ printf(&quot;[%d] = %s&#092;n&quot;, index, str); };<br />	<br />	iterateCollection(argvi, myBlock);<br />	iterateCollection(env, myBlock);<br />	<br />}<br /><br />void iterateCollection(char **collection, void(^block)(int index, char *str))<br />{<br />	int i = 0;<br />	while (*collection != NULL)<br />	{<br />		block(i, *collection);<br />		collection++;<br />		i++;<br />	}<br />}
    


    je compile avec la version par défaut de gcc

    % gcc --version
    i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5646)
    Copyright (C) 2007 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    % gcc  pgm.c -o pgm
    %


    J'exécute


    % pgm coucou world
    [0] = pgm
    [1] = coucou
    [2] = world
    [0] = TERM_PROGRAM=Apple_Terminal
    [1] = TERM=xterm-color
    [2] = SHELL=/bin/bash
    [3] = TMPDIR=/var/folders/jb/jbsOdBd4F1yyP1lsmrpcI++++TM/-Tmp-/
    [4] = Apple_PubSub_Socket_Render=/tmp/launch-EtUaep/Render
    [5] = TERM_PROGRAM_VERSION=272
    [6] = OLDPWD=/Users/bureau
    [7] = USER=bureau
    [8] = COMMAND_MODE=unix2003
    [9] = SSH_AUTH_SOCK=/tmp/launch-7mEvG7/Listeners
    [10] = __CF_USER_TEXT_ENCODING=0x1F6:0:1
    [11] = PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/Users/bureau/Desktop:/Users/bureau/usr/bin/
    [12] = PWD=/Users/bureau/Desktop
    [13] = LANG=fr_FR.UTF-8
    [14] = SHLVL=1
    [15] = HOME=/Users/bureau
    [16] = LOGNAME=bureau
    [17] = LC_CTYPE=fr_FR.UTF-8
    [18] = DISPLAY=/tmp/launch-gM4LY3/:0
    [19] = _=/Users/bureau/Desktop/pgm


    Impec
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #20
    Dis-le que c'est beau :P

    Par contre tu peux virer le void *_NSConcreteStackBlock; au début du fichier, ça devrait marcher sans.

    De plus normalement pour que les blocks fonctionnent il faudrait utiliser l'option -fblocks.
  • Philippe49Philippe49 Membre
    septembre 2009 modifié #21
    dans 1252773192:

    Dis-le que c'est beau :P

    C'est beau comme une crèche, .... je mate  :)

    dans 1252773192:

    Par contre tu peux virer le void *_NSConcreteStackBlock; au début du fichier, ça devrait marcher sans.

    Ok

    dans 1252773192:

    De plus normalement pour que les blocks fonctionnent il faudrait utiliser l'option -fblocks.


    A l'essai, cela devient utile si on met -std=c99, par exemple pour faire une déclaration locale de variable de boucle : for(int i=0;...;...)
    Par contre je ne vois pas trace de -fblocks dans le man ?
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #22
    Bizarre ils ont du le mettre par défaut dans leur branche à  eux. Bon en tous les cas c'est beaaauuu et super pratique.

    Sinon, sous SL, tu peux rajouter /Developer/usr/bin dans ton path pour avoir accès direct depuis la console à  Clang et LLVM-GCC.

    Dans ton fichier ~/.bash_login tu rajoutes :
    PATH=$PATH:/Developer/usr/bin
    

  • AliGatorAliGator Membre, Modérateur
    septembre 2009 modifié #23
    Moi aussi je suis fan. C'est les avantages des pointeurs de fonctions, sans les inconvénients, et avec en plus les avantages de la définition "en ligne" et pas extérieurement... Et puis... bah c'est beau quoi :P

    Par contre perso pour rendre ton code plus lisible Philippe je mettrais un typedef sur ton type de block :
    #include &lt;stdio.h&gt;<br /><br />typedef void (^IndexedCStringPrinter)(int idx, char* s);<br /><br />void iterateCollection(char** collection, IndexedCStringPrinter printer)<br />{<br />	int i=0;<br />	while(*collection) { printer(i++,*collection++); }<br />}<br /><br /><br /><br />void main(int argc, char* argv&#91;], char* env&#91;])<br />{<br />	IndexedCStringPrinter myPrinter = ^(int i, char* s) {<br />		printf(&quot;[%02d] = %s&#092;n&quot;,i,s);<br />	};<br /><br /><br />	iterateCollection(argv, myPrinter);	<br />	printf(&quot;------&#092;n&quot;);<br />	iterateCollection(env,  myPrinter);	<br />}
    
    Juste histoire de monter qu'on peut typedef-er les blocks (comme on peut le faire avec les pointeurs de fonctions) et qu'à  mon goût ça rend la chose encore plus lisible (et appréhendable pour qqun qui ne connais pas la syntaxe des blocks et à  qui cette surcharge syntaxique de partout pourrait faire peur :P)
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #24
    C'est mon code à  l'origine... Mais pas grave. :P

    Au passage, dans Xcode 3.2, lorsque vous taper le nom d'une méthode contenant des blocks en paramètres, vous avez les espaces dédiés au paramètre entre lesquels vous pouvez circuler, mais en plus si vous appuyer sur enter sur la paramètre de type block il créera un block vide dont le type est adapté au paramètre !!! En revanche ça ne marche pas si c'est un typedef, par exemple avec NSComparator ça marchera pas...
  • AliGatorAliGator Membre, Modérateur
    23:20 modifié #25
    Oups, ah oui tiens pour le coup Philippe l'ayant repris (pourtant il l'a même mentionné qu'il l'avait repris...), m'a gourré, désolé... Bah tu me diras combien je te dois en droit d'auteur :)

    Bien vu pour l'autocomplétion des blocs dans Xcode, trop fort :P
    Par contre dommage que ça ne fonctionne pas pour les typedefs, qui sont pourtant bien pratique pour éclaircir la compréhension du code (comme le montre mon remaniement de ton code ou l'utilisation de NSComparator chez Apple) et que perso dans le cas des pointeurs de fonctions ou des blocks, j'utilise tout le temps plutôt que la "version longue"... zut.
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #26
    Nan mais NSComparator c'est compréhensible de le mettre en typedef parce qu'il y est utilisé très souvent, pour les autres pas vraiment.
  • AliGatorAliGator Membre, Modérateur
    septembre 2009 modifié #27
    Oui.
    Enfin quoique "pour les autres pas vraiment", ça dépend, enfin ça se discute ;)

    Moi question lisibilité, à  part si j'utilise ledit bloc (ou pointeur de fonction) juste une fois dans mon code, maxi 2 mais pas plus, je trouve qd mm plus clair et lisible d'avoir un typedef pour définir ce pointeur de fonction ou ce block... surtout si ça commence à  être un pointeur de fonction ou bloc avec pas mal d'arguments  et des pointeurs partout, fastidieux à  réécrire à  chaque fois pour la déclaration des variables de ce type ou des prototypes des fonctions qui prennent ces blocks / PdF en paramètres.


    Parce que bon, imagine que dans ton code tu aies 3 fonctions prennant en paramètre un block ayant pour signature :
    char* (^myBlock)(int* n, char** v, char** t, char* (^subiterator)(int n1, int n2, char* s, char* t), int* errcode);
    
    Bon évidemment j'exagère, mais bon tu vois le truc, ça peut commencer à  faire long... Car là  je n'ai mis que la déclaration de variable, mais après si ce n'est qu'un type de block à  passer en tant qu'un argument (potentiellement parmi d'autres) à  une fonction... Ca commence à  faire long ! :)
    Et puis même si ce n'est qu'une variable, là  je n'ai fait que la déclarer, mais après s'il faut l'affecter c'est pas mieux :
    char* (^myBlock)(int* n, char** v, char** t, char* (^subiterator)(int n1, int n2, char* s, char* t), int* errcode) = ^(int* n, char** v, char** t, char* (^subiterator)(int n1, int n2, char* s, char* t), int* errcode) {<br />  ...<br />};
    
    Pfiou ! Alors qu'avec des typedefs, ça donnerait :
    BlockType1 myBlock = ^(int* n, char** v, char** t, BlockType2 subiterator, int* errcode) {<br />  ...<br />};
    
    ça donne déjà  plus sympa à  la lecture, non ? :P

    Bon bien sûr tu ne rencontres pas tous les jours des signatures de blocks aussi alambiquées et prise de tête, je te le concède sans problème. Encore moins des blocks prenant lui-mm des blocks en paramètre. ;)
    Et encore que ça m'est déjà  arrivé (enfin avec des pointeurs de fonction, mais bon) dans un de mes projets, où j'avais une architecture pas mal basée sur les plugins et pointeurs de fonction.



    Mais bon après là  c'est juste pour mettre en avant avec un cas extrême que parfois ça peut avoir une syntaxe super lourde, alors que si tu transformes ça avec des typedefs c'est bien plus lisible ;)
    Oh et puis surtout, je trouve ça plus lisible d'avoir le nom de variable à  la fin, pour retrouver une syntaxe similaire à  [tt]int n[/tt] dans [tt]TypeBlock1 myBlock[/tt], alors que sans typeDef le nom de variable est coincé en plein milieu de la ligne de déclaration, je trouve ça moyen côté cohérence comme côté lisibilité

    Bon après chacun ses habitudes de codage. Et pour un bloc qui prend juste un int et un char*, ça reste raisonnable de ne pas utiliser de typedef, mais bon, fait pas aller bcp plus loin dans les types d'arguments quoi.
  • Philippe49Philippe49 Membre
    septembre 2009 modifié #28
    J'utilise également les typedef pour les pointeurs de fonctions, surtout au niveau formation. Cela a le mérite de supprimer la difficulté syntaxique pour des gens qui débutent, tout en conservant l'enthousiasme de la découverte de cette possibilité.

    Par contre j'aimerais demander votre avis sur la disposition du code. Je suis pour ma part intransigeant sur la déclaration de tous les prototypes dans un code C, n'acceptant pas la disposition

    #include &lt;stdio.h&gt;<br /><br />...<br /><br />void iterateCollection(char** collection, IndexedCStringPrinter printer) {<br />...<br />}<br /><br />void main(int argc, char* argv&#91;], char* env&#91;]) {
    



    mais imposant :
    #include &lt;stdio.h&gt;<br /><br />...<br />// Documentation<br />void iterateCollection(char** collection, IndexedCStringPrinter printer); <br /><br />void main(int argc, char* argv&#91;], char* env&#91;]) {<br />}<br /><br />void iterateCollection(char** collection, IndexedCStringPrinter printer) {<br />...<br />}<br /><br />
    


    Quelle est votre position sur le sujet ? (étant bien entendu que pour le code de ce post, on s'en fout)
  • Philippe49Philippe49 Membre
    septembre 2009 modifié #29
    Autres questions :

    1) En tant que professionnel, comptez-vous utiliser beaucoup cette option, et quelles situations standards d'utilisation vous viennent à  l'esprit ?

    2) Est-ce que cela est disponible de base dans les autres systèmes UNIX ?

    3) En clair, un enseignement C de bonne qualité doit-il à  terme inclure les blocks ?
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #30
    Personnellement, j'utiliserais la deuxième technique, c'est-à -dire prototype avant, et définition après. Tout simplement par lisibilité.

    En effet, le fait de ne pas créer de prototype te force à  déclarer toutes les fonctions avant qu'elles soient utilisées, déjà  si A appelle B et B appelle A ça se pose un gros problème pour l'ordre qui est tout simplement impossible à  trouver. Mais au-delà  de ça, ça te force surtout à  ordonner tes fonctions dans l'ordre d'utilisation et non pas dans un ordre plus logique que serait l'importance de la fonction ou le groupement de fonctions par type ou par tâche. Et au final tu te retrouves avec la fonction d'entrée du programme main() à  la fin de ton fichier ! Alors qu'en général on suppose que l'entrée est au début.

    Donc pour moi, on déclare les prototypes de fonctions au début du fichier pour dire "voilà  toutes les fonctions utilisées dans ce programme" puis on définit le main pour dire "voilà  comme marche le programme" et enfin on définit les autres fonctions pour dire "voilà  comment chaque fonction est définie" et bien sûr on les met dans un ordre permettant une meilleure lisibilité.

    Et pour finir, le fait de déclarer les prototypes avant et les définitions après permet, pour les débutants, d'introduire plus facilement le concept de programmation modulaire et d'en-tête. En effet, comme les en-têtes ne contiennent que des déclarations de types et de fonctions utiles pour utiliser une API, ces en-têtes sont inclus en début de fichier, alors que les fichiers de définitions de fonctions sont compilés à  part.

    Cependant, la première technique peut avoir un aspect pédagogique pour montrer comme fonctionne le compilateur C surtout en ce qui concerne les ordres d'apparition.
  • psychoh13psychoh13 Mothership Developer Membre
    23:20 modifié #31
    Pour ce qui est de l'utilisation des blocks :

    1) Mon boulot, pour l'instant, se situe essentiellement dans le monde mac, bon je dois encore développer pour Leopard, donc pas de blocks à  priori (bien qu'il existe un runtime pour leopard, qui est inspiré d'un runtime que j'avais fait d'ailleurs), cependant, dès que je ne développerai plus que pour Snow Leopard et au-delà  je ne ferai qu'utiliser les blocks. Tout simplement parce qu'ils améliorent la lisibilité du code et qu'ils vont permettent aux implémenteurs du code qui les utilise (Apple par exemple) d'améliorer les performances facilement sans détruire notre code.

    2) Les compilateurs Clang et LLVM-GCC qui sont open-source définisse les blocks et sont utilisables partout, donc sur Unix, les spécifications du langage, le runtime et les spécifications du runtime sont tout autant Open Source donc utilisable partout et notamment sur Unix. De plus, je sais que le projet BSD va petit à  petit changer de compilateur principal et passer à  Clang pour se débarrasser de GCC ce qui va mettre les blocks pratiquement en standard sur cette plateforme.

    Il faut aussi ajouter que le projet GNUStep, en partie grâce à  moi, intègre déjà  un runtime pour les blocks et va lui aussi petit-à -petite passer à  Clang car avec celui-là  ils sont sûrs qu'ObjC sera toujours soutenu par le compilateur, alors qu'il est abandonné dans la branche principale de GCC.

    3) Maintenant, pour ce qui est d'enseigner l'existence des blocks. Moi je dirais OUI, ne serait-ce que pour inciter les gens à  vouloir les utiliser et ainsi les mettre en standard de facto. Peut-être au début tu mets les blocks en extra, indiquant qu'ils existent, comment on les utilise, les avantages et inconvénients, mais genre pour les personnes qui voudraient en savoir plus.

    Mais une nouvelle technologie ne peut réellement s'imposer que si les gens en parlent (dans le milieu concerné bien sûr), donc c'est toujours bon d'en parler à  des débutants C, vers la fin car il s'agit d'un concept plutôt complexe mais surtout avec des cas critiques difficilement soupçonnable.
    D'un autre côté, les blocks ont été conçues d'une telle manière qu'ils ne détruisent en rien le C lui-même ; certes il s'agit d'une nouvelle syntaxe, mais elle n'a pas plus d'effet sur le C que la syntaxe d'envoi de messages d'ObjC, c'est-à -dire qu'il est tout à  fait de reproduire ce que fait la syntaxe du block en C pur et que cette syntaxe n'est qu'une simplification. Ce qui fait qu'on se retrouve avec les mêmes cas limites qu'en C pur (notamment niveau portée des variables), et donc apprendre comment fonctionnent les blocks ainsi que ses cas limites peut aider les débutants à  mieux comprendre certains aspects obscures et méconnus du C.

    Exemple : "Pourquoi ce code ne marche pas ?":
    <br />void testNumber(int g, int val)<br />{<br />&nbsp; &nbsp; void (^blk)(int);<br /><br />&nbsp; &nbsp; if(g == 0)<br />&nbsp; &nbsp; &nbsp; &nbsp; blk = ^(int i) { if(i &gt; 10) printf(&quot;The number is correct.&quot;); };<br />&nbsp; &nbsp; else if(g == 1)<br />&nbsp; &nbsp; &nbsp; &nbsp; blk = ^(int i) { if(i &lt; 10) printf(&quot;The number is correct.&quot;); };<br /><br />&nbsp; &nbsp; blk(val);<br />} <br />
    


    Donc selon moi, les blocks sont une bonne chose à  apprendre, non seulement pour les répandre mais aussi pour comprendre encore mieux le C.
Connectez-vous ou Inscrivez-vous pour répondre.