Flash info

Prenez une longueur d'avance avec PowerShell. Faire ce choix c'est anticiper l'avenir des produits Microsoft mais aussi être plus performant dans son travail quotidien d'admin système.

 
Accueil arrow Forum

Bienvenue sur le forum PowerShell-Scripting.com

 
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
The powers hell ou la puissance de l'enfer - 2/10/08 13:27 J'ai trouvé ce script sur le blog d'un développeur japonais.
Ce script implémente les closures, +- autorise la persistence des variables locales entre plusieurs appels de fonction.

On peut y voir, difficilement , que le PS peut être un vrai casse-tête.
Je propose aux courageux une petite séance de reverse, en gros c'est comment que ça marche
Code:

  function global:closure #From http://csharper.blog57.fc2.com/blog-entry-218.html         param ([ScriptBlock]$private:script)     trap { break; }      #Vérification de l'argument          if ($null -eq $script)         { throw 'The script is null.' }      #La variable ClosureStore, de type hash table, maintient la liste des closures         if ($null -eq $global:ClosureStore)     {        Set-Variable 'ClosureStore' @{} -Scope 'global' -Option 'Constant, AllScope';     }     #Supprime dans la hastable les éléments qui ont été collectés par le GC     ($ClosureStore.GetEnumerator() | ? { !$_.Value.IsAlive; }) | ? { $() -ne $_; } | % { $ClosureStore.Remove($_.Key); };          #Mémorisation de l'environnement de la portée enfant (toutes les variables sauf les variables  automatiques)     $autoVariableNames =          @(             '$''?''^''_''args''ConfirmPreference''ConsoleFileName''DebugPreference''Error''ErrorActionPreference',              'ErrorView''ExecutionContext''false''FormatEnumerationLimit''foreach''HOME''Host''input''LASTEXITCODE''lastWord',              'line''Matches''MaximumAliasCount''MaximumDriveCount''MaximumErrorCount''MaximumFunctionCount''MaximumHistoryCount''MaximumVariableCount''MyInvocation''NestedPromptLevel',              'null''OutputEncoding''PID''PROFILE''ProgressPreference''PSHOME''PWD''ReportErrorShowExceptionClass''ReportErrorShowInnerException''ReportErrorShowSource',              'ReportErrorShowStackTrace''ShellId''StackTrace''switch''true''VerbosePreference''WarningPreference''WhatIfPreference'         );     $private:environment = & { return Get-Variable | ? { $autoVariableNames -notcontains $_.Name }; };     #Création de l'objet Closure, combinant le scriptbloc reçu en paramétre et l'environnement.     $private:closure =          New-Object 'PSObject' |              Add-Member 'Script' $script -MemberType 'NoteProperty' -PassThru |              Add-Member 'Environment' $environment -MemberType 'NoteProperty' -PassThru;     # Mémorisation de la fermeture dans le magasin. Le GUID est la clé qui désigne la fermeture, passée  en tant une référence faible (weak reference)     $private:closureId = [Guid]::NewGuid();     $ClosureStore.Add($closureId, [WeakReference]$closure);          #Construit l'appel de la fermeture, on y insére le GUID dynamiquement     $private:invokerText =  "InvokeClosureScript `"$closureId``$Args;";     #On convertit le texte en un script, tout en castant le résultat dans le type PSObject     #Ce script block exécutera le traitement réel contenu dans la fermeture      $private:invoker = [PSObject](Invoke-Expression "$invokerText }");     #La durée de vie du script et de l'environnement est dépendant du fait que l'environnement est  attaché au scriptblock     Add-Member -InputObject $invoker -Name 'Closure' -Value $closure -MemberType 'NoteProperty';     return $invoker; } function global:InvokeClosureScript {     param ([Guid]$private:closureId, [Array]$private:Args_)     #On récupére la référence faible sur la fermeture hébergé dans le magasin ($ClosureStore)      $private:closure = $ClosureStore[$closureId].Target;     if ($null -eq $closure)       { throw 'The closure which in ID which it appoints relation is attached does not exist.'; }          #L'appel de fonction est construit dynamiquement         $private:invokerText = 'param ([ScriptBlock]$private:script, [Array]$private:Args_) .$script';            #On ajoute les possibles paramètres      for ($private:i = 0; $i -lt $Args_.Length; $i++) { $invokerText += " `$Args_[$i]"; }          #On convertit le texte en un script,      $private:invoker = Invoke-Expression "{ $invokerText }";          #On charge l'environnement et on exécute la fermeture dans la portée d'enfant     $private:result =          &{              #Charge l'environnement             $Args[1].Environment | % { trap { continue; } $ExecutionContext.SessionState.PSVariable.Set($_); };             #Execute la fermeture             return .$Args[0] $Args[1].Script $Args[2];         } $invoker $closure $Args_;     return $result; }


Un exemple de fonction
Code:

   function NewCounter {     $i 0;     return closure {         $i++;         return $i;     }; } $counter NewCounter; $counter &$counter# renvoie 1 &$counter# renvoie 2 &$counter# renvoie 3


Un exemple de fonction utilisant un paramètre
Code:

  function NewDecorator {     param ([string]$decoration)     return closure {         param ([string]$text)         return $decoration $text $decoration;     }; } $sharpDecorator NewDecorator '#'; $sharpDecorator &$sharpDecorator "hoge"# renvoie #hoge# &$sharpDecorator "fuga"# renvoie #fuga# &$sharpDecorator "piyo"# renvoie #piyo#


J'oubliais l'aspirine n'est pas fournie
L'objectif est certes différent de celui de l'Obfuscated PowerShell mais pas loin !

A propos des références faibles sous .NET:
http://merlin.developpez.com/cours/dotnet/WeakReference/
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 3/10/08 12:55 Voci quelques informations au sujet de ces 2 fonctions.

Les acteurs de l'avant-plan
Code:

  function NewCounter {     $i 0     return closure {         $i++;         return $i     }; } $counter NewCounter $counter &$counter# 1


La fonction NewCounter est une fonction quelconque utilisant une closure.
$i est une variable locale à la fonction NewCounter.
Les lignes suivantes :
Code:

  return closure {         $i++;         return $i     };


sont constituées :
-d'une instruction return,
-d'une création de closure (appel à function:Closure). On lui passe en paramètre un scriptblock qui incrémente la variable locale $i puis renvoi le résultat.
Notez qu'ici l'auteur crée en quelque sorte une nouvelle fonctionnalité du langage, cf. DSL((Domain-Specific Language).

L'affectation suivante :
Code:

  $counter NewCounter


appelle la fonction NewCounter qui initialise la variable locale $i et la closure.
$Counter affiche le contenu de la variable $counter qui est de type scriptblock :
Code:

  InvokeClosureScript "5cd56fbb-86a8-44d9-a7eb-bcbf8770760e" $Args;


Cette variable contient un membre synthétique nommé Closure
Code:

  $counter.Closure


Il est constitué des 2 membres suivants :
Code:

  $counter.Closure.Script $counter.Closure.Environment 


$counter.Closure.Script est un scriptblock qui contient {$i++;return $i};
$counter.Closure.Environment est un tableau de variables qui contient les variables locales déclarées dans la fonction NewCounter et utilisées dans Closure.Script. Pour rappel $i n'est pas accessible hors de la fonction NewCounter. On doit donc mémoriser le "contexte d'exécution" pour retrouver les variables dans l'état où elles étaient lors du dernier appel. On ne mémorise pas les variables automatiques.
Le scriptblock Closure.Script est en quelque sorte le corps de la méthode de la fonction NewCounter.
Son fonctionnement ressemble, de loin, à celui du code suivant :
Code:

    $global:$i 0 function NewCounter {   $global:$i++;   return $global:$i }


Tout en sachant que dans ce cas la variable $i est accessible dans toutes les portées, console, script,etc.

La fonction closure crée une variable globale constante nommée $ClosureStore de type hash table.
Code:

   Set-Variable 'ClosureStore' @{} -Scope 'global' -Option 'Constant, AllScope';


Cette variable globale mémorise le scriptblock à exécuter (Closure.Script) et l'environnement dans lequel l'exécuter (Closure.Environment). L'environnement référence toutes les variables, sauf les variables automatiques, existantes au moment de l'appel à la fonction Closure.

Les acteurs d'arrière-plan :
TODO
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 4/10/08 14:27 Les acteurs d'arrière-plan :
1)La fonction globale Closure
Cette fonction est appelée à chaque fois que l'on crée une closure (fermeture) :
Code:

  $counter NewCounter $counter2 NewCounter #Crée une deuxiéme closure


pour le vérifier :
Code:

  $ClosureStore #Name                           Value #----                           ----- #730713d6-127a-4350-b74c-746... System.WeakReference #5cd56fbb-86a8-44d9-a7eb-bcb... System.WeakReference


Enfin plus exactement c'est la ligne d'instructions suivante, présente dans le code de la fonction NewCounter, qui crée la fermeture :
Code:

       return closure {


le code suivant :
Code:

  &$Counter


Ne crée pas de fermeture mais l'exécute, le & commerciale exécutant un scriptblock, on a vu précédement que son contenu était :
Code:

  InvokeClosureScript "5cd56fbb-86a8-44d9-a7eb-bcbf8770760e" $Args;


voyons le détail de cette ligne :
-Nous aborderons plus avant la fonction InvokeClosureScript
-"5cd56fbb-86a8-44d9-a7eb-bcbf8770760e" est un GUID, donnée pseudo-aléatoire. Ce GUID est la clé de la fermeture mémorisée dans la hastable $ClosureStore.
Code:

   $private:closureId = [Guid]::NewGuid();  


-$Args est un tableau contenant les arguments optionnels utilisés par le script de la fermeture.


Les références faibles
Pour plus de détails sur le sujet vous pouvez consulter ce tutoriel : http://merlin.developpez.com/cours/dotnet/WeakReference/
Rapidement les références faibles vont permettre de faciliter le cycle de vie des objets inactifs(détruit) dans la hashtable $ClosureStore.

Le probléme qui se pose est le suivant :
Un même objet, notre fermeture, est référencé par un scriptbloc enrichi d'un membre synthétique de type NoteProperty ($Counter.Closure) et par le magasin $ClosureStore.
Tant qu'un objet est référencé par une variable il ne peut être supprimé par le garbage collector. Dans ce cas on parle de références fortes.
Code:

     #Création de l'objet Closure, combinant le scriptbloc reçu en paramétre et l'environnement d' exécution.   $private:closure =    New-Object 'PSObject' |     Add-Member 'Script' $script -MemberType 'NoteProperty' -PassThru |     Add-Member 'Environment' $environment -MemberType 'NoteProperty' -PassThru;     # Mémorisation dans le magasin dédié de la fermeture.      #Le GUID est la clé qui désigne la fermeture, passée en tant une référence faible (weak  reference)     $private:closureId = [Guid]::NewGuid();     $ClosureStore.Add($closureId$closure);    Add-Member -InputObject $invoker -Name 'Closure' -Value $closure -MemberType 'NoteProperty';


Avec ce code, modifié pour l'explication, si on supprime la fermeture :
Code:

  $Counter=$null


on ne supprime pas l'entrée correspondante dans la hashtable $ClosureStore. Et surtout il n'est pas possible de savoir si une référence est active ou non, c'est à dire si l'objet a été détruit.
De plus pour chaque suppression de fermeture il nous faudrait supprimer l'entrée correspondante dans le magasin. Tout en sachant qu'on ne connaît pas directement l'entrée associée dans le magasin.

Si tel était le cas le nombre d'entrée dans la hashtable $ClosureStore augmenterait sans cesse tout en contenant des informations périmées/inutilisées.

Avec l'usage de la référence faible, c'est à dire avec le code suivant,
Code:

   $ClosureStore.Add($closureId, [WeakReference]$closure);


notre objet fermeture peut être supprimé automatiquement car une fois la variable $Counter supprimée il ne reste plus, dans le magasin, aucune référence forte sur cet objet mais seulement une référence faible.
La référence faible permettra :
- au GC de libérer rééllement notre fermeture($Counter),
- et à notre fonction globale Closure de supprimer les entrées inactives (IsAlive=False) dans le magasin :
Code:

     #Supprime dans la hastable les éléments qui ont été collectés par le GC    ($ClosureStore.GetEnumerator() | ? { !$_.Value.IsAlive; }) | ? { $() -ne $_; } | % { $ClosureStore.Remove($_.Key); };


L'instruction [WeakReference]$closure encapsule l'objet $closure au sein d'une instance de la classe WeakReference.

Dit autrement dans le SDK :

Le mécanisme de garbage collection du Common Language Runtime récupère des objets inaccessibles de la mémoire. Un objet devient inaccessible si toutes les références directes et indirectes à cet objet ne sont plus valides, par exemple si ces références ont la valeur référence Null (Nothing en Visual Basic). Une référence à un objet accessible est appelée référence forte.

Une référence faible fait également référence à un objet accessible, appelé la cible. Un utilisateur crée une référence forte à la cible en assignant la valeur de la propriété Target à une variable. Toutefois, s'il n'y a pas de référence forte à la cible, celle-ci est susceptible d'être traitée par le garbage collection, même s'il existe une référence faible à l'objet.


Affichons quelques informations à propos de la gestion de la durée de vie des entrées de la table $ClosureStore.
Ouvrez une nouvelle session de PowerShell, exécutez le script Closure.ps1:
Code:

  . .\Closure.ps1


puis créeons ces deux fonctions :
Code:

  function NewCounter {     $i 0;     return closure {         $i++;         return $i;     }; } function NewDecorator {     param ([string]$decoration)     return closure {         param ([string]$text)         return $decoration $text $decoration;     }; }


Ensuite créons 2 fermetures et affichons le contenu du magasin :
Code:

  $counter NewCounter $sharpDecorator NewDecorator '#'; $ClosureStore.GetEnumerator() | ? { $_.Value.IsAlive; } #Name                              Value #----                              ----- #248b8272-76ce-4054-9051-d28...    System.WeakReference #89e00708-d51a-4416-abb1-d6e...    System.WeakReference $ClosureStore.GetEnumerator() | % {$_.value } #    IsAlive            TrackResurrection  Target #    -------            -----------------  ------ #     True              False              @{Script=$i++;... #     True              False              @{Script=param([string]$text) return...


Le champ IsAlive est un membre de la classe WeakReference et nous indique si la référence, c'est à dire une entrée de notre hashtable, est toujours accessible (actif).
Créons une troisiéme fermeture mais en utilisant une variable existante, ici $Counter:
Code:

  $counter NewCounter $ClosureStore.GetEnumerator() | % {$_.value } #   IsAlive            TrackResurrection  Target #   -------            -----------------  ------ #   True               False              @{Script=$i++;... #   True               False              @{Script=param([string]$text) return... #   True               False              @{Script=$i++;...


dans cette table nous avons 3 entrées pour 2 closures actives. Forçons la collecte du garbage collector (GC) :
Code:

  [GC]::Collect([GC]::MaxGeneration) $ClosureStore.GetEnumerator() | % {$_.value } #  IsAlive             TrackResurrection  Target #  -------             -----------------  ------ #  False               False #  True                False              @{Script=param([string]$text) return... #  True                False              @{Script=$i++;...                


La référence de la première fermeture a été supprimée automatiquement par le garbage collector. Mais notre référence faible existe encore dans le magasin.
C'est la méthode Closure qui se charge de supprimer les entrées inaccessibles présente dans la hastable car le garbage collector collecte automatiquement et à intervalle régulier les objets détruits :

Créeons une nouvelle fermeture dans une nouvelle variable :
Code:

  function InitArray {     $Instance$null;     return closure {         if ($Instance -eq $null)          { $Instance=@(1,2,3)}         return $Instance;     }; } $Init=InitArray$Array=&$Init"$array" #1 2 3 $array+=0$Array+=&$Init; [string]::Join(',',$array) #1,2,3,0,1,2,3 $ClosureStore.GetEnumerator() | % {$_.value } #   IsAlive          TrackResurrection  Target #   -------          -----------------  ------ #   True             False              @{Script=param([string]$text) return... #   True             False              @ {Script=if ($Instance -eq $null)... #   True             False              @{Script=$i++;...


On voit que nous avons désormais 3 entrées pour 3 closures actives et que l'entrée inaccessible, la référence faible, a été supprimée lors de l'appel suivant :
Code:

  $Init=InitArray# Appel en interne la fonction closure 



2)La fonction globale InvokeClosureScript
TODO
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 6/10/08 22:29 2)La fonction globale InvokeClosureScript

Cette fonction est relativement complexe à saisir car elle utilise pleinement le dynamisme de PowerShell.
Elle recoit en paramètre le GUID de la fermeture concernée et un tableau d'objets.
Le paramètre GUID permet de récupérer la fermeture dans le magasin ($ClosureStore).
Code:

   $private:closure $ClosureStore[$closureId].Target;


On récupére une référence forte sur la fermeture via le membre Target.
Ensuite on crée, selon le nombre d'éléments dans le tableau Args, un des codes suivants :
Code:

  param([ScriptBlock]$private:script, [Array]$private:Args_) .$script   #ou param([ScriptBlock]$private:script, [Array]$private:Args_) .$script $Args_[0] #ou encore param([ScriptBlock]$private:script, [Array]$private:Args_) .$script $Args_[0$Args_[n]


Ensuite, et comme dirait Christian C., c'est là que ça se corse !

Dans un premier temps on construit un scriptblock
Code:

  {   param([ScriptBlock]$private:script, [Array]$private:Args_)   .$script $Args_[0$Args_[n] }


puis viens ceci :
Code:

      $private:result =          &{               #Charge l'environnement             $Args[1].Environment | % { trap { continue; } $ExecutionContext.SessionState.PSVariable.Set($_); };              #Execute la fermeture             return .$Args[0$Args[1].Script $Args[2];          } $invoker $closure $Args_     return $result;    


Décortiquons l'affectation de la variable $Result tout en simplifiant le code, que vous pouvez exécuter directement dans la console:
Code:

      $result =          &{           Echo "$Args"           "`$invoker" "`$closure" "`$Args_"     &$result         


A première vue ce code semble assez complexe et une des questions qui vient à l'esprit est pourquoi procéder ainsi ?
La fermeture doit retrouver son "contexte d'exécution" c'est à dire les variables locales sur lesquelles elle intervient, i.e. son environnement lexical.
Code:

    $ExecutionContext.SessionState.PSVariable.Set($_)


Cette instruction "injecte" une variable dans le contexte d'exécution courant.

La présence du scriptblock $Result permet de créer une portée enfant autorisant leur restauration sans effet de bord sur la portée courante.
Reprenont,
Niveau 0 : La fonction InvokeClosureScript
Niveau 1 : La variable $Result contient le résultat de l'exécution d'un scriptblock avec 3 arguments.
Niveau 2 : Son code imbrique l'exécution d'un scriptblock, $Invoker, prenant en paramétre 2 arguments.
Niveau 3 : Et enfin le code de ce scriptblock imbriqué exécute à son tour le code du scriptbloc de la closure qui lui attend 0 ou n arguments.

Le second appel de scriptblock imbriqué, .$Args[0], simule les portées du code d'origine, par exemple :
Code:

  function NewCounter           result =  {                              &{     $i 0                        $ExecutionContext.SessionState.PSVariable.Set($i)     return closure {              return &{param([ScriptBlock]$private:script, [Array]$private:Args_)         $i++;                               .{$i++;return $i # pas de paramétre, $Args_ est inutilisé dans ce cas présent         return $i                                  };                                       }  }


Tout compte fait cette construction propage le scriptbloc de la closure et ses arguments jusqu'a la portée adéquate.
A noter une dernière remarque, sur ce code
Code:

   return .$Args[0$Args[1].Script $Args[2];


L'usage du dotsourcing sur un scriptbloc l'exécute dans la portée globale alors que l'usage du & l'exécuterais dans une portée locale.
C'est ce qui fait que le code de la closure modifie ces variables dans la portée adéquate. Une fois ceci expliqué cette portion de code est déjà plus facile à aborder, enfin j'espère.

Reste la sauvegarde des variables modifiées dans le scriptblock de la fermeture. Puisque l'on récupère, pour chaque variable injectée dans la portée parente, une référence forte sur nos variables mémorisées dans le magasin, leur modification se fait sur le même objet. Il n'est donc pas nécessaire de mettre à jour le magasin.

Par l'étude de ce script on peut voir que PowerShell offre des fonctionnalités étendues qui associées à une connaissance des principes de base de .NET. permet des traitements assez poussés, certes au détriment parfois de la relecture.
Bien évidemmment ce n'est pas tous les jours qu'on utilise ce genre de mécanismes, heureusement d'ailleurs, mais dans certains cas de savoir qu'ils existent peut être utile.

PowerShell c'est d'enfer mais attention au retour de flamme

Lien :
Portée dynamique : http://fr.wikipedia.org/wiki/Port%C3%A9e_(informatique)
PS est un langage à portée dynamique
Code:

  function get{echo $i} get #ras $i=10 get #10 function get2{$i=15;get} get2 #15 get #10 function get2{$i=15;get;&{$i=84;get}} get2 #15 #84



Fermetures lexicales en C : ftp://ftp.linux-kheops.com/pub/traduc.org/doc-vf/gazette-linux/html/2005/112/lg112-H.html
La syntaxe de PowerShell étant proche de celle du C, la connaissance de ce langage n'est pas nécessaire pour aborder la première partie de ce texte.

Définitions: fermeture (closure en anglais) et environnement lexical sous .NET : http://www.dotnetguru.org/articles/dossiers/anonymescsharp2/CS2_AnonymousMethod_FR.htm#_Toc83201319

PS
Amélioration possible :
Il reste un point à vérifier, ce code mémorise un nombre important de variables sans que l'on sache si cela est vraiment nécessaire.
Comme PowerShell est un langage à portée dynamique je ne sais pas si l'environnement lexical concerne les seules variables manipulées dans le code de la closure ou toutes les variables du contexte comme le fait le code d'origine.

Une solution possible serait d'étendre le principe des DSL comme ceci :
Code:

  function NewCounter {     Var i     $i 0;     return closure {         $i++;         return $i;     }; }


Var est une fonction globale utilisée pour mémoriser uniquement les variables locale de la fermeture.
Dans ce cas la gestion des variables dans le code de la fonction Closure ressemblerait à ceci :
Code:

   $private:environment $__ClosureVarStore|% {Get-Variable -name $_}


A suivre...
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Arnaud
Admin

Administrateur
Messages: 1317
graphgraph
Karma: 45  
Re:The powers hell ou la puissance de l'enfer - 6/10/08 22:58 Bonsoir Laurent,

Ce n'est pas du Japonais, mais c'est pas si loin...

Pourrais tu nous dire à quoi peuvent servir les closures ? J'ai regardé attentivement le lien Wikipedia que tu as donné mais je vois pas trop quelle est la finalité de la chose.

Merci

Arnaud
MVP PowerShell (depuis 2007)
Suivez moi sur Twitter !
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 7/10/08 11:16 Salut Arnaud
Arnaud écrit:

Pourrais tu nous dire à quoi peuvent servir les closures ?

Donne moi un peu de temps pour te répondre, je ne suis pas très à l'aise pour les expliquer simplement et rapidement.
Arnaud écrit:
Ce n'est pas du Japonais, mais c'est pas si loin...
Qu'est-ce qui te pose pb, autre que celui exposé ?
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 8/10/08 12:48 Arnaud écrit:
Pourrais tu nous dire à quoi peuvent servir les closures ?
Voilà ce que je peux en dire aujourd'hui
L'usage d'une closure est plus courant dans les langages orienté objet et est liée aux méthodes anonyme, sous PowerShell un scriptblock, on peut les voir comme "une manière de programmer".
Souvent leur interêt permet de ne pas créer de classse qui ne porterait qu'une seule fonction.
Bon on peut s'en passer et programmer de manière "classique".

Le premier interêt que j'ai trouvé à ce code était justement une autre manière de faire et elle présente de nombreux mécanisme de PS et .NET.
Pour un curieux comme moi, c'est du pain béni

L'interêt que je peux présentement y trouver est de paramètrer des traitements( function contenant la closure) puis de passer ce code en paramètre à une autre fonction.
Cela rejoint le principe des délégués (pointeur de fonction), la différence, très sommaire, est qu'on y mémorise les variables locales.
Ici on nomme cette technique le currying, désolé d'introduire ce terme pour expliquer le premier...
Encore une fois c'est un mécanisme plus orienté développeur qu'administrateur.

Reprenons l'exemple cité:
Code:

  function NewCounter {     $i 0     return closure {         $i++;         return $i     }; } $counter NewCounter $compteur=&$counter


On peut le transformer ainsi :
Code:

  function NewCounter2($Compteur) {   $Compteur++   return $Compteur++ }; $compteur=0 $compteur=NewCounter2 $Compteur $compteur


L'initialisation se fait à l'extérieur de la fonction et on doit lui passer en paramètre la variable $compteur qu'on souhaite incrémenter.

Reprenons le second exemple cité:
Code:

  function NewDecorator {     param ([string]$decoration)     return closure {         param ([string]$text)         return $decoration $text $decoration;     }; } $sharpDecorator NewDecorator '#'; $SlashDecorator NewDecorator '//';


On peut réécrire cette fonction auquel on passe le texte à commenter et la chaîne de commentaire :
Code:

  function Commenter1($Texte,$decoration) {     foreach($Ligne in $Texte)     {$decoration $Ligne $decoration }    } $A=dir|%{$_.name} Commenter1 $A "#" Commenter1 $A "//"


Ici comme il n'y a qu'un paramètre cela ne change pas grand chose mais dans le cas où le traitement en nécessite plusieurs cela peut faciliter l'écriture.

La closure permet aussi de spécialiser le traitement, ici commenter des lignes de PS ou du C#, on a donc un décorateur pour chaque langage.
Dans le cas où j'ai besoin d'un décorateur pour le C, j'ai besoin de 2 paramètres :
Code:

  function Commenter2($Texte,[string]$Begin, [string]$End) {     foreach($Ligne in $Texte)     {$Begin $text $End} } Commenter2 $A "/*" "*/"


PS ne propose pas la surchage de méthode/fonction, i.e. un même nom de fonction mais avec un nombre de paramètre différent. Mais on peut le simuler.
Donc dans ce cas si je souhaite utiliser le même code (commenter1) ce n'est pas possible, pourtant commenter2 à le même objectif mais elle ne le réalise pas tout à fait de la même manière.

On peut réécrire cette fonction auquel on passe la closure et son paramètre :
Code:

  function Commenter($Texte,$Sb) {     foreach($Ligne in $Texte)     {&$Sb $Ligne  }    }


Avec les closures je crée un nouveau décorateur et c'est la closure qui mémorise les paramètres du décorateur, de plus la closure recoit tjr un seul paramètre, le texte sur lequel on effectue le traitement.
Code:

  function CDecorator {     param ([string]$Begin, [string]$End)     return closure {         param ([string]$text)         return $Begin $text $End;     }; } $CDecorator=CDecorator "/*" "*/"


Ainsi je peux appeler les 3 décorateurs sur le tableaux $A sans me préoccuper du nombre de paramètres des décorateurs et ma fonction commenter ne nécessite pas de maintenance, c'est à dire de modifications :
Code:

  Commenter $A $sharpDecorator Commenter $A $SlashDecorator Commenter $A $CDecorator


Donc sans closure le nombre de paramètre doit être propagé, on pourrait les propager dans un seul paramètre de type tableau, voir créer un objet synthétique;
Avec une closure le nombre de paramètre est réduit, on ne les propage pas car ils sont mémorisés automatiquement,et le code n'est ni à modifier ni à adapter.
Reste à savoir si mes explications sur le sujet sont suffisante, je ne le pense pas.

Tout compte fait l'usage de délégué est plus approprié
http://blogs.msdn.com/powershell/archive/2006/07/25/678259.aspx
Code:

  [string[]]$T=[System.IO.FileMode]|gm -static -membertype property|% {$_.Name} $Deleg=get-delegate "System.Predicate``1[System.String]" {write-host "Test"} #[Array]::Find($T, $Deleg) Invoke-GenericMethod ([System.Array]) Find String ($T,$Deleg)


Malheureusement ce code ne fonctionne pas, le script Invoke-GenericMethod ne semble pas prendre en compte tous les cas de figure.

Ou encore avec un script bloc sans closure :
Code:

  $sbsharpDecorator={ param ([string]$Text)    return "#{0}#" -F $Text; } $sbSlashDecorator={ param ([string]$Text)    return "//{0}//" -F $Text; } $sbCDecorator={ param ([string]$Text)    return "/*{0}*/" -F $Text; } $A=dir|%{$_.name} Commenter $A $sbsharpDecorator Commenter $A $sbSlashDecorator Commenter $A $sbCDecorator


A noter une duplication de code dans ce cas.
Et effectivement à voir cette approche des closures, de prime abord on pourrait se dire pourquoi faire simple quand on peut faire compliqué
[edit]
J'ai modifié la syntaxe d'appel du délégué qui était érroné mais cela ne régle pas le pb...

Message édité par: Laurent Dardenne, à: 8/10/08 15:48
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 9/10/08 11:19 Un autre oubli, le lien sur le script Invoke-GenericMethod
http://www.leeholmes.com/blog/InvokingGenericMethodsOnNonGenericClassesInPowerShell.aspx
Je pense que le problème vient de la création du délégué. Ce code fonctionne avec des arguments de type différent.
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 13/10/08 12:24 Suite et fin.

Concernant l'usage d'un mot clé pour mémoriser uniquement les variables déclarées dans le code englobant notre closure, j'avais envisagé d'utiliser une fonction nommée Var.
Cette approche fonctionne, bien que nécessitant un code conséquent pour l'analyse des paramètres pour prendre en charge les constructions suivante qui doivent respecter la syntaxe de PowerShell :
Code:

  function Test {    Var instance instance3    Var 1 2 3;      #4 paramètres dont un tableau    var 1 2 3 4,5,;     #5 paramètres dont 2 tableaux    var 1 2 3 (4,5,6a,b    var 1 2 3 @(4,5,6a,b    #etc


Un autre aspect à prendre en compte concerne la définition d'une syntaxe de déclaration, la déclaration du type et l'assignation par défaut :
Code:

      #A la Pascal-Delphi   var 1:[string]    #Ou à la C#   var [System.String1    var [System.Truc+NestedTypeJ= (new-object System.Truc+NestedType parametre_1)    var  [String1=$null   var  [stringx={ &Scriptbloc ...}      var  [string1=( Get-variable $x)  


Donc les DSL c'est facile et surtout ça peut rapporter gros en développement

L'ébauche de cette fonction Var mémorise toutes les variables déclarées par les appels successif de la pseudo-clause Var.
Outre le problème d'analyse des paramètres il reste celui de la récupération de cette liste de variables dans le code de la fonction Closure.
Pour ce faire on déclare une variable dans la portée parente de notre fonction Var
Code:

   if (!(test-path Variable:__ClosureVarStore))   {  #Création de la variable __ClosureVarStore     Set-Variable '__ClosureVarStore' $Variables -Scope 1    }  else    {  #La variable __ClosureVarStore est déjà déclarée."      #On récupère la variable de la portée parente      #Et on évite les duplications de nom de variables     $__ClosureVarStore+=$Variables|? {$__ClosureVarStore -notcontains $_}     Set-Variable '__ClosureVarStore' $__ClosureVarStore -Scope 1   }   


Ainsi tout code utilisant la pseudo-clause Var n'a pas besoin de se préocupper de la gestion de cette liste.
On récupére tout naturellement la variable automatique '__ClosureVarStore' dans le code de la closure.
Cette mécanique fonctionne pour des déclarations simples.

Mais si les variables peuvent être ainsi gérées il reste la clause param. Bon ici ça complique, mieux vaut abandonner cette piste .

Donc comment faire pour récupérer les variables déclarées dans le code et dans la clause param ?
La solution retenue se base sur la manipulation de la portée des variables.
Les cmdlets Get-Variable et Set-Variable proposent le paramètre Scope qui défini la portée dans laquelle on récupère ou déclare une variable. Il s'agit en quelque sorte d'un mécanisme de création de variable "automatique".

Ainsi dans la fonction Closure on récupère les variables du code englobant :
Code:

      $private:ParentPrivateVariables=Get-Variable -scope 1|? {$_.options -notmatch "AllScope"}


Tout en filtrant celles qui sont globales
Code:

 ($MaVariable.Options="AllScope")



On modifie donc le code de la fonction Closure comme ceci :
Code:

     #Recherche des variables de la portée parente      #Au sein du code parent le contenu des portées "local" et "private" sont identiques en PS  v1.0, sauf erreur de ma part.      #On ne récupére que les variables, automatiques ou non, dont la portée est "none"     $private:ParentPrivateVariables=Get-Variable -scope 1|? {$_.options -notmatch "AllScope"}           #Mémorisation de l'environnement de la portée enfant (toutes les variables sauf les variables  automatiques)     $autoVariableNames =          @(             '$''?''^''_''args''ConfirmPreference''ConsoleFileName''DebugPreference''Error''ErrorActionPreference',              'ErrorView''ExecutionContext''false''FormatEnumerationLimit''foreach''HOME''Host''input''LASTEXITCODE',              'lastWord''line''Matches''MaximumAliasCount''MaximumDriveCount''MaximumErrorCount''MaximumFunctionCount',              'MaximumHistoryCount''MaximumVariableCount''MyInvocation''NestedPromptLevel','null''OutputEncoding''PID',              'PROFILE''ProgressPreference''PSHOME''PWD''ReportErrorShowExceptionClass''ReportErrorShowInnerException',              'ReportErrorShowSource','ReportErrorShowStackTrace''ShellId''StackTrace''switch''true''VerbosePreference',              'WarningPreference''WhatIfPreference',             #PS v2 ctp2             '$CommandLineParameters','$PSVersionTable','$PSCulture','$PSUICulture'         );               #On ne garde que les variables 'non-automatiques', celles déclarées dans le corps de la méthode ou  celles de la clause param     $private:environment $private:ParentPrivateVariables| ? { $autoVariableNames -notcontains $_.Name }


De cette manière on ne mémorise que le strict nécessaire, c'est à dire les variables du code englobant.
D'où un gain conséquent en occupation mémoire.

Tout comptes fait cette étude s'est révélée très instructive
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 13/10/08 12:29 Le code modifié et commenté :
Code:

  function global:closure #Crée un objet Closure         param ([ScriptBlock]$private:script)     trap { break; }      #Vérification de l'argument          if ($null -eq $script)         { throw 'The script is null.' }      Write-debug ("Script à exécuter : {0}" -F $private:script)                  #La variable ClosureStore, de type hash table, maintient la liste de toutes les closures      if ($null -eq $global:ClosureStore)     {        Set-Variable 'ClosureStore' @{} -Scope 'global' -Option 'Constant, AllScope';     }     #Supprime dans la hastable les éléments qui ont été collectés par le GC     ($ClosureStore.GetEnumerator() | ? { !$_.Value.IsAlive; }) | ? { $() -ne $_; } | % { $ClosureStore.Remove($_.Key); };                #Recherche des variables de la portée parente      #Au sein du code parent le contenu des portées "local" et "private" sont identiques en PS  v1.0, sauf erreur de ma part.      #On ne récupére que les variables, automatiques ou non, dont la portée est "none"     $private:ParentPrivateVariables=Get-Variable -scope 1|? {$_.options -notmatch "AllScope"}           #Mémorisation de l'environnement de la portée enfant (toutes les variables sauf les variables  automatiques)     $autoVariableNames =          @(             '$''?''^''_''args''ConfirmPreference''ConsoleFileName''DebugPreference''Error''ErrorActionPreference',              'ErrorView''ExecutionContext''false''FormatEnumerationLimit''foreach''HOME''Host''input''LASTEXITCODE',              'lastWord''line''Matches''MaximumAliasCount''MaximumDriveCount''MaximumErrorCount''MaximumFunctionCount',              'MaximumHistoryCount''MaximumVariableCount''MyInvocation''NestedPromptLevel','null''OutputEncoding''PID',              'PROFILE''ProgressPreference''PSHOME''PWD''ReportErrorShowExceptionClass''ReportErrorShowInnerException',              'ReportErrorShowSource','ReportErrorShowStackTrace''ShellId''StackTrace''switch''true''VerbosePreference',              'WarningPreference''WhatIfPreference',             #PS v2 ctp2             '$CommandLineParameters','$PSVersionTable','$PSCulture','$PSUICulture'         );               #On ne garde que les variables 'non-automatiques', celles déclarées dans le corps de la méthode ou  celle de la clause param     $private:environment $private:ParentPrivateVariables| ? { $autoVariableNames -notcontains $_.Name }    #need Dbgview.exe     [int$private:level=1;[string$category="PS $($MyInvocation.InvocationName)"     [string$private:Message"Environnement parent`r`n{0}" -$("-" 40)     [System.Diagnostics.Debugger]::Log($level$category$message)          $private:Tab=Get-Variable -scope 1|? {$_.options -notmatch "AllScope"}|% {$_.name" : " $_.Options}      ForEach( $Line in $private:Tab)       {[System.Diagnostics.Debugger]::Log($private:level$private:category$Line)}     [System.Diagnostics.Debugger]::Log($private:level$private:category, $("{0}" -$("-" 40)) )               #Création de l'objet Closure, combinant le scriptbloc reçu en paramètre et l'environnement d' exécution.     $private:closure =          New-Object 'PSObject' |              Add-Member 'Script' $script -MemberType 'NoteProperty' -PassThru |              Add-Member 'Environment' $environment -MemberType 'NoteProperty' -PassThru;      Write-debug ("Closure: {0}" -F $private:closure)          # Mémorisation dans le magasin dédié de la fermeture.      #Le GUID est la clé qui désigne la fermeture, passée en tant une référence faible (weak  reference)     $private:closureId = [Guid]::NewGuid();     $ClosureStore.Add($closureId, [WeakReference]$closure);          #Construit l'appel de la fermeture, on y insére le GUID dynamiquement     #Ex: InvokeClosureScript "903eafc7-10c4-4541-98d0-ff90f68071de" $Args;     $private:invokerText =  "InvokeClosureScript `"$closureId``$Args;";      Write-debug ("invokerText : {0}" -F $private:invokerText)          #On construit un scriptblock, tout en castant le résultat dans le type PSObject     #Ce scriptblock exécutera le traitement réel       #$private:invoker = [PSObject](Invoke-Expression "$invokerText }");     $private:invoker=[PSObject]$ExecutionContext.InvokeCommand.NewScriptBlock($invokerText)          #On ajoute à l'objet invoker un membre nommé Closure qui contient l'objet personnalisé $Closure        #La durée de vie du script et de l'environnement est dépendant du fait que l'environnement est  attaché au script     Add-Member -InputObject $invoker -Name 'Closure' -Value $closure -MemberType 'NoteProperty';      Write-debug ("invoker : {0}" -F $private:invoker)     return $invoker; } function global:InvokeClosureScript { #Exécute la varaible closure     param ([Guid]$private:closureId, [Array]$private:Args_)     #On récupére une référence faible sur la fermeture hébergée dans le magasin ($ClosureStore)      $private:closure = $ClosureStore[$closureId].Target;      Write-debug ("Closure : {0}" -F $private:closure)     if ( $DebugPreference -ne "SilentlyContinue")       { "Variable de contexte avant exécution de la closure"        $private:closure.Environment      }               if ($null -eq $closure) { throw 'The closure which in ID which it appoints relation is attached  does not exist.'; }          #L'appel de fonction est construit dynamiquement.     #On déclare la clause param avec 2 arguments :     # -le script à exécuter, la closure,      # -et un tableau d'arguments (de la closure).         $private:invokerText = 'param ([ScriptBlock]$private:script, [Array]$private:Args_) .$script';       Write-debug ("invokerText : {0}" -F $private:invokerText)                   #On ajoute les possibles paramètres, récupérés dans le tableau Args_ de la clause Param  précédente      for ($private:i = 0; $i -lt $Args_.Length; $i++) { $invokerText += " `$Args_[$i]"; }      Write-debug ("invokerText : {0}" -F $private:invokerText)          #On convertit le texte en un script     # exemple : param([ScriptBlock]$private:script, [Array]$private:Args_) .$script       #           param([ScriptBlock]$private:script, [Array]$private:Args_) .$script $Args_[0]     #           param([ScriptBlock]$private:script, [Array]$private:Args_) .$script $Args_[0] $Args_[n]     #$private:invoker = Invoke-Expression "{ $invokerText }";     #On construit un scriptblock avec le texte contenant du code     $private:invoker=$ExecutionContext.InvokeCommand.NewScriptBlock($invokerText)      Write-debug ("invoker : {0}" -F $private:invoker)     #Invoker contient quelque chose comme ceci, les lignes sont récréées :     # {     #    param([ScriptBlock]$private:script, [Array]$private:Args_)     #    .$script $Args_[0] $Args_[n]     # }          #On crée un scriptblock afin de créer une portée enfant.     #On charge l'environnement et on exécute la fermeture dans la portée enfant du ScriptBlock     #Il retourne le résulat de l'exécution de la commande passé dans le paramètre 1 ($Arg[0] dans le code)     #cette commande reçoit les paramètres      $private:result =          &{               #$Args[0] référence le contenu de la variable invoker (scriptblock)              Write-debug ("`$Args[0]={0" -F $Args[0])              #$Args[1] référence la fermeture               Write-debug ("`$Args[1].script={0}" -F $($Args[1].Script))              #$Args[1] référence les paramètres, optionnels, du code de la fermeture               Write-debug ("`$Args[2]={0}" -F $Args[2])                           #Charge l'environnement dans la portée courante, c'est à dire ce scriptblock où on  exécute la closure             #On recrée les objet variables qui ont été précédement sauvegardées             $Args[1].Environment | % { trap { continue; }  $ExecutionContext.SessionState.PSVariable.Set($_); };             #Execute la fermeture avec le bon environnement(variables)             # et renvoi le résultat du traitement effectué par la closure                          return .$Args[0] $Args[1].Script $Args[2];         } $invoker $closure $Args_;     #Résumée des portées :     #      # niveau 0 la function InvokeClosureScript     #        niveau 1 le scriptblock Result : Restauration des variables     #               niveau 2 le scriptblock de l'invoker : simule la portée parente de la closure     #                      niveau 3 le scriptblock de la closure : exécute le code de la closure      Write-debug ("Result= {0}" -F $result)     if ( $DebugPreference -ne "SilentlyContinue")       { "Variable de contexte après exécution de la closure"        $private:ColorTmp=$Host.Ui.RawUi.ForeGroundColor        $Host.Ui.RawUi.ForeGroundColor ="green"        $private:closure.Environment        $Host.Ui.RawUi.ForeGroundColor =$private:ColorTmp      }     return $result; }

File Attachment:
File name: Closure3.ps1
File size:10922 bytes
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Robin
Admin

Administrateur
Messages: 262
graphgraph
Karma: 8  
Re:The powers hell ou la puissance de l'enfer - 16/10/08 12:14 Aïe, claquage de cerveau ! Robin MVP PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 16/10/08 12:31 Robin écrit:
Aïe, claquage de cerveau !
Mdr

Vous savez avec la V2 faut vous attendre à trouver sur sur le net des trucs carabinés.
Plus il y a de mou plus on tire sur la corde

A ne pas confondre avec plus du pédale moins fort moins d'avance plus vite
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 3/11/08 17:55 Pour info voici le même principe de la function decorator codé en Delphi 2009 :

program Anonyme1;

{$APPTYPE CONSOLE}

uses
SysUtils;
type
FDecorateur = reference to function (const Value: String) : String;

function Decorateur(Decoration : String):FDecorateur;Overload;
begin
result:= function(const Texte : String) : String
begin
result:=Decoration + Texte + Decoration;
end;
end;

function Decorateur(Debut : String; Fin : String):FDecorateur;Overload;
begin
result:= function(const Texte : String) : String
begin
result:=Debut + Texte + Fin ;
end;
end;


var CSharpCommente,
DelphiCommente,
CCommente : FDecorateur;

Text : String;
begin
try
DelphiCommente:= Decorateur('//');
CSharpCommente:= Decorateur('#');
CCommente:= Decorateur('/*', '*/');

Text:='Texte à commenter';
Writeln(DelphiCommente(Text));
Writeln(CSharpCommente(Text));
Writeln(CCommente(Text));

readln;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.

Cela fait la même chose mais ici pas besoin de coder le mécanisme, il est natif via le mot clé reference qui indique au compilateur une fonction anonyme.

Message édité par: Laurent Dardenne, à: 5/11/08 16:32
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 5/11/08 15:36 Allez, un dernier pour la route

A propos des fonctions locales
Il reste d’autres aspects à aborder sur les closures, notamment l’usage de fonction locale.
Le script présenté ici ne capture pas les fonctions locales d’une fonction utilisant une closure, c’est à dire :
Code:

  function M{   [int$a=0   [int$x=0     function local:F  {write-host "Dans F"       return $a     }   write-host "Dans M"      return closure {      write-host "Closure"      $a=1      $x=F   $x   } } $ClosureM=M &$ClosureM


Le terme « F » n'est pas reconnu en tant qu'applet de commande, fonction, programme exécutable ou fichier de script.
Pour cette écriture la fonction F n’est plus accessible lors de l’exécution de la closure.
Comme expliqué sur cette page: http://www.liafa.jussieu.fr/~hf/verif/ens/cours/C++/node17.html
Bien que la fonction M.F soit locale il n’est pas possible de le savoir car un objet fonction ne renseigne pas sa propriété Options (System.Management.Automation.ScopedItemOptions) :
Code:

    write-host "Dans M" @(dir function:M;dir function:F)|% {$_.name" : " $_.Options}


On peut déclarer la fonction F en globale :
Code:

  $A=dir|%{$_.name} function {write-host "Dans F"    return $a } function M{   [int$a=5   [int$x=0   write-host "Dans M"   Write-host (F)   return closure {      write-host "Closure"      $a=1      $x=F      $x   } } $ClosureM=M &$ClosureM F


Ce n’est pas forcément souhaitable, reste à la solution de l’inclure dans la closure :
Code:

  Remove-item Function:f function M {   [int$a=5   [int$x=0      write-host "Dans M"   return closure {    function #Déclarée Implicitement en locale    {      $y=$x      write-host "Dans F a =$a x =$x y =$y"      return $a*2    }         write-host "Closure"      $a=10;      $x=F;      F|out-null   } } $ClosureM=M &$ClosureM F


Malheureusement avec cette approche la fonction M n’a plus accès à la fonction F. Quelques fois sous PowerShell la résolution de problèmes en crée de nouveaux.

Une solution moyennement satisfaisante serait de prendre en charge la redéclaration de la fonction F dans le code de la closure :
Code:

  function Set-Function ( [String$FunctionName$CodeClosure){    #concaténe le code d'une fonction avec le code d'une closure.    # On renvoi un scriptbloc.    # On utilise une here-string pour l’expansion des     # différentes définitions.   return $ExecutionContext.InvokeCommand.NewScriptBlock( @"   function $FunctionName {      $((dir function:$FunctionName).Definition)   }  $CodeClosure "@  #here-string   )#NewScriptBlock }#Set-Function


Ensuite la déclaration de la closure s’en trouve modifié :
Code:

  function M{   [int$a=0   [int$x=0     function F  {write-host "Dans F"       return $a     }   write-host "Dans M"   return closure (     #Création de la function F dans le code de la closure     Set-Function "F" $('        #Définition du code de la closure       write-host "Closure"       $a=1       $x=F       $x       ')     ) }


Bon je m’arrête ici car PowerShell c’est un peu comme avec les cacahuètes, on sait où ça commence jamais où ça s’arrête
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 28/03/09 14:22 Pour info, les closures sont implémentées dans le V2.
C'est déjà plus "simple"
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
Laurent Dardenne
Utilisateur

PowerShelleur Platinum
Messages: 5658
graph
Karma: 206  
Re:The powers hell ou la puissance de l'enfer - 1/04/09 13:12 Quelques remarques sur la création de closure à l'aide de la méthode GetNewClosure.

Closure sur une fonction déclarant un paramètre :
Code:

    # le précédent code :   # function NewDecorator([string]$decoration) {   #   return closure {   #         param ([string]$text)   #         return $decoration + $text + $decoration;   #     };   # }   #Devient    # function NewDecorator([string]$decoration) {   return {            param ([string]$text)            return $decoration $text          }.GetNewClosure(); } $sharpDecorator NewDecorator '//' $sharpDecorator &$sharpDecorator "Ligne" #émet //Ligne


Closure sur une fonction déclarant deux paramètres :
Code:

  function CDecorator([string]$Begin, [string]$End) {   return {           param ([string]$text)           return $Begin $text $End;          }.GetNewClosure(); } $CDecorator=CDecorator "/*" "*/" $CDecorator &$CDecorator "Commentaire"#émet /*Commentaire*/


On obtient donc pour ce type de fonction le même comportement que précédement (usage de la fonction Closure).
Voyons ce qu'il en est avec la fonction compteur.


Code:

   #Out-string converti un objet en un tableau de chaîne,   # Write-host affiche l'objet sans conversion. function NewCounter {     $i 255#Variable(s) locale(s) pouvant être capturée(s) par un appel à GetNewClosure()      #On renvoi un scriptblock     return {        #Ce scriptblock restitue, lors de son exécution, la valeur précédement capturée de $i       gv i|Out-String|Write-Host #Affiche i=255         $i++        gv i|Out-String|Write-Host #Affiche i=256       return $i     }.GetNewClosure()       #GetNewClosure() capture le ou les noms des variables et leur contenu à un instant t ($i = 255).       #GetNewClosure() renvoi un nouveau scriptbloc PLUS les variables mémorisées déclarées dans le  méthode NewCounter.      #D'après MSDN : "Any local variables that are in the context of the caller will be copied into  the module." } $counter NewCounter#Emet dans le pipeline un unique objet de type scriptblock &$counter# $i= 255, renvoi 256 &$counter# $i= 255, renvoi 256  #$i n'est pas déclaré dans la portée courante $i 


Dans ce cas l'incrémentation de $i n'est pas prise en compte en dehors de la closure, c'est à dire dans le bloc de code suivant :
Code:

   #closure mémorisant le nom et la valeur de $i {      #Ce scriptblock restitue, lors de son exécution, la valeur précédement capturée de $i     gv i|Out-String|Write-Host       $i++      gv i|Out-String|Write-Host     return $i  


Dans la closure la valeur initial de $i persiste d'appel en appel, sa modification à l'intérieur du bloc de code constituant la closure n'est pas sauvegardée.
Son nom et son contenu sont mémorisés au moment de la construction de la closure et seulement à ce moment là. On prend donc une photo de cette variable.

Ajoutons une variable $j externe à notre closure:
Code:

  $j=12 #on ne capture pas cette variable function NewCounter {     $i 255;      return {         gv i,j|Out-String|Write-Host          $i++          $j++          gv i,j|Out-String|Write-Host          return $i     }.GetNewClosure() } $counter NewCounter; $j #Affiche le contenu de $j, c'est à dire 12 &$counter#Appel la closure qui affiche 255,12 pour $i et $j.Puis 256,13 pour $i et $j            #Et renvoie 256 $j #Affiche le contenu de $j, c'est à dire 12


Modifions $j à l'extérieur de la closure :
Code:

  $j=789 &$counter#Appel la closure qui affiche 255,789 pour $i et $j.Puis 256,790 pour $i et $j $j # $j=789


L'incrémentation de la variable $j est locale à la closure.
Sa suppression dans la portée courante impacte la closure.
Code:

  remove-variable j &$counter



Allons un peu plus loin. La variable $j est toujours externe à notre closure modifions la désormais à l'intérieure de la closure :
Code:

  $j=12 #on ne capture pas cette variable function NewCounter {     $i 255#Variable(s) locale(s) pouvant être capturée(s) par un appel à GetNewClosure()     $j=999      #On renvoi un scriptblock     return {       #Ce scriptblock restitue, lors de son exécution, la valeur précédement capturée de $i         gv i,j|Out-String|Write-Host          $i++          $j++ # variable de la portée courante. On capture cette variable         #$j         gv i,j|Out-String|Write-Host          return $i     }.GetNewClosure() } $counter NewCounter; $j #est égale à 12 &$counter;  #Appel la closure qui renvoie 255,999 pour $i et $j.Puis 256,1000 pour $i et $j             #Renvoie 256 $j #est égale à 12 $j=200 &$counter;  #Appel la closure qui affiche 255,999 pour $i et $j.Puis 256,1000 pour $i et $j             #Renvoie 256 $j #Affiche le contenu de $j, c'est à dire 200


Bien qu'elle soit déclarée à l'extérieure de la fonction (NewCounter) retournant notre closure,
Ici la variable $j est également mémorisée car on crée une nouvelle variable locale dans la fonction NewCounter.
Sa suppression dans la portée courante n'impacte pas la closure.
Code:

  remove-variable j &$counter




Reprenons le code précédent en y ajoutant l'émission de $J dans le pipeline :
Code:

  $j=12 #on ne capture pas cette variable function NewCounter {     $i 255#Variable(s) locale(s) pouvant être capturée(s) par un appel à GetNewClosure()     $j=999     $j      #On renvoi un scriptblock     return {       #Ce scriptblock restitue, lors de son exécution, la valeur précédement capturée de $i         gv i,j|Out-String|Write-Host          $i++          $j++ # variable de la portée courante         gv i,j|Out-String|Write-Host          return $i     }.GetNewClosure() } $counter NewCounter; $j #est égale à 12 &$counter;  #Appel la closure qui renvoie 255,999 pour $i et $j.Puis 256,1000 pour $i et $j $j #est égale à 12


On voit que l'appel à counter génére une erreur, bien que sa lecture dans la pipeline n'en génére pas:
Code:

  $Counter|%{"item = $_"}


Ceci est normal puisque dans ce cas on émet dans le pipeline un tableau d'objet(object0:999 ; objet1criptblock) et non plus un unique objet de type scriptblock :
Code:

  $Counter.GetType()


Déplaçons l'emission de $J dans la closure:
Code:

  $j=12 #on ne capture pas cette variable function NewCounter {     $i 255#Variable(s) locale(s) pouvant être capturée(s) par un appel à GetNewClosure()     $j=999      #On renvoi un scriptblock     return {       #Ce scriptblock restitue, lors de son exécution, la valeur précédement capturée de $i         gv i,j|Out-String|Write-Host          $i++          $j++ # variable de la portée courante         $j         gv i,j|Out-String|Write-Host          return $i     }.GetNewClosure() } $counter NewCounter; $counter $j #est égale à 12 &$counter;  #Appel la closure qui renvoie 255,999 pour $i et $j.Puis 256,1000 pour $i et $j $j #est égale à 12


Cela ne génére plus d'erreur puisque désormais c'est la closure qui émet 2 objets.
Notre fonction doit donc renvoyer une seule valeur, à savoir une fermeture par l'appel à la méthode GetNewClosure.

Récapitulons

Première approche
Code:

   #On fixe la valeur de la décoration via un paramètre #La variable $décoration, externe à la fonction, N'A PAS D'IMPACT lors de la création d'un  décorateur. function NewDecorator([string]$decoration) {   return {            param ([string]$text)            return $decoration $text          }.GetNewClosure(); } $decoration='#'  #On crée un décorateur avec le contenu '//' indépendamment du contenu de la variable $decoration.  $sharpDecorator NewDecorator '//' &$sharpDecorator "premier"  $decoration='?' &$sharpDecorator "Second"


Seconde approche
Code:

  #On fixe la valeur de la décoration selon le contenu de la variable $decoration. #La variable $décoration, externe à la fonction, A UN IMPACT LORS de la création d'un décorateur. function NewDecorator{   [string$decoration=$decoration   return {            param ([string]$text)            return $decoration $text          }.GetNewClosure(); } [string$decoration='//'  #On crée un décorateur avec '//', ce que contient la variable $decoration   $Decorateur1 NewDecorator &$Decorateur1 "Premier //" #Affiche //Premier // $decoration='-'  #On crée un décorateur avec '#', ce que contient la variable $decoration $Decorateur2 NewDecorator &$Decorateur1 "Second-1 #"  #Affiche  //Second-1 # &$Decorateur2 "Second-2 #"  #Affiche  -Second-2 #  #La modification du contenu de la variable $décoration n'a pas d'impact sur les décorateurs créés  précédemment $decoration='?' &$Decorateur1 "Second-1 ?"  #Affiche  //Second-1 ?  &$Decorateur2 "Second-2 ?"  #Affiche  -Second-2 ?


Troisième approche
Code:

  #On fixe la valeur de la décoration selon le contenu de la variable $decoration. #La variable $décoration, externe à la fonction, a un impact A CHAQUE APPEL du décorateur. #Dans ce cas la construction d'une closure n'est pas nécessaire, un simple scriptblock suffit. function NewDecorator{   return {            param ([string]$text)            return $decoration $text          }.GetNewClosure(); #Appel inutile dans ce cas } [string$decoration='//'  #On crée un décorateur avec '//', ce que contient la variable $decoration   $Decorateur1 NewDecorator &$Decorateur1 "Premier //" #Affiche //Premier // $decoration='-'  #On crée un décorateur avec '#', ce que contient la variable $decoration $Decorateur2 NewDecorator &$Decorateur1 "Second-1 #" #Affiche  -Second-1 # &$Decorateur2 "Second-2 #" #Affiche  -Second-2 #  #La modification du contenu de la variable $décoration n'a pas d'impact sur les décorateur crée  précédemment $decoration='?' &$Decorateur1 "Second-1 ?"  #Affiche  ?Second-1 ?  &$Decorateur2 "Second-2 ?"  #Affiche  ?Second-2 ?


La dernière approche démontre que tous les décorateurs suivent la valeur courante de la variable $Decoration, elle
n'est donc pas d'un grand intérêt.
La seconde utilisant un effet de bord est à mon avis potentiellement dangereuse.
Donc autant privilégier la première approche statique qui précise l'intention et facilite la maintenance.

[edit]
Voir aussi ces posts :
http://blogs.technet.com/b/heyscriptingguy/archive/2013/04/05/closures-in-powershell.aspx? utm_source=twitterfeed&utm_medium=twitter

http://www.nivot.org/2010/03/11/PowerShell20PartialApplicationOfFunctionsAndCmdlets.aspx

http://efreedom.com/Question/1-1827561/PowerShell-Passing-Blocks-Parameters-Functions

http://www.dougfinke.com/blog/index.php/2010/12/11/does-powershell-support-high-order-functions/

C# :
http://mikehadlow.blogspot.com/2011/07/what-is-closure.html

PowerShell Closure scenarios (class)

Message édité par: Laurent Dardenne, à: 5/04/14 10:30

Message édité par: Laurent Dardenne, à: 7/05/19 19:34
Tutoriels PowerShell
  | | L'administrateur a désactivé l'accés public en écriture.
© 2019 PowerShell-Scripting.com