PowerShellのバイナリモジュールをF#で書こう!
この記事はPowerShell Advent Calendar 2014の5日めの記事です。
F#の文法は非常に簡潔で、短いタイプ量でコードを記述できるようになっています。このため、いったんF#のコーディングになれてしまうとC#でのコーディングが少し億劫になってしまいがちです(異論は認めません)。PowerShell用にバイナリーモジュールを書くときも同様です。できればF#で書きたい!
バイナリモジュールをF#で書くことそのもはそれほどむつかしくありません。C#で書くときと同じようにPSCmdletを継承したクラスを定義してアセンブリを生成するだけです(C#によるバイナリモジュールの生成の詳細については、ぎたぱそ先生の「PowerShell のモジュール詳解とモジュールへのコマンドレット配置手法を考える」をご覧ください)。
以下は、パイプラインを通じて与えられた整数の中から最大値を返すコマンドレットSelect-MaxをF#で定義した例です。
namespace sample
open System.Management.Automation
[<Cmdlet("Select", "Max")>]
type SelectMax() =
inherit PSCmdlet()
[<DefaultValue>]
val mutable private result : int
[<DefaultValue>]
val mutable private _InputValue : int
[<Parameter(Mandatory=false,ValueFromPipeline=true)>]
member public this.InputValue
with get() = this._InputValue
and set value = this._InputValue <- value
override this.BeginProcessing() =
do this.result <- 0
override this.ProcessRecord() =
do this.result <- max this.result this._InputValue
override this.EndProcessing() =
do base.WriteObject(this.result)
このF#のソースファイルをバイナリモジュールとして利用するには、まずコンパイルし、生成されたアセンブリをImport-Moduleで読みこんでやる必要があります。以下はコマンドラインコンパイラfsc.exeを利用してコンパイルしている例です。
fsc.exe --out:sample.dll --target:library `
--reference:System.Management.Automation sample.fs
Import-Module .\sample.dll
これで以下のようにF#のソースファイルに記述したコマンドレットを利用できます。
PS C:\> @(1,2,3) | Select-Max
3
さて、これで一応使えるのですが、fsc.exeでわざわざコンパイルするのが少し面倒です。特にfsc.exeには標準でパスが通っていないので、上記のように普通にfsc.exeとだけ入力するだけでは通常エラーになりますし、出来上がったモジュールを配布する時のことを考えると、環境変数PATHの設定をしてもらうのも少し億劫です(F#を利用していない人にとってはfsc.exeをインストールするのがそもそも億劫なのでは、といった疑問は思っても口には出さない)。
F#のソースファイルをモジュールのようにあつかう
そこでF#のソースファイル、上記の例でいえばsample.fsですが、これをあたかもモジュールのように扱うことはできないでしょうか。例えば以下のようにです。
Import-Module sample.fs
さすがにいろいろな制約があって、これはそのままに実現するのはちょっとむつかしいのですが、これにかなり近い形なら可能です。今回ここで紹介するPowerShellモジュールFSharpを用いると、以下のような形でF#のソースをモジュールとしてロードできるようになります。
Import FSharp
New-FsModule sample.fs | Import-Module
New-FsModuleでまずモジュールを生成し、それをImport-Moduleで読みこむという二段階の処理になっていますが、fsc.exeでコンパイルして……という手順と比較すればかなり手軽に使えるのではないでしょうか。
実装の概要
興味のある方もいるかもしれませんので、念のためFSharpの概要についも簡単に触れておきましょう。ソースを見てわかるように、このモジュールでは以下の3つの関数が定義されています。
Find-FscExe | fsc.exeがインストールされている場所をレジストリから探し出す |
Invoke-FscExe | Find-FscExeを用いて発見したfsc.exeを起動します |
New-FsModule | 指定されたF#のソースファイルをFind-FscExeを用いてコンパイルし、生成されたアセンブリへのパスを返します |
解説が必要な箇所はほとんどないかと思います。Find-FsxExeのロジックがいくぶんアドホックに見えますが、こちらはFSharp.Compiler.CodeDomパッケージのCompilerLocationUtil.fsを参考にしています。
NOTE: サンプルを含めたソースファイル一式はgithubから入手可能です。