本を読む

読書やコンピュータなどに関するメモ

「Ubuntu Magazine」vol.03

Ubuntu Magazine Japan vol.03 (アスキームック)

アスキー・メディアワークス
売り上げランキング: 198

 "よい子のためのFLOSSまんが"「うぶんちゅ!」の今回は、テクニカルなオチで来た。

 巻頭特集「重大トラブル完全レスキュー」は、ありがちなトラブルを徹底的にシューティング。標準ディレクトリの解説など、「はじめてのコマンド&端末入門」ともども、比較的地味だけどいざというときに役に立つことを解説している。

 「自分だけのUbuntuを自作する」は、あの「萌え☆彡OS」を題材に、UbuntuのカスタマイズCD作成を紹介。

 あと、「10.04LTSアーリープレビュー」で、Firefoxのバージョンが固定されなくなることを知った。

「Software Design」2010年3月号

 第1特集は「Linuxサーバー危機管理対策」。特に第3・4章は、Linuxサーバーの動作状況を確認する基礎を解説している。

 松信さんの「MySQL完全理解への道」の今回は、比較的いじる可能性が多いという設定パラメータを解説。ほか、Apache Software FoundationのJim Jagielskiのインタビューとか、Emacsのちょっと便利なライブラリの紹介とか、ちょっと尖ったリバースプロキシVarnishの紹介とか、Firefoxのネットブック向けカスタマイズとか。付録で小冊子「Android開発 A to Z」が付属。

「さらい屋五葉」第七集

さらい屋五葉 / 7 (IKKI COMIX)
オノ・ナツメ
小学館
「軽蔑いたす。…だが」

「プログラミングClojure」

プログラミングClojure
プログラミングClojure
posted with amazlet at 10.02.24
Stuart Halloway
オーム社
売り上げランキング: 8549

 本書でClojure言語を面白く学んだ。書籍としても、日本語がこなれていて読みやすい。

 Clojureは、JavaVMで動くLispで、変更不可能(immutable)なデータによる並行関数型言語だそうな。言語仕様の組み合わせはもちろん、それ以上に割り切りのセンスがいいと思った。

 よーし、GAE/JでClojureとcompojureを使って並行処理をキメちゃうぞ、とか思ったけど、GAE/Jではスレッドを作れないのであった。

 以下、本書を見ながら手を動かして、ほかのLisp(主にSchemeとかCommon Lispとか)との差分について部分的に写経したメモ。

インストールと起動

 これを書いてる時点でDebian sidにパッケージがある。

$ sudo apt-get install clojure

 依存関係で、openjdk6等が入ってなければ入る。

 REPLを起動。

$ clojure-repl
WARNING: clojure.lang.Repl is deprecated.
Instead, use clojure.main like this:
java -cp clojure.jar clojure.main -i init.clj -r args...
Clojure 1.1.0
user=> 

関数とか値とか

 関数はdefnで定義。仮引数はリストじゃなくてベクタ。

user=> (defn plusone [n] (+ n 1))
#'user/plusone
user=> (plusone 3)
4

 束縛自体もvarという型の値。varは#'hogeまたは(var hoge)で参照できる。userは標準の名前空間。

 無名関数はlambdaじゃなくてfn。

user=> ((fn [n] (+ n 1)) 3)
4

 または#()。この場合、引数は1つなら%、複数なら%1、%2…。

user=> (#(+ % 1) 3)
4
user=> (#(+ %1 %2) 3 5)
8

 値はdefで定義。

user=> (def foo 51)
#'user/foo
user=> foo
51
 ClojureはLisp 1。
user=> (def plusone (fn [n] (+ n 1)))
#'user/plusone
user=> (plusone 3)
4

シーケンス、遅延評価

 carとcdrのかわりにfirstとrest。

user=> (first '(a b c))
a
user=> (rest '(a b c))
(b c)

 リスト以外の「シーケンス」なデータ型も同じように扱える。

user=> (first [1 2 3])
1
user=> (rest [1 2 3])
(2 3)
user=> (first "abc")
\a
user=> (rest "abc")
(\b \c)

 シーケンスとして抽象化された値(上の返り値など)をREPLが表示するときには、仮にリストとして見える。

 シーケンスは遅延評価。iterate関数は第1引数の関数を順に適用した無限シーケンスを返す。takeは先頭からn個の値を返す。

user=> (take 3 (iterate inc 1))
(1 2 3)

 ちなみに()とnilは別。

user=> (= () nil)
false

制御構造的なナニカ

 condはカッコのレベルが1つ少なく、条件と評価する式の交代リスト。関数型なので暗黙のprognは不要ということか。

user=> (cond (< foo 50) 'low (> foo 50) 'high)
high

 prognやbeginに相当するのはdo。副作用目的。Clojureではdoほげほげという名前は副作用を意識したものにつける慣習らしい。

user=> (do 1 2)
2

 「..」は、値を順番に関数の引数にする。Haskellの$みたいというか、アナフォリックマクロ的なことをメソッドチェーンやパイプ風にやるマクロ。

user=> (.. '((a b) c) (first) (first))
a

 loopとrecurは、無名の名前付きlet(?)というところ。

user=> (defn sum-to-0 [n] (loop [r 0 i n] (if (zero? i) r (recur (+ r i) (- i 1)))))
#'user/sum-to-0
user=> (sum-to-0 3)
6

 loopなしだとrecurは関数自身の再帰に。

user=> (defn sum-to-0 [r i] (if (zero? i) r (recur (+ r i) (- i 1))))
#'user/sum-to-0
user=> (sum-to-0 0 3)
6

 Clojureでは関数は末尾再帰などの最適化はしないが、recurなら最適化する。Java VMで実行しやすいようにとか。再帰やループよりシーケンスを使うのがClojure流、ということらしい。

 forは制御構造ではなくシーケンスの内包表記。

user=> (take 5 (for [n (iterate inc 1)] (+ n 10)))
(11 12 13 14 15)

 シーケンスは遅延評価なので、下のprintlnはhogeの値がREPLなどに参照されるまで実行されない。

user=> (def hoge (for [n (range 1 3)] (println n)))
#'user/hoge

 すべての評価を強制するには、値が必要ならdoall、不要ならdorun。

user=> (def hoge (dorun (for [n (range 1 3)] (println n))))
1
2
#'user/hoge

マップ型、セット型

 ほかの言語でハッシュとかハッシュテーブルとかディクショナリとか呼ぶデータ構造は、closureではマップ型。

user=> (def m {:foo 51 :bar 52})
#'user/m

 Clojureでは「,」は空白なので、こう書いても同じ。

user=> (def m {:foo 51, :bar 52})
#'user/m

 ちなみに、構文クォート(準クォート)では「,」「,@」のかわりに「~」「~@」を使う。

 マップ型は関数として参照できる。

user=> (m :foo)
51

 RubyとかPythonとかにあるようなセット型もある。

user=> (def s #{3 5 7})
#'user/s
user=> (s 3)
3
user=> (s 4)
nil

メタデータ

 オブジェクトにはメタデータというデータを付けられる。Lispでシンボルにつくプロパティリストを汎用的にしたようなもの?

user=> (def foo (with-meta 'tom {:age 21}))
#'user/foo
user=> (meta foo)
{:age 21}

 本書の本文では^fooという表記(リーダーマクロ)が使われているけど、訳注にもあるとおり古い仕様なので、使うと警告が出る。

user=> ^foo
WARNING: reader macro ^ is deprecated; use meta instead
{:age 21}

 関数定義などのvarにdoc-stringや型定義を付けられる。これもメタデータ。

user=> (defn plusone "returns n plus 1" [n] (+ n 1))
#'user/plusone
user=> (doc plusone)
-------------------------
user/plusone
([n])
  returns n plus 1
nil
user=> (meta #'plusone)
{:ns #<Namespace user>, :name plusone, :file "NO_SOURCE_PATH", :line 25, :arglists ([n]), :doc "returns n plus 1"}

Javaとのつながり

 Clojureのデータ型はJavaのクラスと一対一対応。Javaの標準クラスで表現できるものは表現する。

user=> (class "abc")
java.lang.String

 Javaのメソッドの呼び出し。

user=> (. "abc" toUpperCase)
"ABC"
user=> (.toUpperCase "abc")
"ABC"

 new。

user=> (def rnd (new java.util.Random))
#'user/rnd
user=> (def rnd (java.util.Random.))
#'user/rnd

 staticメソッド。

user=> (. Math PI)
3.141592653589793
user=> (Math/PI)
3.141592653589793

マルチメソッド

 総称関数。defmultiで名前とディスパッチを定義し、defmethodで処理を実装する。

user=> (defmulti num-or-str class)
#'user/num-or-str
user=> (defmethod num-or-str Number [n] "It's a number")
#<MultiFn clojure.lang.MultiFn@1774b9b>
user=> (defmethod num-or-str String [s] "It's a string")
#<MultiFn clojure.lang.MultiFn@1774b9b>
user=> (num-or-str 5)
"It's a number"
user=> (num-or-str "5")
"It's a string"

 上の「class」は単なる判定関数。型以外の条件でも分岐できる。

user=> (defmulti fuga #(= % "fuga"))
#'user/fuga
user=> (defmethod fuga true [s] 3)
#<MultiFn clojure.lang.MultiFn@b03be0>
user=> (defmethod fuga false [s] 5)
#<MultiFn clojure.lang.MultiFn@b03be0>
user=> (fuga "fuga")
3
user=> (fuga "hoge")
5

動的スコープ

 Clojureは原則レキシカルスコープだけど、Common Lispのスペシャル変数に相当するのが、スレッドローカルな束縛。

 まず、変数を定義だけしておく。(declare a b)は(def a)(def b)と同じ。

user=> (declare a b)
#'user/b

 次に自由変数を参照している関数を定義する。

user=> (defn hoge [] (+ a b))
#'user/hoge

 この関数をletから呼び出すと、レキシカルスコープなのでエラーになる。

user=> (let [a 3 b 5] (hoge))
java.lang.IllegalStateException: Var user/a is unbound. (NO_SOURCE_FILE:0)

 letと同じ書式でbindngを使うと値が渡る。let同様、bindingの外では使えない。

user=> (binding [a 3 b 5] (hoge))
8

 スレッドローカルな束縛はset!で値を変更できる。あまり推奨されない、最後の手段らしい。

user=> (binding [a 3 b 5] (do (set! a 7)(hoge)))
12

値を共有するためのナニカ

 Clojureでは原則、データは変更不可能(immutable)。たとえば、マップに追加するには、元のマップに値を追加した新しいマップを作ることになる。

user=> (def hoge {:foo 3})
#'user/hoge
user=> (def fuga (assoc hoge :bar 5))
#'user/fuga
user=> hoge
{:foo 3}
user=> fuga
{:bar 5, :foo 3}
user=> (= hoge fuga)
false

 たいていは困らないけど、複数のスレッドなどから同じデータを参照したり操作したりする場合には困る。そういうときにはatomとかrefとかを使う。元のデータを参照するオブジェクトを間にはさんで間接参照し、新しい値をそのatomやrefに登録しなおす感じ。そのatomとかrefとかを共有すれば、同じデータを参照したり操作したりできる。

user=> (def hoge-a (atom {:foo 3, :bar 5}))
#'user/hoge-a
user=> (def hoge-r (ref {:maguro 2, :aji 1}))
#'user/hoge-r

 参照は(deref foo)または@foo。

user=> @hoge-a
{:foo 3, :bar 5}
user=> @hoge-r
{:maguro 2, :aji 1}

 参照先を変更するには、atomではreset!。

user=> (reset! hoge-a (assoc @hoge-a :baz 7))
{:baz 7, :foo 3, :bar 5}
user=> @hoge-a
{:baz 7, :foo 3, :bar 5}

 refではref-set。ただし、refの参照先を変更するにはdosyncでロックをかける必要がある。

user=> (dosync (ref-set hoge-r (assoc @hoge-r :maguro 3)))
{:maguro 3, :aji 1}
user=> @hoge-r
{:maguro 3, :aji 1}

 refのほうが手間は増えるけど、dosyncにより複数のrefを整合性を保って更新できる(STM)。

user=> (def total (ref 400))
#'user/total
user=> (dosync (ref-set hoge-r (assoc @hoge-r :aji 2))(ref-set total 500))
500
user=> @total
500
user=> @hoge-r
{:maguro 3, :aji 2}

「Q.E.D.」35巻、「C.M.B.」13巻

 加藤元浩氏のミステリ漫画がまたまた同時発売。あいかわらず高レベルをキープしていて、楽しんだ。

 以下、ネタバレは含まないように注意してるつもりだけど、念のためご注意を。

 「Q.E.D.」35巻は、「二人の容疑者」と「クリスマス・プレゼント」の2編を収録。「二人の容疑者」は、ビジュアル伏線にやられた。「クリスマス・プレゼント」は、ミステリー同好会をからめた学園祭ドタバタコメディーで、ラストでにっこり。

 「C.M.B.」13巻は、「夏草」「霧の山荘」「アサド」「オルゴール」の4編を収録。純粋な本格ミステリじゃないけど、いずれもアッと言わせる展開が印象的。あと、「夏草」は何か遠くを見た気分にさせ、「霧の山荘」は切ない。

「日経Linux」2010年3月号

 特集が「Linuxで蘇る中古パソコン」で、定番の記事だなあと思って読んだら、第2部の1万円台の中古ノートPC編が面白かった。ディスクI/Oを中心に、地味で地道なパフォーマンスチューニング(いわばファインチューニング)が解説されていて、参考になる。

 あと、黒柴でeSATAの初期化に対応するカーネルパッチとか、PAMの解説記事とか、OOo 3.2の解説とか。

軽量MTA「esmtp」を試してみた

 家庭LAN内で動かしているLinuxサーバーで、cronとかのデーモンが出すメールを受け取ろうと思いました。

 LinuxのMTAというと、Postfixやsendmail、あるいはqmailやexim4などが使われています。でも、デーモンからのメールをマシン内でローカルに受け取るだけだし、送信頻度も高くないので、ちょっと大袈裟な気がします。そこで、esmtpを試してみました。

esmtpって?

 esmtpは、ローカル配送はprocmailなどのMDAに丸投げ、外部への配送は外部サーバーにSMTPで丸投げ(スマートホスト)、という簡易MTAです。/etc/aliasesも、いまのところ使えません(Roadmapには載っている)。そのぶん小さいのが特徴です。

インストール

 Debian、Ubuntu、Fedoraなどにはパッケージがあります。CentOSならEPELで。

 以下、Debianの例。本体のesmtpと、sendmail互換インターフェイスのesmtp-runの2パッケージに分かれています。

$ sudo apt-get install esmtp-run procmail

 デーモンはなく、SMTPポートもリッスンしていません。

$ netstat -l | grep smtp
$

 esmtp-runはFedoraやEPELのesmtpにはありませんが、ようするに"ln -s /usr/bin/esmtp /usr/sbin/sendmail"みたいなことをやるパッケージのようです。

試す

 自分宛のメールを送ってみます。

$ cat mail.txt
To: emasaka
Subject: test

This is a test mail.
$ /usr/sbin/sendmail -t < mail.txt

 emasakaにメールが届きます。

$ cat /var/mail/emasaka
From emasaka  Fri Feb 12 09:17:26 2010
To: emasaka
Subject: test

This is a test mail.

 sendmailの標準にインターフェイスを合わせているので、/usr/sbin/sendmailや/usr/lib/sendmailを呼んでメールを出すデーモンやらMUAやらであれば、メールを送れます。/usr/bin/mailqや/usr/bin/newaliasesもあります。もちろん、newaliasesは機能しないダミーです。

root宛のメールなどを自分のメールボックスに

 すべてのメールを自分のメールボックスに集約するには、aliasesが使えないので、procmailでこんな(↓)ふうに設定すればいいのかな(危険はないか、教えてえらい人)。

$ cat /etc/procmailrc
:0
/var/mail/emasaka

 なんかprocmailの意味がないような気もしますが。

設定ファイル

 設定ファイルは/etc/esmtprc(全体設定)または~/.esmtprc(個人設定)。~/.esmtprcのパーミッションは710またはそれより厳しい値(600とか)にしておかないと警告メッセージが出ます。

 外部にメールを送るには、設定ファイルでSMTPサーバーを指定します。SMTP AuthやTLSにも対応しているようです。POP before SMTPは非対応。実験は面倒なのでパス。

 procmail以外のMDAに変えたいときも、設定ファイルで指定します。デフォルトは以下のとおり。

mda = "/usr/bin/procmail -d %T"

まとめ

 esmtpを使うと、常駐デーモンなしでローカルのメールを配送できます。ただ、aliasesは欲しいところです。

GREE Labs勉強会でPlan9の話を聞いてきた

 GREEで開催されている「オープンソーステクノロジー勉強会」で、Plan9の話をやるというので聞いてきました。GREEとPlan9という組み合わせはちょっと不思議でしたが、GoogleのGo言語からの流れなのかもしれません。

 以下、メモ。

Plan9のお話:高野(@oraccha)さん

 産総研の高野さんが、Plan9について解説しました。Plan9は、オリジナルUnixを作った人たちがその後に開発したOSで、「UnixよりUnixらしい」のを目指しているそうです。ちなみに、開発メンバーのほとんどはGoogleに行って、Go言語とかを作ってるようです。

 Plan9の特徴は「すべてがファイル」であること。ネットワークやウィンドウシステムなどもprocfsやsysfsのようにファイルとして見えるそうです。ただし、ネットワークアドレスや共有メモリなどは例外だそうです。

 なので、/dev/screenを読めば画面全体のスクリーンショットがとれるし、/dev/windowを読めばそのプロセスのウィンドウのスクリーンショットがとれる、というのを実演していました。ファイルの名前空間はプロセスごとに分かれているので、/dev/windowがそのプロセスごとに違うそうです。

 ネットワークも同じで、コネクション1つがディレクトリとして見えて、その中にdata(データの読み書き)とかstatus(状態)とかremote(接続先のIPアドレスとポートが読める)とかが見えてました。接続するときは、/net/tcp/cloneに書き込むと新しいコネクションが作られるそうです。

> cat > /net/tcp/clone
connect 192.168.0.1:80

 そのため、ネットワーク用のシステムコールはなく、通常のopen、write、closeを使うとのこと。ioctl()もなくて、listen()でブロックする仕様。ただ、ラッパーとしてCライブラリも用意されていて、GoのGoのdial()はそのライブラリ由来だとか。

 など、高野さんは、「Unixカーネル=I/O多重化装置」「Plan9カーネル=サービスの多重化装置」とまとめていました。

 以下、細かいネタ。

  • CPUサーバー、ファイルサーバー、ターミナルなどから成る分散環境を想定
  • 共通する9Pプロトコルで通信
  • 最近はスタンドアロン版も
    • ターミナル+fossil(ファイルサーバー)
      • ファイルの内容のSHA1がIDに
  • プロセスのリターンバリューは整数じゃなくて文字列
    • (emasaka注:シグナルも整数じゃなくて文字列らしい。そのメモリは誰が管理するのか、聞こうと思って聞きそびれた)
  • Unixで動くPlan9環境
    • Plan9port:Plan9コマンドセット
    • 9vx:Plan9カーネルをUnixのユーザープロセスとして
    • Glandix:Linuxカーネル拡張
    • drawterm:UnixからCPUサーバーにグラフィカルに接続
  • Plan9でman emacsすると、「それはバグである」と出てくるw
  • ヘッダファイルの中にリンク用のpragmaが入っている
  • 環境変数も/envで参照
  • 実行プログラムの入ったディレクトリはみな/binにユニオンマウントされている
  • コアダンプしないで、フリーズした状態(broken)でメモリに残る
    • 閾値で掃除される
  • リモートデバッグも/procをimportするだけ
  • createシステムコールw
  • ダイナミックリンクがない、すべてスタティックリンク
  • Blue GeneでもPlan9が動いてる
  • SheevaPlugでPlan9が動いた!

 質疑応答で、Plan9にはselect相当のものやノンブロッキングreadがないらしいけど、どうやってネットワークプログラミングをするのでしょうか、と質問してみました。質問がわかりづらかったようですが、懇親会で高野さんと話したところによると、selectがなくてすべてマルチスレッドで処理するモデルという回答でした。Limbo/Infernoのスレッドは並行処理ではなくてコルーチンのようなものだけど、I/O待ちでコンテキストが切り替わるので、そのあたりをうまく処理できる、のだそうです。スレッドやGoroutineは相互にChannelの読み書きで通信するので、そのあたりもコンテキストの制御モデルに合っているようです。

軽快なPlan9:斎藤(@go_vm)さん

 筑波大の斉藤さんは、「みなさん今日帰ってPlan9をインストールすると思いますがw」という言葉を枕に、仮想化ハイパーバイザーのKVM上でPlan9を動かした話を紹介しました。

 前半は、同人誌「軽快なPlan9」の話。研究室でPlan9が流行って、なぜかGlendaのぬいぐるみを作った人もいたとか。で、ビッグサイトのイベントに出店したところ、3時間すこしで50部完売したそうです。若い人よりはおじさんが興味を持ったようで、「マネージャーに買ってこいと言われたので…」という謎のお客さんもいたとか。

 後半はその内容の紹介。実はPlan9というよりKVMのほうを中心としたネタだそうです。KVM上でPlan9を動かしてみたけどネットワークが使えない。そこで、ホストOSに通信処理を丸投げするようにKVMを改造したのだそうです。具体的には、ゲストマシン側でCPUのVMCALL命令を実行してKVMに制御を移し、ホストマシンのコンテキストでソケットを開いて、そのデータをゲストマシンに書き込む、という荒技だそうです。

 名付けて「VM床抜き」。これでPlan9からインターネットにアクセスして操作方法を調べられる、というオチで締めました。

「インターネット新世代」

インターネット新世代 (岩波新書)
村井 純
岩波書店
売り上げランキング: 527

 村井純さんが、いまのインターネットの主にインフラ面で動いていることを、教科書的に総括している本。村井さんのビジョンってぶれてないなあ、とか、村井さんのビジョンに世の中が追いついてきたのかな、とかそんな感想。

 中国がらみの話は、ちょっと考えさせられた。

  • 中国では信用の問題で通販が根付かなかったが、アリババがデポジットによる信用の仕組みを作って躍進した
  • ファーウェイが通信機器で世界の50%超のシェアを持つようになった
  • 中国がインターネット利用者数で日本やアメリカを抜いた

「純潔のマリア」

純潔のマリア 1 (アフタヌーンKC)
石川 雅之
講談社 (2010-02-05)

 面白いな。「カタリベ」みたく主人公が空回りするストーリーになると、より自分の好みかも。

関数型言語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は関数型言語です(キリッ

 | HOME | 

Categories

Recent Entries

Recent Comments

Recent Trackbacks

Appendix

emasaka

emasaka

フリーター。
連絡先はこのへん

Monthly