okuの日記: 関数型プログラミング言語 sh
既にどこかでやられているような気もしますが、ネタを以下略。
sh は Unix に標準バンドルされている関数型プログラミング言語です。 最近の日和った商用 Unix ベンダの中にはプログラミング環境を標準で提供しないチキン野郎が少なくないのですが、そのような去勢ベンダですら sh は提供しています。 本稿では LISP と対比して sh プログラミングを学びます。
みなさんご存知の通り、LISP では
(関数名 値)
という形式で関数を適用しますが、sh では以下のようにします。
echo 値 | 関数名
また、LISP で関数を連鎖的に適用する場合は以下のように書きますが:
(関数2 (関数1 値))
sh では以下のように書きます:
echo 値 | 関数1 | 関数2
最初に適用する関数が左側に、後で適用する関数が右側に来ますので、sh の方が LISP よりも分かり易いですね。
それでは早速コードを書いてみましょう。 普通に Unix に login すれば、そこはもう sh プログラミングのできる環境です (login shell が csh 系の人は豆腐の角に頭をぶつけて sh を実行してください)。 まずは「+1する」関数を定義して、実行してみることにしましょう。
$ # LISP のビルトイン関数「1+」相当の関数の定義
$ add1() {
> sed 's/$/ 1+p/' | dc
> }
$ # 試しに実行
$ echo 3 | add1
4
$
もちろん、関数「add2」は以下のように関数「add1」を2回適用することで定義できます。
$ add2() {
> add1 | add1
> }
$ echo 3 | add2
5
$
簡単ですね! LISP の「+」相当の関数を定義するには以下のようにします。
$ plus() {
> sed 's/ */ +/g' | bc
> }
$ echo 3 1 4 | plus
8
$
とまあこの調子で何でもできます (嘘です。 できません)。
では、次に定番の「car」と「cdr」を定義してみましょう。 と言っても、残念なことに sh は階層構造のあるリストを扱うことができないので、厳密には「cdr」や「cadr」や「cddar」や以下略を定義することができません。 sh は平坦なリストしか扱えないのです (この点で sh は Perl に似ていますが、Perl はリファレンスをうまく使うことで「リストのリスト」を表現することが可能です)。
ううむ、残念。 誤解を招かないように「first」と「rest」という名前にしましょうか。 そのまえに「list」に相当するものを定義する必要がありますね (「cons」は勘弁してください)。
$ list() {
> tr -s ' ' '\n'
> }
$
sh は関数型言語としては非常に原始的なので「list」関数のようなものですら、プログラマが定義してやる必要があります。 やれやれ。
さて「list」も定義し終えたことですし、本番の「first」と「rest」に行ってみましょう。
$ first() {
> awk 'NR == 1'
> }
$ rest() {
> awk 'NR != 1'
> }
$ second() {
> rest | first
> }
$
ああ、勢い余って「second」まで定義してしまいました。 では試してみましょう。
$ echo "fuga" "hoge" | list | first
fuga
$ echo "foo" "bar" "baz" | list | second
bar
$
言うまでもなく、LISP では以下の例に相当します。
[1]> (first (list "fuga" "hoge"))
"fuga"
[2]> (second (list "foo" "bar" "baz"))
"bar"
[3]>
オチはありません、悪しからず。
2007-05-13 追記:
list は sed ではなく tr を使うようにしました
(「\n」を使えない sed の実装の方が多いと思ったので)。
plus の結果に誤記があったので訂正しました。
関数型プログラミング言語 sh More ログイン