2014年1月4日土曜日

続・PowerShell で DSL - The Sequel: Create the DSL for the template engine that generates automatically the C++ library code that hooks the JIT of a program written by C# -

"そうですな。休みを・・・・・・暇を頂きましょう"


この記事は、前回の第 1 回 PowerShell 勉強会のフォローアップ記事の続きとなります。

本来であれば 1 つの記事にできると良かったのですが、解説向けのコンパクトなサンプルを切り出すのが結構難しく、ちょっと時間が空いてしまいました。また、結構なボリュームになりそうだったため、結局、全 3 部作になる勢いです ((((;゚Д゚))))

今回の記事では、解説しきれなかった残りの構文とともに、ハイブリッドな構文の実現方法や、モジュール性確保の時の注意点などを情報共有できればと思います。パフォーマンス向上の工夫や、テンプレートエンジンの解説は次回ですかね。なお、プレゼン資料の中で紹介させていただいたテンプレートエンジン、Swathe.Automation の公開がまだ先になりそうということで、規模は小さいのですが、同じ考え方でテンプレートエンジンとその設定 DSL を改めて実装し、それを解説しようかと考えています。

それでは、最後までどうぞお付き合いいただければと思います!

※今回の記事で扱うのも、PowerShell v2 の情報となります。PowerShell v3/v4 での情報は、いつか必ず・・・。


こちらの情報も参考に。改めて見直すと、便利なものを見落としていたりするものです・・・...( = =)
PowerShell Advent Calendar 2013 ATND
第1回 PowerShell勉強会 ATND
第1回 PowerShell勉強会 #jpposh ツイートまとめ - Togetterまとめ
Dean Edwards: A Base Class for JavaScript Inheritance
Dean Edwards: Prototype and Base
抽象(Abstract)クラスを作成 - JavaScriptist
Prototypal Inheritance Using PowerShell - Code Pyre
Prototypal Inheritance Using PowerShell (Part 2) ScriptProperties - Code Pyre
Prototypal Inheritance Using PowerShell (Part 3) Mixins - Code Pyre





目次

残りの構文(Abstract~、Override~)
まずは前回ツンデレキャラサンプルを参照しつつ、解説していない構文をざっと見ていきたいと思います。残っていたのはこれだけ、のはず?:
AbstractProperty(Add-AbstractProperty) 関数
AbstractMethod(Add-AbstractMethod) 関数
OverrideProperty(Add-OverrideProperty) 関数
OverrideMethod(Add-OverrideMethod) 関数
さあ、順に見ていきましょう!


AbstractProperty(Add-AbstractProperty) 関数
抽象プロパティを定義するには、AbstractProperty を使います。Add-AbstractProperty 関数のエイリアスですね。

ちなみに、前回解説した構文だけでなく、私が作成し、公開している PowerShell のモジュール全てにおいて、実際の関数とエイリアスをそれぞれ定義するという形を取っています。理由は、PowerShell のモジュールを Import-Module した時に出る警告 "WARNING: Some imported command names include unapproved verbs which might make them less discoverable. Use the Verbose parameter for more detail or type Get-Verb to see the list of approved verbs." を回避しつつ、文脈にあった命名(PSAnonym.Prototype であれば、型宣言らしい見た目)をできるようにするためです。

・・・ただこの方法、確かに要件は満たせているのですが、よく考えなくても管理が煩雑になることは目に見えています。他に良い方法があれば、是非共有させていただきたいところですね (^_^;)

では、コマンドのシグニチャを確認しておきましょう:
PS C:\> Add-AbstractProperty -?
Add-AbstractProperty [-Name] <String> [-Getter] [-Setter] [-InputObject <PSObject>] [-Hidden] …
 
抽象プロパティということで、通常のプロパティを定義する Property(Add-Property) 関数と比べると、Getter と Setter が、スクリプトブロックを指定しない、単なるコマンドスイッチになっていることが分かります。ツンデレキャラサンプルでは登場させる隙が無かったので、後ほど OverrideProperty(Add-OverrideProperty) 関数の解説の中で書き方を紹介できればと。
後のスイッチは変わり映えしませんね、どんどん行きましょう!


AbstractMethod(Add-AbstractMethod) 関数
抽象メソッドです。ツンデレキャラサンプルでも、AbstractMethod を使って、$キャラ 基底プロトタイプが持っている 誘う メソッドを抽象メソッドとして定義し、その処理は、派生プロトタイプである $ツンキャラ / $デレキャラ / $ツンデレキャラ で実装していることが見て取れるかと思います。
実際は Add-AbstractMethod 関数のエイリアス、ということでシグニチャを確認してみます:
PS C:\> Add-AbstractMethod -?
Add-AbstractMethod [-Name] <String> [-InputObject <PSObject>] [-Hidden] …
 
抽象プロパティと同じく、通常のメソッドを定義する Method(Add-Method) 関数と比べると、スクリプトブロックを指定していなくなっていますので、非常にシンプルなものとなっています。

抽象メンバの制御構文が出揃ったところで、これらが持っている特別な機能を 2 つほど解説したいと思います。
まず 1 つ目として、抽象メンバを持つプロトタイプは、抽象プロトタイプになり、それ以降新しいコピーを作ることが出来なくなります。例を見てみましょう:

※「抽象プロトタイプ」という言葉ですが、同じプロトタイプベースなオブジェクト指向言語である JavaScript でこういったことを表現する場合、該当するオブジェクトを抽象クラスと呼ぶようですね。自分はクラスベースなオブジェクト指向言語とごっちゃになりそう・・・と思いましたので、抽象プロトタイプという言葉を選んでいます。どちらが適切かどうかよくわかりませんが、雰囲気は伝わるかと (^_^;)。
PS C:\> $animal = Prototype Animal | AbstractMethod Cry
PS C:\> $animal1 = $animal.New()
New : Exception calling "New" with "0" argument(s): "This object is not constructible yet 
because it has some abstract members 'Cry'."
At line:1 char:23
+ $animal1 = $animal.New <<<< ()
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ScriptMethodRuntimeException
 
こんな感じで、コピーを作ろうとすると、エラーが出て作成できないようになっています。

次に、抽象メンバは、派生プロトタイプで明示的に上書きしようと思って上書きしなければダメようにしています。例えば、メソッドの場合だと、派生プロトタイプでは、Method(Add-Method) 関数ではなく、後述の OverrideMethod(Add-OverrideMethod) 関数を使って明示的にメンバを上書きする必要があります:
PS C:\> $animal = Prototype Animal | AbstractMethod Cry
PS C:\> $dog = $animal | Prototype Dog | Method Cry { 'わんわん' }
throw : The member 'Cry' has already existed in the designated object. If you want to override it, you could use 
'Add-OverrideMethod' instead of 'Add-Method'.
パラメータ名: $Name
At C:\Users\User\Documents\WindowsPowerShell\Modules\Urasandesu.PSAnonym\Urasandesu\PSAnonym\Prototype.psm1:167 char:1
4
+         throw <<<<  New-Object ArgumentException $message, '$Name'
    + CategoryInfo          : OperationStopped: (:) [], ArgumentException
    + FullyQualifiedErrorId : The member 'Cry' has already existed in the designated object. If you want to override it, 
    you could use 'Add-OverrideMethod' instead of 'Add-Method'.
パラメータ名: $Name
 
これは、C# で、たまたま基底クラスと同じ名前のメンバを定義した場合に、"The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'." のような感じで警告が出るのと同じですね。
この辺りの厳密さは、動的型付け言語とすればやり過ぎなのかもしれませんが、前回記事で解説させていただいた Field(Add-Field) 関数と同様、個人的には、できればシステム側で見守っていてほしい派だったりするので、この形に落ち着いています。

お次は、これらの明示的上書きメンバですね。


OverrideProperty(Add-OverrideProperty) 関数
OverrideProperty を使って、抽象プロパティの実装を記述することができます。これももちろん、関数 Add-OverrideProperty のエイリアス。シグネチャを見てみましょう:
PS C:\> Add-OverrideProperty -?
Add-OverrideProperty [-Name] <String> [[-Getter] <ScriptBlock>] [[-Setter] <ScriptBlock>] [-InputObject <PSObject>] [-Hidden] …
 
先ほど説明した抽象メンバを上書きできる専用の構文である、ということを除けば、普通の Property(Add-Property) 関数と機能は同じです。ツンデレキャラサンプルでは登場させられませんでしたので、別のサンプルで使い方を見てみたいと思います:
PS C:\> $animal = Prototype Animal |
>>                 AbstractProperty FingersPerLimb -Getter |
>>                 AbstractProperty Limbs -Getter |
>>                 Property AllFingers { $Me.FingersPerLimb * $Me.Limbs }
>>
PS C:\> $snake = $animal |
>>                 Prototype Snake |
>>                     OverrideProperty FingersPerLimb { 0 } |
>>                     OverrideProperty Limbs { 0 }
>>
PS C:\> $horse = $animal |
>>                 Prototype Horse |
>>                     OverrideProperty FingersPerLimb { 1 } |
>>                     OverrideProperty Limbs { 4 }
>>
PS C:\> $chimera = $snake, $horse |
>>                 Prototype Chimera -Force |
>>                     OverrideProperty FingersPerLimb { $Snake.FingersPerLimb + $Horse.FingersPerLimb } |
>>                     OverrideProperty Limbs { $Snake.Limbs + $Horse.Limbs }
>>
PS C:\> $snake, $horse, $chimera | % { $_.AllFingers }
0
4
4
 
・・・はい、良い例が浮かばず、いつものキメラ錬成(干支風味)をしてみました (^^ゞ

最初に $animal: 動物 抽象プロトタイプを準備します。持っているプロパティは、FingersPerLimb: 肢毎の指の数、Limbs: 肢の数、AllFingers: 全ての指の数、となっています。
AllFingers は、FingersPerLimb * Limbs で求められるので、派生プロトタイプではこれらの情報を実装してもらうようにしました。$snake: 巳 派生プロトタイプは指も肢もありませんので、両方 0 です。$horse: 午 派生プロトタイプのほうは、指は各肢に 1 本ずつ、肢が 4 本となっています。$chimera: キメラ はこれらの合成ですね。計算結果はもうお分かりになるかと思いますが、それぞれ、0 本/4 本/4 本 という結果になります。

なお、ここにもちょっとしたチェック機構を設けています。
この例のように、抽象プロパティを宣言する時に Getter しか準備をしなかった場合、派生プロトタイプで Setter を実装しようとすると、こんな感じでエラーを発生させるようにしました:
PS C:\> $slime = $animal |
>>                 Prototype Slime |
>>                     Field m_rand (New-Object Random) -Hidden |
>>                     Method Reset { $Me.m_rand = New-Object Random } -Hidden |
>>                     OverrideProperty FingersPerLimb { $Me.m_rand.Next(1, 10) } { $Me.Reset() } |
>>                     OverrideProperty Limbs { $Me.m_rand.Next(10, 50) } { $Me.Reset() }
>>
throw : The designated member 'FingersPerLimb' can't be overridden because it isn't settable."
パラメータ名: $Name
At C:\Users\User\Documents\WindowsPowerShell\Modules\Urasandesu.PSAnonym\Urasandesu\PSAnonym\Prototype.psm1:238 char:1
4
+         throw <<<<  New-Object ArgumentException ($AssertionMessages.IsSettable -f $Name), '$Name'
    + CategoryInfo          : OperationStopped: (:) [], ArgumentException
    + FullyQualifiedErrorId : The designated member 'FingersPerLimb' can't be overridden because it isn't settable."
パラメータ名: $Name
 
$slime: スライム 派生プロトタイプです。スライムは不定形ですので、指の数や肢の数は乱数で決めるようにしています。

ある時(例えば、派生プロトタイプが増えてきたせいで当初の設計を忘れかけた頃)、各プロパティに値がセットされたタイミングで、乱数をリセットしたほうが良いかな?・・・とやってみましたが、それはできないと怒られ、ああそうだったと気づいた、みたいな。このように、ある程度インターフェースに制限を掛けることで、プログラムの規模が大きくなっても、問題が起きにくくなるのではと思っています。

次で未解説構文は最後ですね。


OverrideMethod(Add-OverrideMethod) 関数
抽象メソッドの実装の記述には、OverrideMethod を使います。例に漏れず、Add-OverrideMethod 関数のエイリアスになっています。シグネチャは以下の通り:
PS C:\> Add-OverrideMethod -?
Add-OverrideMethod [-Name] <String> [-Body] <ScriptBlock> [-InputObject <PSObject>] [-Hidden] …
 
OverrideProperty(Add-OverrideProperty) 関数と同様、抽象メンバを上書きするための専用の構文である、ということを除けば、通常の Method(Add-Method) 関数と全く同じものになっています。ツンデレキャラサンプルでも多用しているところですので、特に解説する必要はなさそうですね (`・ω・´)

以上で、残りの構文は全て説明できました。
・・・今思えば、コマンドのシグネチャもちゃんとヘルプを書いておけば、ここまで説明する必要なかったな、と思ったり。PowerShell は、コマンドごとのドキュメントの仕組みについても標準で持っていますので、PowerShell の良さを広めるのであれば、この辺りももっと活用しなければと反省しております・・・...( = =)

さて、次の項からは、PSAnonym.Prototype を実装する上で使っている実現方法や注意点などを解説したいと思います。





ハイブリッドな構文の実現方法
前回さらっと触れましたが、スクリプトブロックを用いた汎用言語に近い構文(以下、スクリプトブロック構文)と、パイプラインを使った構文(以下、パイプライン構文)の両方をサポートするのは、手間ではあるのですが、個人的には外せなかった要件でした。この実現方法を簡単に説明させていただきたいと思います。プロトタイプの始まりとなる Prototype(New-Prototype) 関数とメソッドの追加処理である Method(Add-Method) 関数を見てみましょう:

Prototype(New-Prototype) 関数 ※抜粋
function New-Prototype {
    
    [CmdletBinding()]
    [OutputType([psobject])]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [psobject[]]
        $InputObject, 
        
        [parameter(Mandatory = $true, Position = 0)]
        [string]
        $Name,

        [parameter(Position = 1)]
        [scriptblock]
        $Declaration,

        [switch]
        $Force
    )
    
    begin {
        $state = @{ Prototype = $null; BaseObjectIndex = -1 }
    } process {
        if ($null -ne $InputObject) {
            foreach ($InputObject_ in $InputObject) {
                & $Inherit $InputObject_ $Name $state $Force
            }
        }
    } end {
        if ($null -eq $state.Prototype) {
            $state.Prototype = & $NewPrototype $Name
        }
        
        if ($null -ne $Declaration) {
            $declarations = @(& $Declaration)
            foreach ($Declaration_ in $declarations) {
                ($member, $mode) = $Declaration_
                & $AddOrOverride $state.Prototype $member $mode
            }
        }
        
        $state.Prototype
    }
}
 
スクリプトブロック構文で使われるのは、第 3 引数の -Declaration スイッチに指定するスクリプトブロックです。この引数はオプショナルになっていますので、省略されているかどうか(=$null かどうか)で、スクリプトブロック構文かパイプライン構文を決めています。
スクリプトブロック構文の場合、345 行目~にある通り、スクリプトブロックを実行してその中身を取り出し、宣言の数分回しながら、そのメンバを追加する、もしくは上書きするかの処理へ流しています。パイプライン構文の場合は、生成したプロトタイプを、ストリームに流すだけですね。ここにはこれといった処理は記述されていません。
「じゃあ、パイプライン構文の場合は、メンバの追加/上書きを行う各関数側で制御している?」と思われた方、鋭い!正解です!!

メンバの制御は似通っていますので、代表的なものとして、Method(Add-Method) 関数の中身を見てみたいと思います:

Method(Add-Method) 関数 ※抜粋
function Add-Method {
    
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [psobject]
        $InputObject,

        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $Name, 

        [Parameter(Mandatory = $true, Position = 1)]
        [scriptblock]
        $Body,
        
        [switch]
        $Hidden
    )
    
    if ($null -ne $InputObject) {
        & $AssertIsPrototype $InputObject    
        & $AssertMemberNonexistence $InputObject $Name ($AssertionMessages.NonexistenceHelp1 -f 'Add-OverrideMethod', 'Add-Method')
    }

    $CacheScriptMethod = $CacheScriptMethod
    $value = { & $CacheScriptMethod $this $Name $Body $Hidden $args }.GetNewClosure()
    $scriptMethod = New-Object Management.Automation.PSScriptMethod $Name, $value
    
    if ($null -ne $InputObject) {
        $InputObject.psobject.Members.Add($scriptMethod)
        $InputObject
    } else {
        , ($scriptMethod, ([Urasandesu.PSAnonym.Prototype.AddModes]::None))
    }
}
 
前回の Field(Add-Field) 関数の解説でも、ちらっと触れていますが、メンバの追加/上書きを行う各関数は、必ず InputObject をパイプラインから取るようになっています。
InputObject が存在すればパイプライン構文、無ければスクリプトブロック構文で使われていると判断しているわけです。52 行目、61 行目でそれらしい分岐がありますね。
52 行目の分岐では、流れてきた psobject があれば、PSAnonym.Prototype で扱えるプロトタイプかどうかをチェックしています。また、前述した意図しない上書きなどのチェックもここで行っています。
57 行目~ 59 行目は、メソッドの表すオブジェクトを生成しています・・・もしかすると、そろそろ、「あれ?そういえば、なんでこの人、普通に関数呼び出しせずに、いつも変数にスクリプトブロック入れて & で起動してるんだ?」と思われる方も出てくるかもしれませんが・・・。これに関しては、次項のモジュール性確保の注意点で解説させていただければと思いますので、もう少々お待ちください <(_ _)>
61 行目の分岐では、流れてきた psobject があればこのメソッドをメンバーとして追加し、無ければこのメソッドがどのようなメソッドかという付帯情報を付けてストリームに流します。ストリームに流したものは、先ほど解説した Prototype(New-Prototype) 関数の 345 行目~で拾われることになります。

実は、このようなプロトタイプとそのメンバ、に限らず、上下関係というか階層構造が決まっているものは、このような手法を取ることで、機械的にスクリプトブロック構文とパイプライン構文の両方をサポートすることができるようになります。もし何かしらの DSL を PowerShell で設計されようとしているのであれば、手法の 1 つとして、頭の片隅に置いておくと良いかもしれませんね。





モジュール性確保の注意点
PowerShell には再利用のための仕組みとして、$PROFILE への直接的な関数記載や、スクリプトファイルの呼び出し、ドットソース化、スナップイン などいくつかの方法があります。その中でも、モジュールは後発(v2 ~)であることもあり、インストールがファイルコピーベースで導入が簡単だったり、どの関数を公開してどの関数を非公開にするといったアクセシビリティの制御がサポートされていたり、セッションへのロード/アンロードが可能だったりと機能も手厚く、これから再利用可能なライブラリを構築するのであれば、モジュールを選んでおいて失敗は無いでしょう。

ただ、このモジュール、PowerShell のセッションとスコープの扱いのせいか、ひとクセある状態になっています(この辺りの話は、昨年の Advent Calendar でもまとめさせていただいています)。例えば以下の例。今の時代、プログラミング言語が扱うスコープの趨勢はレキシカルですので、以下のスクリプトの奇妙さにお気づきになられない方も多いかと思います:
PS C:\> New-Module {
>>       function Greet {
>>           'あけおめ!'
>>       }
>>       function New-Greeter {
>>           { Greet }
>>       }
>>       Export-ModuleMember *-*
>>   } |
>>   Import-Module
>>
PS C:\> $greeter = New-Greeter
PS C:\> & $greeter
あけおめ!
PS C:\> function Greet {
>>       'あれ?なにかおかしい??'
>>   }
>>
PS C:\> & $greeter
あけおめ!
 
この例では、無名のインメモリモジュールを作成し、2 つの関数を定義しています。New-Greeter は、モジュール内に定義された Greet を呼び出すスクリプトブロックを返します。
モジュールからは、*-* のパターンにマッチする名前の関数しかエクスポートしていませんので、Greet はモジュール外に出てしまえば、そのセッションのスコープからは見えないはず・・・つまり関数が見つからない旨のエラーになるですよね?

そう、PowerShell は、ダイナミックスコープが基本であるにも関わらず、モジュールが絡んだ時だけ、なぜかレキシカルスコープ的な動きをする場合があるのです。

なぜこのようなちぐはぐな仕様にしたのか納得いかないのですが・・・神本、PowerShell インアクションの次版には何かしらの理由が書かれているのでしょうか・・・(初版は v1 相当の内容のため、モジュールには触れられていないのです)。

ちなみに、このようなスクリプトブロックを返す関数は、往々にして何らかの環境を保持したくなる場合も多く、そんな場合は GetNewClosure を使ってクロージャを作成することになると思うのですが、そうすると、思い出したようにダイナミックスコープ的な動きに戻ります(!?!?):
PS C:\> New-Module {
>>       function Greet {
>>           'あけおめ!'
>>       }
>>       function New-Greeter {
>>           param (
>>               $Message
>>           )
>>           { (Greet) + $Message }.GetNewClosure()
>>       }
>>       Export-ModuleMember *-*
>>   } |
>>   Import-Module
>>
PS C:\> $greeter = New-Greeter 午年だよ!
PS C:\> & $greeter
Greet : The term 'Greet' is not recognized as the name of a cmdlet, function, 
script file, or operable program. Check the spelling of the name, or if a path was 
included, verify that the path is correct and try again.
At line:9 char:9
+ { (Greet <<<< ) + $Message }.GetNewClosure()
    + CategoryInfo          : ObjectNotFound: (Greet:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

PS C:\> function Greet {
>>       'あーそうだった。ダイナミックスコープならこう動くべきだよね。'
>>   }
>>
PS C:\> & $greeter
あーそうだった。ダイナミックスコープならこう動くべきだよね。午年だよ!
 
ここで、前述のハイブリッドな構文の実現方法で出てきた謎の構文の理由がわかっていただけるかと思います。ダイナミックスコープであっても、あらかじめ、そのスコープ取り込んだ変数に入っているスクリプトブロックを実行するのであれば、何も問題はないはずですよね:
PS C:\> New-Module {
>>       New-Variable Greet {
>>           'あけおめ!'
>>       } -Option ReadOnly
>>       function New-Greeter {
>>           param (
>>               $Message
>>           )
>>           $Greet = $Greet # このスコープの変数に付け替え。GetNewClosure は現在のスコープの環境を保存するため。
>>           { (& $Greet) + $Message }.GetNewClosure()
>>       }
>>       Export-ModuleMember *-*
>>   } |
>>   Import-Module
>>
PS C:\> $greeter = New-Greeter 午年だよ!
PS C:\> & $greeter
あけおめ!午年だよ!
PS C:\> New-Variable Greet {
>>       'この変数は無視され、取り込まれた変数が働いてる。これなら納得!'
>>   } -Option ReadOnly
>>
PS C:\> & $greeter
あけおめ!午年だよ!
 
この動きがあることから、私が作成し、公開している PowerShell のモジュールにおいては、モジュール外に公開しない、内部的にしか利用しない共通関数について、基本的に変数にスクリプトブロックを割り当てる形で、実装を行っています。

・・・ただ、やっぱりわかりにくいですよね。他に良い方法があれば、是非ご教授いただきたいところです (^_^;)





これでフィニッシュ?
な訳無いデショ! L('ω')┘三└('ω')」

・・・失礼しました。
改めてまとめると、思っていた以上に色んなことを考えて作っていたのだなーと思い、感慨深いものがあります。誤解を恐れずに言えば、PSAnonym も、Swathe.Automation も、Prig を作る過程でできた、単なる副産物だったりするのですががが。実際は、作ってみると色々な知見が得られますし、PowerShell に習熟することで実務も効率化できたり、すごい方々にお会いして情報交換できたりするもので、やってみないとわからないものだなーと、年の初めにも関わらず遠い目をしています ...( = =)

そうそう、年の初めで思い出しましたが、その Prig、ついに制限付きながら 1 パス(C# のメソッドの JIT をフックし、処理を入れ替える)が動き始めました!

2014 年中には、何らかの形にして、なんとか世に出したいところです!


最後になってしまいましたが、改めて新年のご挨拶を。

明けましておめでとうございます。
昨年は色々とお世話になりました。
今年もどうぞよろしくお願いいたします!!! <(_ _)>



2 件のコメント:

  1. 最後のモジュール内関数のスコープの挙動についてですが、
    さしあたっての対処法はpraivateスコープを付ければいいのではないでしょうか。

    挙動としては起点がモジュール内スコープなだけでダイナミックスコープの挙動なので、
    自動でクロージャになっているような状態みたいですね。

    new-module {
    $fuga=100;
    function piyo { { $fuga } };
    function hoge { ri variable:fuga };
    Export-ModuleMember * } | ipmo
    &(piyo) # 100を返す
    hoge # モジュール内変数fugaを削除
    $fuga = 999
    &(piyo) # グローバル変数fugaが呼ばれる

    逆にGetNewClosureメソッドを使用した場合ではしっかり?そのメソッドを実行しているスコープで
    スクリプトブロック内の変数・関数が紐付けられるので、平常運行するようですね。

    in action第2版でモジュール内スコープがなによりも優先されることは書かれていますが、
    生成後のスクリプトブロックの実行位置が無視される点についてはよく分からないです。

    返信削除
    返信
    1. モジュールに関するスコープ管理は、頭を悩ませることばかりですね。。。

      > in action第2版でモジュール内スコープがなによりも優先されることは書かれていますが、
      > 生成後のスクリプトブロックの実行位置が無視される点についてはよく分からないです。
      なるほど、やはりin action 第2版でモジュール内スコープの件は書かれているのですねー。私も、英語だからと敬遠せずに勉強しないと。。。
      情報ありがとうございます! <(_ _)>

      削除