2012年5月30日水曜日

Powershellクロージャあり〼。その2

PowerShell覚え書き


お題

前回のブログでPowerShellのクロージャ機能を使ってみました。せっかくなので、バリエーションとして一つの環境(クロージャ)を複数の関数から共有するようなサンプルを作りました。(Paul GrahamのANSI Common Lispからのパクリです。)前回と同じくアキュムレータなのですが、リセット機能ありのバージョンです。
function Accum()
{
$i=0
$closure={
        $obj = New-Object PSObject
        
        $fn = { $script:i = 0 }
        Add-Member -inputobject $obj -membertype scriptmethod -name reset -value $fn

        $fn = {$script:i++ ;$i }
        Add-Member -inputobject $obj -membertype scriptmethod -name stamp -value $fn

        return $obj
    }.GetNewClosure()
    return &$closure
}

$a=Accum #疑似コンストラクタ

"stamp => "+$a.stamp()
"stamp => "+$a.stamp()
"stamp => "+$a.stamp()
"reset "+$a.reset()
"stamp => "+$a.stamp()
"stamp => "+$a.stamp()


実行結果は以下の通り。
    stamp => 1
    stamp => 2
    stamp => 3
    reset
    stamp => 1
    stamp => 2


メモ

  • 着想はGetNewClosure()はブロックの持つメソッドなので、ブロック内でオブジェクトを作って複数のメソッドを用意すれば環境を複数のインターフェース間で共有できるということです。
  • 関数Accum内でクロージャ定義だけでなく、&でのクロージャ呼び出しを返しているので、利用者側には一種のオブジェクトのように見え、メソッドの呼び出しもスムーズです。
  • クロージャを使うことでカプセル化ができたので、オブジェクト指向的な使い方ができますね。(誰かのブログでclass定義用のライブラリを作っている人がいましたが、クロージャを使ってたのかな。。)
  • ブロック内の変数のスコープの書き方が面倒な点は相変わらずです。なにかよい方法はないのか。


クロージャで面白いことがまだできそうです。どこかでもう少し他のサンプルを作りたいと考えています。


追記(2012/6/2)

遊び半分で上記ソースをオブジェクト指向っぽく?改変してみました。
function new([String]$cname)
{
    $obj = New-Object PSObject
    $sb=Get-Variable $cname -valueonly
    & ($sb.GetNewClosure()) #Closureを作成し同一ブロック内でブロック呼出し
    return $obj
}

function method([String]$mname,[ScriptBlock]$msb)
{
    Add-Member -inputobject $obj -membertype scriptmethod `
               -name $mname -value $msb -Force
}

function PSClass([String]$cname,[ScriptBlock]$csb)
{
    Set-Variable -name $cname -value $csb -scope script
}
<#------------------------------------------------------------#>

PSClass Accum {
    $i=0

    method reset { $script:i = 0 } #スコープ修飾はまだ必要
    method stamp { $script:i++ ;$i } #スコープ修飾はまだ必要
}

<#------------------------------------------------------------#>
$i=50

$a=new Accum #疑似コンストラクタ
$b=new Accum #疑似コンストラクタ

"`$a.stamp() => "+$a.stamp()  # $a.stamp() => 1
"`$a.stamp() => "+$a.stamp()  # $a.stamp() => 2
"`$b.stamp() => "+$b.stamp()  # $b.stamp() => 1
"`$b.stamp() => "+$b.stamp()  # $b.stamp() => 2
"`$a.stamp() => "+$a.stamp()  # $a.stamp() => 3
"`$a.stamp() => "+$a.stamp()  # $a.stamp() => 4
"`$a.reset() "+$a.reset()            # $a.reset()
"`$a.stamp() => "+$a.stamp()  # $a.stamp() => 1

PSObject作成や、メソッドの追加などは関数化できたのですが、肝心のインスタンス変数をメソッドから参照するにはscope指定が必要となってしまいました。インスタンス変数定義用のメソッドを用意したり、文字列マッチングしてメソッド内の変数にスコープを自動追加することもできたのですが、本筋から外れていくのでこれ以上はやめました。
結果的には、糖衣構文ならぬオブジェクトJava中毒構文というか、人面魚見たいというか、「どう見ても違うやろ」というものになりました。Powershellのスクリプトブロックの壁は分厚そうです。

追記(2012/6/3)

Yugui著「初めてのRuby」で調べたらrubyの場合の変数表記は、ローカル変数とインスタンス変数、クラス変数は、aと@aと@@aというように名前の先頭文字で区別しているそうです。それであれば上記のスコープ問題もインスタンス変数専用の頭文字($$iとか)を付けるようにすれば、共通化した関数内で解決できそうです。というか、そんなことも知らなかった自分が恥ずかしいですが、色々な言語を見比べる視点を持つのは良いことですね。その為には各言語の簡潔かつ良質な入門書をそれぞれ手元に置いておきたいものです。

0 件のコメント:

コメントを投稿