PowerShellの関数を手続き的に書きたい(出力が戻り値に含まれてしまう問題)
(追記 2015/11/08 1:17) 指摘を頂いたので記事を修正しました
(追記 2015/12/05 2:27) 追記記事書きました
「PowerShellの関数を手続き的に書きたい(出力が戻り値に含まれてしまう問題)」を書いて約0.08年が経過しました - Humanity
本文
PowerShellは、関数の中で標準出力に出力すると戻り値に含まれる仕様です。
複数行出力があれば配列として全ての出力が戻ってきます。
つまり出力と戻り値が一緒です。
しかし、この仕様はバッチファイルやシェルスクリプトとは違うため、PowerShellをよく知らない人が書いた仕様書をコーディングする時、returnしたつもりないのに戻り値が複数返ってきて困る…となります。
普通、設計書は手続き的に書かれているので、(限度はありますが)なるべく設計書の文章とコードを対応させるような書き方をしようとするとどうしても手続き的にならざるを得ません。
というかまぁ消極的な理由として下手に関数型的な書き方をするとコードレビューの時に説明を求められて四苦八苦します…*1
よってあえて手続き的に書きたいケースもあるのです。
コード
.{
と}
の中に本体を書き、その中で返したい値を代入し、即座にreturnします。
.{
と}
の中でreturnするとブロックを抜けるので、関数末尾のreturnの直前にジャンプできます。
function func { $ret = 0 # ブロックの中でreturnするとブロックを抜け、関数の末尾に移動する。 .{ if (...) { $ret = 255 return # ブロックを抜ける echo "このechoは実行されない。" } return echo "このechoも実行されない。" } | Out-Null # ブロックの中の出力は戻り値に含めない。 return $ret }
ちなみに.{}
はドットソース演算子(.
)と{}
で作成したブロックから構成されていて、{}
で作成したブロックをドットソース演算子(.
)で実行するという意味だそうです。
ブロックは多くのプログラミング言語同様スコープが限定されますが、ドットソース演算子で実行した場合は実行するスクリプトブロックを実行元スコープに持ってきます。
一方 &{}
という書き方もあるようで、これも&呼び出し演算子(&
)とブロック({}
)の2つから成っており、こちらはスクリプトブロック内部で処理を留めます(つまり代入した値はブロック内でのみ有効)。
今回戻り値を設定したいため.{}
を使用していますが、スコープを限定したい用途では&{}
を使った方が良さげです。
他にもクロージャ使えたり何だか色々楽しそうですね(記事末尾項の「色々アドバイス頂いた方達」参照)。 もっとPowerShellについて深追いしたくなりました。
最近
ネタがなくて仕事の事を無理矢理ネタにした感が強い。
色々アドバイス頂いた方達
ありがとうございます!>@rbtnn さん、@guitarrapc_tech さん
PowerShellは言語的にアレなので、関数型言語のように、副作用がない関数を書いていきたい。そうしないと色々とデバッグが辛い。
— うさぎ (@rbtnn) November 7, 2015
https://twitter.com/tyru/status/663020453506629632
@rbtnn ちなみにデバッグってどうやってますか。自分は Set-PSDebug -Stepを適当な個所に挟むことで対処してる感じです…
@tyru ①関数ごとにWrite-Hostのprintfデバッグ。 ②Set-PSDebugを使う。 ③PowerShell_ISEを立ち上げる。 のどれかですかね。
— うさぎ (@rbtnn) November 7, 2015
これは、藻き戻しとか Sharowing ではないです。 「&{ ~ }という記法もあり、そちらは多くのプログラミング言語のブロック同様スコープが限定され、かつブロック内で変数に代入してもブロックを抜けると巻き戻されるようです」
— guitarrapc_tech (@guitarrapc_tech) November 7, 2015
. {} の ドットスコープは、実行するスクリプトブロックを実行元スコープにもってきています。なので $hoge; . {$hoge = 4}; return $hoge; は 4が出力されます。
— guitarrapc_tech (@guitarrapc_tech) November 7, 2015
一方で、 &{} は{} スクリプトブロックないぶで処理をとどめます。そのため、 $hoge; & {$hoge = 4}; return $hoge; は$hoge が空なので出力はないです。
— guitarrapc_tech (@guitarrapc_tech) November 7, 2015
スクリプトブロックは、パイプラインと並んで PowerShell の要で、 {} と {}.GetNewClosure(); で Closure の取得状態が変わります。これ大事
— guitarrapc_tech (@guitarrapc_tech) November 7, 2015
{} は Func と同様なので、そこを実現するのが .GetNewClosure()
— guitarrapc_tech (@guitarrapc_tech) November 7, 2015
これで .GetNewClosure() ありなしを試すといい
function add([int]$x) { return { param([int]$y) return $y + $x }.GetNewClosure() }
$m2 = add 2
&$m2 3 #5 が返る
— guitarrapc_tech (@guitarrapc_tech) November 7, 2015
$x が束縛されてるかどうか pic.twitter.com/vX7LxkBUok
— guitarrapc_tech (@guitarrapc_tech) November 7, 2015
変更履歴
2015/11/08 1:17
ちなみに&{ ~ }
という記法もあり、そちらは多くのプログラミング言語のブロック同様スコープが限定され、かつブロック内で変数に代入してもブロックを抜けると巻き戻されるようです(つまり代入した値はブロック内でのみ有効)。
ちなみに.{}
はドットソース演算子(.
)と{}
で作成したブロックから構成されていて、{}
で作成したブロックをドットソース演算子(.
)で実行するという意味だそうです。
ブロックは多くのプログラミング言語同様スコープが限定されますが、ドットソース演算子で実行した場合は実行するスクリプトブロックを実行元スコープに持ってきます。
一方 &{}
という書き方もあるようで、これも&呼び出し演算子(&
)とブロック({}
)の2つから成っており、こちらはスクリプトブロック内部で処理を留めます(つまり代入した値はブロック内でのみ有効)。
(その他、なんかやっぱりダサいので全体的に{ ~ }
を{}
に修正)
*1:説明力の無さが辛い