関数型言語shの基礎文法最速マスター
関数型言語shの文法一覧です。他の関数型言語をある程度知っている人がこれを読めば、shの基礎をマスターしてshを書けるようになっています。以下、Clojureあたりを想定して説明します。
注意:これは基礎文法最速マスターねたのパロディです。動作は本物ですが、意味はコジツケです。
REPL
shの処理系は、POSIX準拠のUnix系環境であれば標準で用意されています。REPLを起動するには、shを実行します。
sh
すると、プロンプトが表示されます。
$
shのほかに、REPLに行編集機能を付けたbash・zsh・tcshなどもありますが、ここでは割愛します。
なお、REPLとして使うほかに、あらかじめ用意したスクリプトをshで実行することもできます。
sh hoge
シーケンス
shの扱うデータは、すべて、ある単位(ラインと呼びます)のデータが並んだシーケンスです。たとえば、seq関数(Linuxで使えます)は、指定した範囲の整数が並んだシーケンスを返します。
$ seq 1 3 1 2 3
厳密には、シーケンスはfdというモナド(って何?)のようなものにくるまれています。REPLは、式を評価して得られたシーケンスをfdから受けとり、1ライン1行で表示します。
関数の返す値をほかの関数に渡す
関数の返す値をほかの関数に渡すには、"|"を使います。
$ seq 1 100 | head -n 2 1 2
head関数は、渡されたシーケンスの先頭からn要素を取り出す関数です。反対に、tail関数は、渡されたシーケンスの末尾からn要素を取り出す関数です。
$ seq 1 100 | tail -n 3 98 99 100
シーケンスを結合する
関数の返すシーケンスと、別の関数が返すシーケンスを結合するには、";"を使います。
$ seq 1 2 ; seq 10 12 1 2 10 11 12
注意が必要なのは、";"は"|"より優先順位が低いことです。";"を優先するには"{}"で囲みます。
$ { seq 1 2 ; seq 10 12 ; } | head -n 3
1
2
10
2つ目のseq関数の後にも";"が付いていることに注意してください。これは、シーケンスの末尾との結合を意味します。
"()"で囲む方法もあります。"()"の場合は、2つ目のseq関数の後に";"は付きません。"()"の場合は、全体が新たなシーケンスを意味するためです。
$ ( seq 1 2 ; seq 10 12 ) | head -n 3 1 2 10
遅延評価
関数が返すシーケンスは、すべて計算されてから次の関数に渡されるのではなく、必要な部分だけ計算されます。たとえば、yes関数は無限に続くシーケンスを返します。
$ yes 10 10 10 10 10 10 (以下略)
これを上記のhead関数に渡すと、yes関数の計算が終わってから(つまり無限時間後に)head関数に渡されるわけではなく、必要な値だけが渡ります。
$ yes 10 | head -n 3 10 10 10
関数定義
新たに関数を定義するには、以下のように書きます。
$ foo() { yes 10 | head -n 3 ; }
$ foo
10
10
10
この"{}"は、「シーケンスを結合する」で解説した"{}"と同じものです。そのため、"()"で定義することもできます。
$ foo() ( yes 10 | head -n 3 ) $ foo 10 10 10
いわば、シーケンスの一連の処理に名前を付けたものといえます。
オプションは"$番号"で参照します。
$ foo() { seq 1 $1 ; }
$ foo 3
1
2
3
map
map相当の処理は、以下のように書きます。
$ seq 1 3 | while read n ; do echo $(( n + 3 )); done 4 5 6
whileがmap、read nがnへの値の束縛、doが処理の開始、doneが処理の終端に当たります。$((〜))は数値演算してその値を取り出す操作です。echoは値を返す操作を意味します。doの前やdoneの前の";"は、"{}"と同様にシーケンスの先頭や末尾との結合を意味します。
なお、この場合の";"はwhileの中のものとみなされるため、"|"より優先されます。
内包表記
シーケンスを作る内包表記には、forを使います。
$ for i in $(seq 1 100) ; do echo $(( i + 2 )) ; done | head -n 2 3 4
条件
条件が真のときと偽のときとで値を変えるには、以下のように書きます。
$ [ 1 = 1 ] && echo 3 || echo 4 3 $ [ 1 = 2 ] && echo 3 || echo 4 4
"[]"が条件、"&&"の後が真のとき、"||"の後が偽のときです。
再帰
seqのように無限に自然数列のシーケンスを返す関数naturalは、再帰を使って以下のように書けます。
$ natural() { echo 0 | _natural ; }
$ _natural() { read n ; echo $n ; { echo $(( n + 1 )) | _natural ; } ; }
$ natural | head -n 5
0
1
2
3
4
高階関数、クロージャ
関数や無名関数を、関数の引数に渡すこともできます。
$ foo() { eval "$1" ; }
$ foo "echo 'Helo'"
Helo
evalは、渡された関数や無名関数を呼び出すことを意味します。Common Lispでいうapplyやfuncallに相当します。
こんなことも。
$ seq 1 5 | while read n ; do foo "echo $n" ; done 1 2 3 4 5
つまり、無名関数"echo $n"として渡された中の"$n"には、呼び出し先の環境ではなく、呼び出し元のレキシカルな環境での値が束縛されているわけです。funarg問題を解決したクロージャですね。
まとめ
shは関数型言語です(キリッ
「仮面ライダーSPIRITS 超絶黙示録」
マンガ「仮面ライダーSPIRITS」の解説本。なのだけど、これが、昭和ライダーの全エピソード・全シーンを覚えてないと書けないような、超こだわりの本なのだ。ライスピのここのシーンはあのエピソードのあのシーンへのオマージュ、とか、これは企画段階のときのあの設定にもとづいている、とか、マニアックな検証が続いている。おかげで、マンガ単行本のサイズなのに、1冊読むのに何日も。
という一方で、データ偏重ではなくて、元番組への愛に満ちあふれてもいる。本書の100問インタビューでも、村枝アニキがこんな泣かせることを言っている。
(一番大切だと思うことは)やはり映像に対する最大限のリスペクトをもって描くことでしょうね。「時間と予算も限られていたんだろうけど、たぶん当時のスタッフは、こういうビジュアルを目指していたんだろうな」と、ポジティブに捉えることが第一だと思います。(略)とにかく僕は、斜に構えて、映像を否定するようなアレンジは嫌いなんです。
それに応えて、解説でも、マスクの空気穴のような部分までいかにライスピでデザインとして昇華しているかなどを検証してくれている。
というわけで、ライスピファン必見。
そのほか、マンガ家さんやクリエーターさんからのメッセージや、ライスピの元になったライダーの「落書き」なども見所。ちなみに、用語辞典で「三浦市」の項にくろば亭の名前が突然出てくるけど、解説者さんのお気に入りなのかな。
「Software Design」2010年2月号
技術評論社
今号に掲載されたインタビューでは、米国と比較してこのような発言がある。
- オープンソースの利点を、商用製品と比較した支払いの面から捉える
- ソフトウェア自体にあまり価値が認められず、SIのほうが重視されがち
- 細分化がお家芸で、コミュニティを設立するのが得意
ちなみにこれは、Apache Software Foundation等のGianugo Rabellino氏(イタリア)がヨーロッパの事情について語ったもの。もちろん、オープンソースへの包容力が米国より強く、すべてがベンダーにコントロールされる商業ソフトに不満を持つ人が多い、ということも言っているので誤解なきよう。あと、カプチーノは午前11時以降に飲むイタリア人はいない、とか。
そのほか、Tokyo Cabinet・kumofs・ROMA・Flareの作者がそれぞれソフトの特徴を解説する特集「Key Value Store講座」、松信さんがMySQLの特性や性能を引き出すポイントをわかりやすく解説する短期連載「MySQL完全理解への道」、TOPPERS/JSPの概念や移植について解説する「リアルタイムOSと普通のOSは何が違う?」などいろいろ。
JavaScriptでAmazon PAAPI
といっても、「Product Advertising API にJavaScriptで対応する」の改造版です。話題としてはいまさら感がありますが。
呼び出し方。
url = make_signed_url(URLのhost, URLのpath, URLのquery, シークレットキー)
元バージョンと同じく、jssha256が必要。IE対応。とりあえず実用的な速度で動いてます。
最大の問題は、Amazonの規約によりシークレットキーを隠す必要があるので、インターネット上のWebでは使えないこと。
秀丸8で文字数を数えるマクロ
秀丸8(執筆時点でβ)では、文字の内部表現が変わったかわりに、マクロにcharcount()関数が追加された。こんな感じで呼び出せる。
上2/3の定数とコメントは、ヘルプからのコピペ。あとは、それをcharcount()に渡してるだけ。
String#[]のサブクラスでの挙動
Rubyで。
$ irb
irb(main):001:0> class Foo < String; end
=> nil
irb(main):002:0> s = Foo.new('abc')
=> "abc"
irb(main):003:0> s.class
=> Foo
irb(main):004:0> s[0..1].class
=> Foo
へー。
1つの条件で複数の変数を束縛する方法を考える
Schemeコードバトンでいじった箇所のうち、気になっていた部分について、ひげぽんさんからチェックをいただきました。
swap! って何だと思ったらマクロだった。こういう使い方は自分はしないから面白いなー。
はい、苦しまぎれの使いかたです(笑)。バトンを渡したあともちょこっと考えていたので、問題(と私が思っている)点を、素人なりにまとめてみます。もっとうまいやりかたがありそうなので、ハッカーさんアドバイスpls。
書いたコードと考えたこと
もともとのプログラムは、英単語を出題して日本語の意味を考えさせるものです。私のところに来たときには、リスト構造のデータから問題と回答をとりだすアクセッサ手続きが、それぞれ整備されていました。
(word-spec-word spec) (word-spec-meaning spec)
で、私は、逆に日本語を出題して英単語を考えさせるオプションを追加してみました。まず考えたのが、アクセスするときに条件判断する方法です。
(if ($$ reverse) (word-spec-meaning spec) (word-spec-word spec)) (if ($$ reverse) (word-spec-word spec) (word-spec-meaning spec))
あるいは。
((if ($$ reverse) word-spec-meaning word-spec-word) spec) ((if ($$ reverse) word-spec-word word-spec-meaning) spec)
これで駄目なわけじゃないのですが、同じ条件判断を2箇所でやっていて、しかも両者が排他的な関係にあるのが、自分には気持ち悪く感じました。…手続き脳ですいません。
となると1つの条件でset!するかな、と考えたわけです。
(let (question answer)
(if ($$ reverse)
(begin
(set! question word-spec-meaning)
(set! answer word-spec-word) )
(begin
(set! question word-spec-word)
(set! answer word-spec-meaning) ))
… )
さすがにこれは逆に、Scheme的に気持ち悪い。というわけで、swap!マクロを定義して、気持ち悪さを軽減してみました。
(let ((question word-spec-word)
(answer word-spec-meaning) )
(when ($$ reverse)
(swap! question answer) )
… )
let直下にswap!を起くことで状態臭を減らしたつもりではありますが、いずれにしてもSchemeっぽくないなあと。これがバトンを回したバージョンです。
多値で?
提出した後でもちょっと考えてました。で、問題をこのエントリーのタイトルのように「1つの条件で複数の変数を束縛する」と自然言語で考えてみたところ、「多値」という方法を思いつきました。
(import (srfi :8))
(receive (question answer)
(if ($$ reverse)
(values word-spec-meaning word-spec-word)
(values word-spec-word word-spec-meaning) )
… )
これがキレイな方法かどうかはともかく、問題に行き詰まったら異なる抽象形式に変えて考えてみる(プログラミング言語→自然言語とか)のも一つの手かな、と。

![Software Design ( ソフトウェアデザイン ) 2010年 02月号 [雑誌]](http://ecx.images-amazon.com/images/I/51rHLVkJy4L._SL160_.jpg)
