シミュレーション用のREPL環境としてPowerShellを使う

プログラマにとって、使い慣れたプログラミング言語のREPLは非常に便利なものだ。電卓代わりの計算機としても、コーディング時にちょっとした機能を探求的に実装していく実験環境としても、REPLは非常に重宝する。

ここ2~3年は、ちょっとしたシミュレーションをする際に、PowerShellのシェル環境をREPLとして使っている。シミュレーションと言っても、予め何らかのアルゴリズムを関数ないしクラス化しておいて、色々とデータを流し込んで結果の数値を眺めてアレコレ考える程度だ。グラフ化のような高度なことはしていない。やってもせいぜいOut-GridViewに流し込むぐらいだ。

普段はSchemeRubyのREPLを使うのだが、私の周囲にはSchemeRubyの利用者がいない。これらの言語でシミュレーション用の環境を構築しても、引き継ぎや業務委託(≒同僚に作業を横流し)する時に、REPLの環境構築から始めてもらわなくてはならない。

しかし幸いなことに、私の周囲はWindowsユーザばかりだ。PowerShell 5.1なら高確率で同僚のPCにも入っている。PowerShellを前提にシミュレーション環境を用意しておけば、同僚に引き渡す時にちょっとだけ手間が減ることになる。

REPLとしてのPowerShell

PowerShellは、その出自からコマンドプロンプトUnix環境のシェルと対比されがちだが、実のところ全くの別物である。どちらかといえば「手軽にシステム管理用APIや外部の実行ファイル類を叩くことができる、Microsoft独自のプログラミング言語」と考えた方がよい。その言語のREPLと簡易IDEWindowsに付属しているのである。

PowerShellのシェル環境では、以下のようにREPL的に四則演算などが可能である。

PS C:\PS> 1 + 1
2
PS C:\PS> "foo"
foo
PS C:\PS> 1..5
1
2
3
4
5
PS C:\PS> 1..5 | %{ $_ * 2 }
2
4
6
8
10
PS C:\PS> _

コマンドプロンプトUnix環境のシェルは、その出自より、ほぼ全てが「コマンドを実行する」という思想に基づいた形式の構文となっている。

一方でPowerShellにおける構文解析の振る舞いは、式モードとコマンドモードに大別される。その詳細は割愛するとして、コマンドモードではなく式モードとして解釈されるステートメントを記述することで、他の一般的なプログラミング言語のREPLで式を評価するのと同じような使い方ができる。

PowerShellをシミュレーション環境として使う

このエントリを書いている数週間前に、ネットでは3n+1問題(コラッツ予想)の懸賞金の話題が出た。ということで、試しに3n+1問題の計算をしてみよう。

まず3n+1問題の定義を関数化してみる。REPL上ではなく、独立したスクリプト・ファイル collatz-problem.ps1 に記述する。

<#
 # collatz-problem.ps1
 #>

Set-StrictMode -Version Latest

<#
.SYNOPSIS
3n+1問題(コラッツ予想)を実際に計算してみる。

.PARAMETER n
計算開始の値。

.INPUTS
なし。

.OUTPUTS
計算過程と最終結果。
 #>
function Calc-Collatz(
    [parameter(mandatory)]
    [uint64] $n)
{
    if ($n -le 1) {
        $n
    } else {
        $m = $(if (($n % 2) -eq 0) {$n / 2} else {$n * 3 + 1})
        "$n --> $m"
        Calc-Collatz $m
    }
}

厳密に定義に従って実装すると「1 → 4 → 2 → 1」と無限ループしてしまうので、1になったら停止するようにしている。お試し実装であるため、深く考えずに再帰を用いているし、演算時のオーバーフローも考慮していないが、ちょっと試す分には問題ないはずだ。

collatz-problem.ps1 が用意できたら、PowerShellのシェル環境にて当該ファイルを読み込んで、関数Calc-Collatzを使って計算してみる。

PS C:\PS> . .\collatz-problem.ps1
PS C:\PS> Calc-Collatz 1
1
PS C:\PS> Calc-Collatz 2
2 --> 1
1
PS C:\PS> Calc-Collatz 3
3 --> 10
10 --> 5
5 --> 16
16 --> 8
8 --> 4
4 --> 2
2 --> 1
1
PS C:\PS> Calc-Collatz 27 | Measure-Object -Line

Lines Words Characters Property
----- ----- ---------- --------
  112


PS C:\PS> _

――こんな感じで、スクリプト・ファイルにシミュレーション用のAPIを定義しておき、PowerShellのシェル環境にてドットソーシング演算子を用いて中身をロードして、REPL的にパラメータをアレコレ弄りながらAPIを実行して、結果を眺めるのである。

引き継ぎの際には、スクリプト・ファイルと簡単な手引書を渡せばよい。

もうほんの少しだけ本格的にシミュレーションしてみる

JUAS 日本情報システム・ユーザー協会がほぼ毎年実施しているソフトウェアメトリックス調査にて、COCOMOモデルを用いて人月から最適工期を計算するために必要なパラメータを、統計データより導きだしている。

そこで、JUASの計算式を用いて最適工期と開発者数を計算する workperiod.ps1 を書いたことがある。

前項の3n+1問題と同様に、workperiod.ps1 を使用してPowerShellのシェル環境でアレコレ計算することが可能だ。

PS C:\PS> . .\workperiod.ps1
PS C:\PS> $wp = [WorkPeriod]::new()
PS C:\PS> $wp.Period(12)
5.72357121276666
PS C:\PS> $wp.MinPeriod(12)
4.29267840957499
PS C:\PS> $wp.HeadCount(12)
2.09659311536712
PS C:\PS> $wp.MaxHeadCount(12)
2.79545748715616
PS C:\PS> $wp = [WorkPeriod]::new([Factor]::JUAS_2016)
PS C:\PS> 12, 24, 36 | %{ $wp.Period($_) }
5.95251406127733
7.49969776559852
8.58501084712603
PS C:\PS> _

ところで、Windows上のPowerShellならば、頑張ればWPF/XAMLを使用してGUI化できなくもない。ということで、workperiod.ps1 とセットで利用する workperiodgui.ps1 も存在する。

PS C:\PS> Get-ChildItem *.ps1 | Select-Object Name

Name
----
workperiod.ps1
workperiodgui.ps1


PS C:\PS> .\workperiodgui.ps1

GUI化が適切かどうかについては「課題の内容次第」だろう。実際には、GUI化が必要となる機会は少なく、それよりも表やグラフによる描画が求められる方が多いと思う。

表についてはOut-GridViewに流し込むのが手っ取り早い。グラフ化については未経験だが、手元のツールで何とかするならば、おそらくExcelを使用するだろう(PowerShellからCOM経由でExcelを操作するスクリプトを何度か書いたことがあるので)。