いものやま。

雑多な知識の寄せ集め

『正義の教室』を読んでみた。

飲茶さんの新刊、『正義の教室』を読んでみたので、その感想とか。

正義の教室 善く生きるための哲学入門

正義の教室 善く生きるための哲学入門

「正義」って何だろう?

この本でまず驚いたのが、小説だということ

哲学の入門書なので、飲茶さんがこれまで出してきたような解説本を想像していたのだけど、いざ読み始めてみたら、なんと小説。
しかも、本題に入るための小噺だけ物語になってるとかではなくて、哲学の説明まで含めて、すべて登場人物たちによって語られ、描かれている。
これにはホント驚かされた。
しかも、物語が普通に面白いw

飲茶さん、多才だなぁ・・・

そんな、小説を通して語られ、説明されているテーマが、「正義」。
「正義」って何なんだろうね、ということ。

これに関して、本書では「正義には3つの判断基準がある」としている。
すなわち、

という3つの立場を挙げ、それぞれについて説明し、また、問題点の指摘も行っている。

これらの説明はさすが飲茶さんで、とても分かりやすい。
それに、それぞれの立場を体現したヒロインたちを登場させてストーリーを展開しているので、面白く読み進められるし、共感もしやすい。
ぜひとも実際に本を手にとって読んでもらいたいところ。

・・・が、ぶっちゃけ、そんなのどうでもいいんだよ!

というのが、この本を最高にファンキーにして、めちゃくちゃ面白くしている部分w

そう、哲学書に教養を求める人ならば、功利主義だとか自由主義だとか、そういった知識を得ることが重要になるんだろうけど、じゃあ、そういった知識を得たからといって、何になるんだ、と。

「トロッコ問題」のような理不尽な状況に実際に置かれてしまったとしたら、そういった知識がなんか役に立つのか?
クソの役にも立たないだろ!!!

実際、飲茶さんは本書を書いた動機として、以下のようなツイートをしている:

めっちゃ分かるw
理不尽でしかないもんね。
物語内では、この理不尽さにヒロインの一人が苦しめられたり。

そして、本書で説明されている3つの立場も、それぞれ問題点が指摘され、ことごとく論破されている。

じゃあ、「正義」なんてものはないのか・・・?

いや、「正義」はあるよ!!!

そう強く主張しているのが、本書。

ロッコ問題という状況で、どんな「行動」が正義と思えるかではなく、どんな「人間」が正義だと思えるか。
(中略)
人間は、完全な正義を直観できないし、知りようもない。
それはどうしようもない現実だ。
でも、そんな何が正しいかもわからない世界の中でも、それでも「正しくありたい」と願い、自分の正しさに不安を覚えながらも「善いこと」を目指して生きていくことはできる。
きっと僕たちにはそれで十分でーーむしろ、それこそが人間にとって唯一可能な正義なんじゃないだろうか。
(『正義の教室』より引用)

つまり、大切なのは「内容」ではなく、「在り方」だということ。

結局のところ、功利主義自由主義直観主義も、「これが正義である」という「内容」に関する立場の違いでしかない。
なので、そんなのは「どうでもいい」となる。
そうではなく、「自分はどうやって生きていくのか」という「在り方」こそが重要というわけだ。

そんな主人公の最後の演説シーンで語られる内容は、まさにニーチェの「超人」を体現するようなものになっていて、とてもよかった。

そして、衝撃のラストw

いやまぁ、途中で予想はついたんだけど、まさかホントにこれをやるとはw
『史上最強の哲学入門』の東洋編で、頭で分かってるだけじゃダメで、リアルな体験をもって理解しないといけない、と書かれている通りなんだけどw

今日はここまで!

正義の教室 善く生きるための哲学入門

正義の教室 善く生きるための哲学入門

技術書典7 はじめてのサークル参加meetupに行ってきた。

f:id:yamaimo0625:20190621155528j:plain

はい、というわけで、技術書典7にサークル参加で申し込んだ。
当選したら、技術書生やす!

技術書典については、以下の公式ページや、自分が一般参加したときのレポートを参照:

はじめてのサークル参加meetup

今回行ってきたのは、以下のイベント:

技術書典にサークル参加しようと決めたはいいものの、分からないことがいろいろあったからね。
特に、今回はオフセット本にチャレンジしてみようと思っているのだけど、オフセット本は作ったことがないので、そのあたりの疑問を解決できればな、と。

ちなみに、このブログでも何度か紹介してるけど、同人誌自体は一応作ったことがある。
技術書でもないし、コピー本を凄くしたようなものなので、普通の人が想像する「同人誌」と呼んでいいのか迷って、「どきどき初参加」枠で申し込んだけど・・・

自分が作った同人誌に関しては、以下の記事から:

PDF版をBOOTHでも販売しているので、興味があれば、ぜひ。

イベントは、わかめさんひつじさんがスピーカーとなって、質疑応答を交えつつ、そも同人誌ってなんじゃらほい、というところから、その製作方法やスケジュール感、執筆での注意点や当日の様子などについての話があった。
以下は、その中で特に自分が参考になったことのまとめ。

スケジュール感

まずはスケジュール感から。

同人誌を作る場合、原稿を書いて、ハイおしまい、というわけにはいかない。
特に、印刷所を使ってオフセット本を作るとなると、その入稿の締め切りも考えないといけない。

大まかな流れは、以下の3つのステージになる:

  1. 執筆前:計画、企画
  2. 執筆 :執筆、編集
  3. 執筆後:印刷、広報、頒布

まず、計画、企画では、各工程の期日を決めて線を引いたり、書く本の内容を決めていく。

次に、執筆ではもくもくと原稿を書く。
執筆はだいたい1ヶ月を目安とするみたい。
また、レビューや校正も必要。
それと、本としてまとめるために、表紙を作ったりレイアウト・スタイルを整えたりといった編集も行なっていくことになる。

最後に、印刷所に入稿して、広報活動と、当日の頒布。
告知は積極的にやった方がいいとのこと。

上記を踏まえると、以下のような感じになる:

時期 やること
7月上旬 当落結果発表、参加費支払い、計画・企画
7月〜8月 この中で1ヶ月くらいとって執筆し、編集、レビュー、校正
9月上旬 入稿、広報活動
9月22日 イベント当日!

以下の手順でカレンダーを取り込むことも出来るみたい:

執筆環境

さて、いざ執筆しようと思ったら、執筆環境が必要なわけだけど、そこで挙がるのがRe:VIEWとかMarkdownとか。
(あるいは、 \TeXで頑張るとか)

普段、Markdownで文章を書くのに慣れているので、正直、書式が違うRe:VIEWを使うのは微妙かなぁ、とも思っていたんだけど、いくつか追加で考慮すべき点があった。

まず、Markdownだと脚注を書くのが難しいよ、という話。
実際のところ、はてなブログだと拡張が入ってて、脚注も書けるようになっているのだけど(Markdownで書いてみた。(まとめ) - いものやま。も参照)、その拡張が使えるとは限らない。
それに、この拡張の例からも分かるとおり、Markdownには方言もけっこう多いので、そのあたりも悩みのタネになる可能性も。
その点、Re:VIEWだと脚注も問題なく使える。
また、マークアップ言語なので拡張もしやすいとのこと。

あと、Re:VIEWは実際に同人誌製作に使われた実績も多いので、印刷まわりで安心できる、というのも大きい感じがする。
例えば、印刷所によっては全ページにノンブルを入れる必要があるけど、Re:VIEWだとその辺もうまくやってくれるとのこと。

う〜ん、悩ましい。
今回は \TeXの勉強も兼ねて \TeXでやろうと考えていたのだけど・・・
Re:VIEWを使う方が無難な気もする(^^;

執筆のアドバイス

執筆に関してもいくつかアドバイスがあった。

まず、書きたい内容に絞って書いた方がいいよ、という話。
説明を書いてると、どうしても「あれも説明しなきゃ」「これも書いておかないと理解してもらえないかも・・・」となっていきがち。
けど、それで書きたい内容が書けなかったら、本末転倒。
本題に入る前に力尽きたり、あるいは最悪、原稿を落としたりしたら、悲しすぎる・・・
なので、まずは書きたいところから書いていき、レビューをしてもらって「ここらへん、説明が足りないからよく分からない」と指摘されてから説明を継ぎ足すくらいでもいいんじゃないか、とのこと。

あと、書いてるときはバックスペース禁止というルールでやるのもいいっぽい。
バックスペースを許すと、書きつつ推敲を始めてしまって、いつまでたっても文章が完成しないことがあるから。
これ、めっちゃ分かる・・・
(書いてるときにどうしても推敲してしまうので、めちゃくちゃ遅筆・・・)

それと、校正に関しては、余裕がない状態でtypoを見つけたりなんか出来ないので、余裕を持ってやるようにねとのこと。
確かに・・・
あと、実際に紙に印刷した方が間違えを見つけやすい人も多いので、実際に印刷してみるといいかも、とのこと。
実際、自分も『哲学散歩道』の校正をやるときは紙に印刷してやったので、その通りだと思う。

他には、爆死しないためには、表紙の分かりやすさが大切、という話もあった。
たくさんの本があるので、まず表紙で目がつくかどうかが重要になる。
そして、その本が自分を対象としたものでない(not for me)と思われてしまったら、手にとってもらえさえしない。
なので、表紙で何について書かれた本なのかや、扱っている内容、あるいはレベル感が分かるようになっていた方がいいとのこと。

サイズや部数をどうするか

実際に同人誌を作るときに悩ましいのが、本のサイズをどうするか。
A5にするのか、B5にするのか・・・

ちなみに、A5でもB5でも、1ページに入る文字数はそれほど変わらないらしい。
これは、A5の場合は文字サイズも少し小さくすることが多いから。

一般に多いサイズはB5。
コードを載せる場合には、B5の方がプログラムのリストが見やすいという利点があるっぽい。

一方、A5だと、気軽な読み物という雰囲気を出しやすい感じがあるっぽい。
B5に比べて小さいので、軽いし。
あと、個人的には表紙のデザインがやりやすそうな気はしてる。
(面が大きい方がイラストや写真も大きいものが必要になるし、ダサいスキマを作らないデザインにするのはなかなか難しい)

あと、何部刷るのかというのもなかなか悩ましい問題。

データによれば、技術書典だと新刊が平均150部くらい出る感じらしい。
機会損失をしたくなければ、それより多めに刷っていた方がいいし、完売するというのはかなり楽しいので、少なめに刷るというのもありみたい。
なお、少なめに刷った場合、電書版のQRコードを名刺などに印刷して用意しておけば、機会損失もだいぶ抑えられるので、そういうのもあり。

ただし、注意点として、技術書典は部数に関して例外的で、他のイベントで同じような部数は刷らないように、とのこと。
在庫を大量に抱えて、悲しいことになるっぽい・・・

部数に関しては、個人的に気になっていたことも聞いてみた。
それは、同人誌の束はどれくらいのサイズ(分厚さ)になるのか、ということ。
運搬するときや、在庫が残ってしまったときには、そのサイズが重要になってくるので。

これは、同人誌100冊で、500枚入りコピー用紙の束が5つ入ったダンボールくらい、という回答をもらって、すごくイメージしやすかった。

実際、50ページの同人誌を考えてみると、1冊あたりの紙は25枚で、それが100冊となると2500枚となり、これはコピー用紙500枚x5束の2500枚と計算があう。

製本サービス

他、面白い情報として、製本サービスの話があった。

セブンイレブンのマルチコピー機はかなり優秀らしいので、コピー本を作ったり、試し刷りをしたりするのに使えるっぽい。

あと、製本直送.comというサービスもあるとのこと。

電書版のみ頒布するという場合も、見本誌があった方がいいので、そういう場合にはこういった製本サービスを使用して見本誌を用意するといいみたい。

なお、紙の本を出版する場合も、「これが見本誌」と分かるようにして見本誌を用意しておいた方が、手にとってもらいやすいみたい。
具体的には、ビニールのカバーなどをかけて「見本」とタグを貼っておくとか。
言われてみればなるほどという感じ。

印刷所とのやりとり

今回、初めてオフセット本を作ろうと思っているので、そこで気になっていたのが、問題なく印刷してもらえるのかどうかということ。
いざ印刷されたものが届いてみたら、思わぬ乱丁があったりレイアウトが崩れたりなんかしていたら、目も当てられない・・・

これに関しては、入稿後に2, 3回くらい印刷所とやりとりすることになるっぽい。
これだと印刷に問題が出そうだけど、大丈夫か、とか。
そういうのがあるなら、安心かな・・・?
もちろん、指摘に対応するだけの余裕を持って入稿する必要があるわけだけど。

ちなみに、依頼しておけば見本誌を送ってもらうとかも可能っぽい。
ただし、これは印刷されたものが送られてくるだけで、それを見て原稿を手直ししたり出来るわけではないようなので、注意。
もし、ちゃんと印刷されるかなどを確認したいなら、前述の製本サービスを使って試し刷りをして、入稿に備える感じになるみたい。

あと、わかめさんが以下のツイートをしていたので、それも参考になりそう:

かんたん後払い

あとは、技術書典特有のものといえば、「かんたん後払い」。

これは、コードを専用アプリで読み取ることで購入手続きを完了させ、あとで支払いを行えるようにする、というサービスなのだけど、サークル参加するとなると、サークル側で何か特別な準備は必要なのかどうかが気になっていた。
例えば、何か特別な機械を用意したりだとか。

結論から言えば、そういった特別な機械とかは必要なくて、規約を読んで申し込みをすればいいだけみたい。
そうすると、当日にコードが印刷された紙が渡されるので、あとはそれを専用アプリで読んでもらえばいいだけっぽい。

ただ、注意点として、現金ほどスループットは出ないという話があった。
なので、お釣りの用意はしっかりとね、とのこと。


ちなみに、作ろうとしている技術書は、以前このブログで書いたポーカーの記事を膨らませたものにしようと思っている。
技術者なら、やっぱりゲームもガチで数理モデルを使って勝ちに行きたいでしょ?w

上の記事で導き出したインプライド・ポットオッズの話は、とても興味深い(そして強化学習のモデルのいい例にもなってる)ものなので、多くの人に知ってもらいたいし、他にもいくつか調べて文章にしたいネタがあるので。

もちろん、ポーカー(テキサスホールデム)やったことないという人も多いと思うので、そこらへんも出来ればカバーする感じで。
(ただ、それで書きたいことが書けなくなったら本末転倒なので、省略するかも)

ポーカーを覚え、数理モデルを駆使して、狙え一攫千金!ってねw

今日はここまで!

「Lucid, the Dataflow Programming Language」を翻訳してみた。(その12)

前回の続き。

2.7 遅延評価

しかし、Iswimの操作的(動的)解釈として、より適切なものがある。
この方法論では、プログラムは実行されると考えるのではなく、プログラムは評価されると考え、その評価は必要に応じて行われるとする。
この評価の方法は、必要以上には評価を行わない(=必要になったときに必要なだけ評価を行う)ので、「遅延」と呼ばれる。
特に、関数呼び出しの実引数は自動的には評価されない。
この遅延評価の方法は、数学的意味論により忠実となっている。
実際、ちょっとした注意を払うことで、それらは完全に正しいものになり、形式的意味論が指定するものを正確に生成する。

これらのうちの最初のものは、「要求駆動(demand-driven)」解釈と呼ばれるかもしれない。
この方法では、プログラムの一部(最初はプログラム全体)の値に対する要求が、部分式の値に対する要求を生成する。
要求は感染症のようにプログラム全体に伝播していき、必要とされた値が要求元へと流れ返っていく。
2つの部分式 E_1 E_2の合計 E_1 + E_2を評価するために、評価マシンは E_1 E_2を(おそらく並行して)評価し、それらの結果を加算する。
個々の定数からなる式を評価するために、評価機は代数によって定数に割り当てられた値を返す。
個々の変数からなる式を評価するために、評価機は変数の適切な定義を検索し、その定義の右辺を評価する。
対象の変数が関数定義の仮引数である場合、対応する実引数を探し出してきて、それを評価する。
評価する式が if  C then  E_1 else  E_2 fiという形式であれば、まず Cを評価して、その真偽値にしたがって、 E_1 E_2のいずれかを評価する。
最後に、(例えば) f(E_1, E_2)という形式の式を評価するなら、 fの定義を見つけ出し、その右辺を評価する。

もちろん、要求駆動型の評価は、上記のざっくりした説明が示すよりも、もう少し複雑である。
それにもかかわらず、実装することはそれほど困難というわけではなく、完全に正しい。
(付録のマニュアルに記述されているpLucidインタプリタは、要求駆動の評価スキームを使用している;そして、Iswim( P)はpLucidの部分言語である)
(※データフロー言語の実装を考えるとき、pull型(シンク側から要求を出し、それに応じてソース側が計算を行う)とpush型(ソース側で計算を行い、結果をシンク側に流してシンク側を駆動させる)を考えることができるけど、この要求主導というのはpull型だという主張。実際の実装がそうなっているのかは未確認・・・)
要求駆動型のモデルは、プログラミングを助けるうえで、適切なものとなっている。
要求駆動型の観点では、定義が実行可能な順序で現れてくる必要はない。
実際、この方法だとプログラムを書くときによりトップダウンな形で書くことが出来る。
まず、欲しい出力の値となる式を書く(そこで使われる変数や関数の定義は書かない)。
この式はwhere節の主題になる。
次に、where節の本体に、必要に応じて主題で使われる変数の定義を書いていく。
したがって、要求駆動のインタプリタは、書かれたのと同じ順序で定義を進めていく(これはインタプリタにとって特別な助けになるというわけではない)。
この「必要に応じて定義する」アプローチは、プログラミングのスタイルをずっと良くし、Iswimの定義型の精神により合っている。

Iswimプログラムを実装する非逐次的な別の方法もある。
そのアイディアは、第7章で議論されるプログラム操作の規則を使用するもので、ソースプログラムを徐々に出力に変換していくものである。
変換方法は、プログラムの定義を書き換え規則として使用する。
実行する簡約(reduction)を選択する戦略が慎重に定義されている場合、実装は必要とされない式の値を評価しようとはしない。

この方法(簡約計算)は非常にシンプルであるが、プログラミングの助けとして無制限に推薦することは出来ない。
中間表現は非常に大きく、複雑になり、元のプログラムとの関係がほとんどなくなる。
この方法は主に構文的であり(※ここでの「構文的」というのは、記号操作的、という意味合いだと思う)、構文に関して排他的に考えるプログラマは、Iswimの意味論を理解するという利点を失う。
プログラマは、関数定義を「関数」ーーすなわち、データを変換する「デバイス」ーーの定義と考えるべきである。
定義をテキストを変換する書き換えルールと考えることはお勧めしない。
記号操作というより、計算であると考えたほうがいい。
(※ラムダ計算の理論では、定義による置換を行うことで計算が出来るとしていて、その置換の戦略によって遅延評価かそうでないかが変わってくる。ここで述べているのはその簡約による方法)

これらの代替操作的モデルは、どちらも \perpの意味をそれぞれの方法で大事にしている。 例えば、次のような定義を考えてみる:

X = X + 1;

要求駆動のインタプリタは、 Xの値に対する要求が Xに対する別の要求に直接つながるため、(要求が)無限に循環することになる。
そして簡約の実装は、 X X + 1に無限に置き換えることで、以下の系列を生み出すことになる:

 X +1, (X +1)+1, ((X +1)+1)+1, \cdots

どちらの場合も、計算は決して終わることなく、これはまさに \perpに対応する操作的な活動になっている。

最後に、プログラマは動的モデルをまったく使用しないことを望むかもしれないということを指摘しておく。
Iswimの最大の強みは、公式での意味論が厳密に静的であり、よく理解されている関数の概念を使用していることである。
プログラマがプログラムの効率(性能)を心配していない場合、実際の操作的な概念を考える必要はまったくない。
プログラマがもっぱら正しさにだけ関心を持つ場合、静的な意味論はプログラムの出力を完全に指定するので、静的な意味論だけで十分である。

しかし、一般的に、プログラマは性能に関する質問を無視できない(そうでなければ、プログラムはなく、仕様のみがあることになる)。
プログラマは、いくつかの操作上の観点から思考し、それを実行するのに必要なリソースの量を減らすようにプログラムを作らなければならない。
しかしこれは、プログラマは実装のあらゆる細かい部分について心配する必要があるだとか、実装のあらゆる部分の動作について理解している必要があるだとか、そういうことではない。
とても実用的なプログラミングでさえ、純粋に宣言的に理解されている部分があるだろう。
さらに、最も実用的で効率的なプログラムでさえ、その正しさは、表示的な(静的な)意味にのみ依存し、操作的なアイディアを参照することなく考えることが出来る。
「抽象性」という用語は、本質的に表現または実装の問題から独立であることを意味する。
したがって、Iswimの形式的で表示的な意味論は抽象的な意味論であり、この言語を使用したいと思う人であれば誰でも理解できるものである。


ということで、遅延評価に関して。

この節に書かれてることはとても興味深くて、遅延評価といえばHaskellが有名なわけだけど、Haskellが遅延評価を実現している方法はまさにこの節で書かれている2番目の方法(簡約による方法)で、簡約の戦略を非正格にすることで評価が遅延するようにしている。
一方で、pLucidはそれとは別の方法で遅延評価を実現していると言っていて、それが1番目に書かれた要求駆動による方法。
データフローの視点で考えることで、1番目の方法が出てくるのはとても面白い。

また、データフロープログラミングの後継と言われることの多いリアクティブプログラミングだと、(オブザーバ・パターンによる)push型の実装が多い印象なんだけど、pLucidはそうではなく、pull型の実装になっている、と。
確かに、push型だと遅延評価は無理だけど、pull型なら実装はWorkStealingアルゴリズムみたいに関数呼び出しを一つのタスクとしてスタックにプッシュする感じになりそうなので、遅延評価になりそう。
いやー、なるほどという感じ。

今日はここまで!

「Lucid, the Dataflow Programming Language」を翻訳してみた。(その11)

前回の続き。

2.6 Iswimでの呼び出しと束縛

手続き的解釈の適用には注意が必要である。
というのも、そうでなければ、スコープ規則に関して間違いが生じる可能性があるからだ。
特に、グローバル変数を含む関数定義の正しい解釈について注意する必要がある。

関数定義の右辺には、仮引数ではなく、右辺の中で関連する定義がない変数が出現する場合がある。
以下の B Cのように使用される変数は、関数定義のグローバル(グローバル変数)と呼ばれる:

f(X, Y) = X + B * Y + C;

グローバル変数を伴った「手続き定義」を言語が許可するときは、グローバルが意味(=値)を持つコンテキストでの関数呼び出しの実行において、グローバルの意味(=値)に微妙な疑問が生じる。
たとえば、

f(3, U)
where
    B = 7;
    C = 5;
    f(X, Y) = X + B * Y + C;
    U = P - Q
    where
        Q = 2;
        B = 3;
        P = f(B, Q);
    end
end

という(where節の)内側での fの「呼び出し」について考えてみる。
この結果はどうあるべきだろうか?
 Pの値は何になるだろうか?

上記のIswimの「逐次的な」解釈に単純に従えば、単に X Yに3と2を割り当て、 X + B * Y + Cという式を評価するだけである。
ところで、評価を開始するとき、 Bの値は何になっているのだろうか?
変数 Bには、この呼び出しが実行される直前に3が割り当てられている。
 Bの値はそのまま3であり、したがって呼び出しの結果が 3 + 3 * 2 + 5、すなわち14であると予想するのは論理的だろう。
一方、(外側のwhere節の)主題で fの呼び出しが実行されると、(外側のwhere節の)本体の最初の文の実行後に Bの値は7になっていることが予想される。
これを少し計算すると、最終的に得られる値は92であると予想される。
(※ f(3, U) = 3 + 7 * (14 - 2) + 5 = 92

しかし、この予想は間違っている。
Iswimの形式的意味論は、この節の値が148であることを示す。
これは、内側の fの呼び出し中に Bが(3ではなく)7を割り当てられていると仮定すると、得られる値である。
(※ P = 3 + 7 * 2 + 5 = 22なので、 f(3, U) = 3 + 7 * (22 - 2) + 5 = 148

この例は、Iswimで従来の手続き的な解釈を用いるときの1つの落とし穴を示している。
Iswimの形式的意味論は、言語が「静的束縛(static binding)」と呼ばれるものを持つことを意味していて、すなわち、グローバル変数は関数が呼び出される環境ではなく、定義されている環境(またはコンテキスト)から値を取得している。
上記の関数 fは、グローバル Bが7と定義されている節で定義されているので、関数がどこで使用されていても Bの値は7となる。
PASCALやALGOLのような本来の命令型言語も静的束縛を持っているにもかかわらず、静的束縛はIswimの逐次的な命令的解釈の観点からはむしろ理解しにくい場合がある。
(※この例の場合、命令型言語であれば、変数 Bをローカルな変数として「明示的に」定義することでグローバルな Bを隠してしまうようにすれば(シャドーイング)、グローバルな Bは(実質的に)静的に束縛されている状態になるわけだけど、Iswimの文を命令型言語と同じように見ると、ローカルを定義しているのではなく、定義済みのグローバルに値を再割り当てしているように見えるので、このような間違いを起こしやすい、ということ)

一方、純粋に宣言的な方法でIswimを解釈し、その定義を任意の仮引数に対して真となる方程式と考えると、静的束縛は完全に自然なものとなる。
以下の定義を考えてみる:

C = 5;
B = 7;
f(X, Y) = X + B * Y + C;

最初の定義は Cが5であり、2つ目の定義は Bが7であることを述べている。
そして、3つ目の定義は、 X Yにどんな値がこようとも、 f(X, Y)の値が X + B * Y + Cであることを述べている。
しかし、 B Cの値がそれぞれ5と7であることは既に分かっているので、 f(X, Y)の値は X Yの値が何であっても X + 7 * Y + 5であると考えることが出来る。

Iswimの宣言的な見方は、Iswimの形式的意味にとても近いので、正しい答えを導き出す。
Iswimの解釈は、値を個々の変数に、関数を関数変数に関連付ける。
Iswimの割り当て/呼び出しモデル(=手続き的な解釈)は、(関数ではなく)「手続き本体」を関数変数に関連付けている(ように考える)ので、誤解を招く。
手続き本体は、グローバル変数の出現を含むことができるテキスト(式)であるが、実際の数学の関数はテキストではなく(それは値の集合間の対応)、「グローバル変数」を持つことは出来ない。

Iswimの命令的解釈には、関数について誤解を招く可能性がある別の微妙な問題がある。
以下のプログラムを考えてみる:

f(3, 4)
where
    f(X, Y) = if X < 1 then 0 else f(x - 1, f(X, Y + 1) fi;
end

この出力はどうなるだろうか?
形式的意味論(および宣言的解釈)であれば、答えは0でなければならない。
変数 fは、常に0を返す2変数の関数として定義されていて、それがこの方程式を満たす唯一の解である。
(※第2引数がなんであれ、第1引数は常に値が減っていくので、どこかで X \lt 1となるため)
しかし、命令的解釈では、 fの呼び出しがいつまでたっても終わらず、プログラムの出力は \perpになると指し示されてしまうだろう。
問題は、命令的解釈に従えば、 f(3, 4)の呼び出しによって f(3, 5)が呼び出され、それはさらに f(3, 6)を呼び出し、 f(3, 7)の呼び出し、 f(3, 8)の呼び出し、と無限に続いてしまうことにある。
実際、このプログラムをPASCALで書き直すと、ずっと深いネストされた呼び出しを生成するので、実行は永遠に終わらない。

関数呼び出しに対する命令型のアプローチの問題は、結果が生成される前に、すべての実引数の値が手続き本体で実際に必要とされることを前提としていることである。
命令型の方法は、手続き本体を実行する前に、シンプルにそれらの値(=実引数の値)をすべて計算する。
実際、結果を出すのに仮引数の値をすべて使わなくてもいい関数を定義できることは、とても有益であるーーたとえ、特定の結果を出すのに、引数のどれが必要となるかを前もって正確に言うことは出来ないとしても。
例えば、PASCALプログラマが言語にif-then-else式(※C言語の3項演算子?:と考えればいいはず)がないのに嘆いて、そのギャップを以下のように定義されたcond関数で埋めようとしたとする:

function cond(P : bool; X, Y : integer) : integer;
begin
    if P then cond := X else cond := Y
end;

そして、階乗の関数を(ローカル変数への)割り当てなしで次のように記述したとしよう:

function fac(n : integer) : integer;
begin
    fac := cond(n < 1, 1, n * fac(n - 1))
end

この目論見はうまくいかず、facの呼び出しは何も値を返さない。
なぜなら、cond関数を呼び出す前に、n * fac(n - 1)を評価しようとするので、nの値によって処理が分岐せず、facの呼び出しが無限に続いてしまうからだ。(※ここ、原文のままだと意味が通じないと思ったので、文を書き変えた)

しかし、Iswimでは、このアイディアは機能する。
すなわち、ユーザは自身で選択関数を定義することが出来る。
以下のプログラムの出力は、形式的意味論に従えば、6となる:

fac(3)
where
    fac(n) = cond(n<1, 1, n * fac(n-1))
    where
        cond(p, x, y) = if p then x else y fi;
    end
end

その理由は、cond(p, x, y)の値は常にif p then x else y fiの値と同じだからであるーーたとえyの値が \perpであったとしてもーー言い換えれば、たとえyの計算が永遠に終わらないものであったとしても。
もちろん、命令的な観点だと、上記のプログラムが決して値を返さないことを示唆することになる。
その素朴な見方(=命令的な観点)は、単に間違っていることになる。

以上から、Iswim( Z)の逐次的な命令的観点は不適切であり、(素朴に従うのだとしても)間違ってさえいるというのは明らかである。
しかし、解釈を修正し、完全に正しいにも関わらずに従来通りであるような実装の基礎として使用することは可能である。
そのためには、グローバルの束縛が動的であることを保証する必要が出てくる(それはそこまで難しくない)。
しかし、関数の仮引数が(事前に評価される)「値」によってではなく、(必要に応じて評価される)「名前」によってであるようにすることも保証する必要がある。
これははるかに困難であるが、不可能ではないーーALGOLはオプションで「名前」による呼び出しが出来る。
それでも、命令的観点は、たとえそれを動かせることが出来たとしても、推薦されるべきではない。
それは単にIswimの「精神」とは違っている。


ということで、前回でIswimのプログラムを手続き型のように解釈する方法を説明したけど、その解釈で出てくる問題を指摘しているのが、今回の話。

まぁ、今の時代から見れば、変数の静的束縛はラムダ式でのキャプチャと考えればいいし、引数の評価のタイミングも、引数に関数呼び出しをベタに書くんじゃなくてラムダ式を書けば評価が遅延されるよね、とかあるけど。
もちろん、引数の式をどの順番で評価するかとかはコンパイラ実装依存となっているので、式に副作用があったりすると、微妙な問題を生み出したりするというのは正しいわけだけど。

そして、評価の遅延の話が出てきてるけど、それについては次の節が「遅延評価」のようなので、また次回に。

今日はここまで!

「Lucid, the Dataflow Programming Language」を翻訳してみた。(その10)

前回の続き。

2.4 サンプルプログラム

where節の本体が「補助定義」であるという非形式的な考え方に形式的意味論がどう一致しているかを説明するために、前に出てきた次の式を考えてみる:

C + A * Z
where
    A = 5;
    B = 5;
    C = Z * A + 1
    where
        A = 3;
        Z = A + W * B;
    end
end

 rをすべての変数にランク0を割り当てるランク付けとし、 Iを、 Zに9、 Wに7、他のすべての変数に0を割り当てる整数の r-解釈とする。
形式的意味論を用いて、 Iに関するこの式の値を決定してみる。

定義3.により、この式の値は本体の等式の「最小解」 Jに対する式 C + A * Zの値である。
 Jは、変数 A B Cにそれぞれ5、5、115が割り当てられることを除いて、 Iと同じ解釈でなければならないことは、簡単に分かる。
(※ C = Z * A + 1 = (A + W * B) * A + 1 = (3 + 7 * 5) * 3 + 1 = 115
 A B Cだけがこの節で定義された変数なので、他の変数についてはすべて、 Iで割り当てられた値に Jは一致しなければならない)。

 C + A * Zは、二項演算子 +とその引数 B A * Zからなっている。
次に、定義2.により、 Jに関する C + A * Zの値は、整数の +の解釈に2つの引数の値(つまりそれらの Jに対する値)を適用した結果である。
最初の項の値は、定義1.により、 J Cに割り当てている値、つまり115である。
2つ目の項の値は45と簡単に分かるので( J A Zにそれぞれ5と9を割り当てている)、最終結果は160である。

 Jが外側のwhere節の本体の3つの等式の解であることを確認するには、それぞれについて、 Jに対して左辺の値が右辺の値と同じであることを検証する必要がある。
最初の2つの等式については明らかだが、3つ目の等式については、 Jに関して内側のwhere節の値を計算する必要がある。

定義3.は、(内側のwhere節の)主題の式の Z * A + 1の値が、(内側のwhere節の)本体の中での定義の最小解 Kに対する値であることを示している。
 Kは、 A Zにそれぞれ3と38(=3 + 7 * 5)を割り当てていることを除いて、 Jと同じである。
したがって、(内側のwhere節の)主題の式の値は115である。

これにより、 J Cに115を割り当てることが分かるので、 Jは外側の節の等式の解であることが確認できる。
もちろん、これは Jが最小解であることを証明するものではないが、定義は非再帰的であり、解は唯一のものである。

自由(未定義)変数が単独の(=ランク0の)変数として使用されるため、上記の式は実際にはプログラムである。
以上から、このプログラムの出力は、入力が \{ (Z, 9), (W, 7) \}のとき(すなわち、 Zの入力値が9、 Wの入力値が7のとき)、160である 。

2.5 Iswimの操作的意味論

Iswimの形式的意味論は単純で正確である。
この「最小不動点(least fixed point)」という仕様は、実装が正しく、かつ完全であると判断するための標準として使用できる。
例えば、(6章で与えるような)ある変換規則が正しいことを証明するといった、言語の妥当性の判断にも使用できる。
しかしながら、形式的な定義は、プログラムの読み書きの良いガイドとはならず、また、言語を実装するのにも直接は使用できない。
幸いにも、Iswimファミリのほとんどのサブクラスは、比較的従来通りの概念を使用して理解、実装することが出来る。

「従来の」モデルは、変数の定義を代入文として解釈し、関数の定義を関数処理の宣言として解釈するという考え方に基づいている。
この解釈は、代数の世界(universe)が( Zのように)「通常の」有限オブジェクトに \perpを加えた集合である場合、適用可能である。

where節の本体の中での定義は、どのような順序であってもよいので、代入文のようにそれらを実行し、意味のある結果を得ることが出来るとは限らない。
しかし、Iswim( Z)と同様の言語では、再帰的に定義された変数は(ほとんどの場合)値が \perpとなるので、そのような定義は無意味である。
循環定義が許されないのであれば、右辺に出てくるすべての変数がすでに定義済みであるように定義を並べ替えることが出来るのは、容易に分かる。
(※変数間の参照関係を有向グラフで書いたとき、循環定義がなければツリー(もしくはフォレスト)になるので、リーフから並べていけばいい)
例えば、

X + Y
where
    C = A + B;
    X = C * C - D;
    D = A - B;
    Y = C + D * D;
end

という式では、本体を一連の代入文として解釈することは出来ない。
なぜなら、 Dの値は3つ目の文で計算されるため、2つ目の文を実行できないからだ。
しかし、次のように定義を並べ替えることが出来る:

X + Y
where
    C = A + B;
    D = A - B;
    X = C * C - D;
    Y = C + D * D;
end

こうすれば、4つの定義が順番に実行され、結果の「格納された」値を使用して主題の式が評価されると想像することが出来る。
評価後、ローカル変数( C D X Y)の値は元に戻さなければならない。

ここで、where節の本体の中で

f(X, Y) = P - Y
where
    Q = X + Y;
    R = X - Y;
    P = Q * R;
end

というような定義を持っていて、本体のどこか(上記の定義が関係する位置)で f(5, A - B)という形の式があるとする。
この定義を手続きの宣言と解釈し、式 f(5, A - B)をこの手続きの呼び出しとして考えたとき、その呼び出しはALGOLやPASCALと同様に実行されると想像することが出来る。
言い換えれば、仮引数 Xに5、 Y A Bの差が一時的に与えられ、 fの手続き本体( fの定義の右辺の式)が実行され、結果が呼び出しの結果として返され、仮引数の元の値が復元される。

「従来の」モデルは全体として、特殊なオブジェクト \perpの正しい解釈を与える。

例えば、次のプログラムを考えてみる:

f(3) where f(n) = n * f(n - 1); end

手続き的意味論をガイドとして用いると、このプログラムからは何の値も得られないと予想できる。
 f(3)の「呼び出し」は f(2)を呼び出し、それは f(1) f(0) f(-1) f(-2)と、無限の呼び出しを生じさせる。
これは手続き的意味論が失敗したように見えるかもしれないが(値が得られないため)、実際にはちゃんと動作する!
形式的意味論は、 fが常に \perpを返す関数であることを指し示し、そして、 \perpがプログラム全体の出力となる。
したがって、 \perpを「非停止(nontermination)」あるいは「出力なし」と解釈するならば、手続き的意味論は正しい。

これこそが \perpの真の意味である。
オブジェクト \perpは、永遠に実行され、決して何も返さないプログラムの「出力」である。
このオブジェクトは、動的な操作的動作の「非停止」に対応する静的な意味値である。
 \perpとの組み合わせに関するさまざまな規則はすべて、この解釈でとても自然に説明することが出来る。
例えば、3と \perpの合計が \perpであるという事実を考えてみる。
計算によって2つの値の加算が行われるが、最初の値の(部分)計算で3が得られるのに対し、2つ目の計算は結果を生み出さずに永久に実行されることになる。
したがって、(この計算は永遠に終わらないので)合計の「値」は \perpでなければならず、これは3と \perpの(拡張された意味での)合計と一致している。


ということで、前回の分かりにくかった説明の具体的な例と、それを操作的にはどう解釈するのかという話。

正直、前回の内容を理解していなかったときは、このサンプルプログラムも「?」な感じではあったのだけど(変数のスコープがちゃんと理解できていなかった)、理解すればそんなに難しくはないかな?
where節の主題の右辺のスコープがすでにローカル内だというのがちょっと間違えやすい気はするけど・・・

今日はここまで!

「Lucid, the Dataflow Programming Language」を翻訳してみた。(その9)

前回の続き。

2.2 Iswimの構文

Iswim( A)という言語の(抽象)構文(syntax)と意味論(semantics)は代数 Aによって完全に定まる。
Iswimファミリはシンプルであり、以下の数段落で合理的で正確な仕様を与えることが出来る。

代数 Aが与えられると、そのシグネチャはIswim( A)の構文(つまり構文的に正しいプログラムの集合)を決定する。
Iswim( A)のプログラムは、引数をとらない変数とシグネチャ Sの個体定数から構築される式である。
式は、シグネチャ Sの操作定数への適切な数の引数の適用と、関数への引数(実引数)のリストの適用、および、where節の形成によって構築される。
where節の中で定義された変数と関数は、節のローカルであると言われる。
構文で必要とされる制約は、次のことを保証するだけである:

  1. 節の各ローカルは、その節の中で1回だけ定義される。
  2. 節の各ローカルは、その節の中では、その定義と同じアリティで使用される。
    つまり、関数呼び出しの実引数の数は、最も内側の節の定義の仮引数の数と同じでなければならない。
    (特に、変数として定義されているローカルは、 その節で変数として使用される)
  3. 関数定義の仮引数は、互いに異なっていて、かつ、定義しようとしている関数とも異なること。
  4. 関数定義の仮引数は、定義の右辺で変数として使用される。
  5. シグネチャ Sの各操作定数は、 Sによって規定されたアリティで使用される。

これをより正確に書くと、以下のようになる。
任意のシグネチャ Sが与えられたとき、ランク付き S S-ranking)とは、 Sの操作定数にランクがあるのと同様に、すべての変数にランクを割り当てることによって Sを拡張したオブジェクトである。
シグネチャ Sと、 rというランク付き Sが与えられたとき、Iswimの r-式( r-expressin)とは、 rで定められたランクに一致するように定数と変数(関数も含む)が使用された式である。(where節の中で、変数(関数も含む)は再定義され、別のアリティ(=ランク)を持ちうる )

一般に、 r-式は、以下のいずれかである:

  1. 単独のシンボル、すなわち、 rでランクが0と割り当てられている定数もしくは変数
  2. 一連の実引数を伴った関数もしくは操作定数;ここで、 rでそのランクが nと割り当てられている場合、一連の実引数 F_0, F_1, \cdots , F_{n-1}はそれぞれ r-式
  3. 1つの r'-式(主題(subject))と r'-定義( r'-definitions)の集合(本体(body))からなるwhere節; r' Bで定義された変数(関数を含む)に割り当てられたランクで、 rとは異なる; Bでの定義は矛盾があってはいけない、すなわち、ある変数(関数を含む)の定義は、ただ一度だけされる

1つの r-定義は、定義される変数(関数を含む)と、一連の変数(仮引数)、および、 r'-式(右辺)で構成され、 r'は仮引数のランクに0が割り当てられるのを除いて、 rと同じランク付けとなっている。
仮引数は、定義される変数と異なるものでなければならない。
仮引数の長さは、定義される変数(関数を含む)が rで割り当てられているランクと同じでなければならない。

Iswim( A)のプログラムは、1つの r-式である;ここでいう r Aシグネチャのすべての変数にランク0を割り当てて拡張したランク付けである。
したがって、プログラムは自由(未定義)変数を持つことができるが、そういった変数は、関数としてではなく、単独のシンボルとしてだけ使用される。

2.3 Iswimの意味論

Iswimの意味論(semantics)は、構文よりもさらに簡単に定められる。
解釈(Interpretation)の概念が必要で、代数がシグネチャで示されるのと同じように、解釈はランク付け(ranking)によって示される。
シグネチャ Sと、 S-代数である代数 A、そして、ランク付き S rが与えられたとき、 r-解釈( r-interpretation)とは、定数(個体定数と操作定数)と同様に、変数や関数に意味(meaning)を与えることによって Aを拡張した数学的なオブジェクトであるーーそして、意味は rによって与えられているランクの情報と一致したものになっている。
解釈 Iが与えられると、 r Sを回復することが出来るので、「 Iシグネチャ」または「 Iの宇宙」について話すことは理にかなっている。(※ここはちょっとよく分からない)

シグネチャ Sを伴った代数 Aが与えられたとき、 r-解釈 Iに対するIswim( A)の r-式 Eの値は、以下のように定義される。

  1.  Eが単独の変数または定数 cの場合、 I対する Eの値は、 Iによって cに割り当てられた値
  2.  Eが、実引数[tex: F_0 , F_1 , \cdots , F{n-1}]を伴った、ランクが nの操作定数または関数 aである場合、 Iに対する Eの値は、[tex: g(v_0, v_1, \cdots , v{n-1})];ここでいう g Iによって aに割り当てられた宇宙 A上の演算であり、 v_iはそれぞれ Iに対する F_iの意味
  3.  Ewhere節で構成されている場合、 Iに対する Eの値は、最小の解釈 I'に対する Eの主題の値;ここでいう I'は、 Eの本体でのそれぞれの定義が I'に対して真となり、 Iと異なるのは Eの本体で定義されている変数に割り当てられた値だけであるような解釈

単独の変数の定義が環境に対して真となるのは、両辺で環境に対する値が等しくなる場合であり、また、そのときに限る。
言い換えれば、解釈によって単独の変数に割り当てられる値は、右辺の解釈に対する値である。
関数の定義が真となるのは、仮変数に任意の値を指定しても両辺が同じ値になる場合であり、また、そのときに限る。
言い換えれば、解釈によって仮引数の値が変わったとしても、両辺は同じ値を持つ。
(以下、原文を訳せなかったので、こういうことを言いたいはず、という内容)
解釈には順序があり、下の解釈(=ローカル、内側の解釈)で上の解釈(外側の解釈)を上書きする。
最小の解釈 I'は、一番内側の解釈である。

さて、代数 Zの世界が追加のオブジェクト \perpを持っていた理由を見ていこう。
Iswimのセマンティクスでは、解釈は半順序である必要があるため、節には最小の解釈がある。
解釈の順序は、 Zの宇宙での操作上の順序と、 Zの宇宙での順序( \perpが底にある)によって引き起こされる。(※何を言ってるのかよく分からない)
Iswimを(半順序と \perpを伴わない)従来の代数の概念に基づいて構築することは不可能であろう。
問題は、 f(x) = f(x)のような(正しい)等式がある1つ以上ある一方で、 f(x)= f(x)+ 1のような従来の代数で正しくない等式もあるということである。
しかし、オブジェクト \perpは、定義の作業を行うために追加された、単なる無意味な仕掛けであるというわけではない。
このオブジェクトはとてもシンプルな操作上の解釈を持っている。
それはすなわち、このオブジェクトは決して終了しない計算の「結果」を表している。
たとえば、以下で定義された関数

f(x) = if x eq 0 or x eq 1 then 1 else f (x - 1) + f (x - 2) fi;

は、(Iswimの意味論に従って)引数4を指定すると値5(4番目のフィボナッチ数)を返すが、引数として-1を指定すると \perpを返す。
これは、再帰に関する操作上の直感と一致している(以下を参照)。
上記の定義には、f(-1)の値を197にするようなものまで含む、多くの解決策がある。
しかし、もしIswimの実装がそのような値を返したとしたら、どうしてその値になったのかと、とても驚くことになるだろう。
ある意味では、定義の最小解というのは、定義から計算していていけない値の解決策と考えることが出来る。
上の方程式はf(-1)に対して値を指定していないので、 \perpが最小解として返された「結果」である。

Iswim( A)のプログラムは、前述のように、自由(未定義)変数を持つことがある。
これは、プログラム自身では意味(=変数への値の割り当て)を持たない可能性があるということを意味し、その場合、自由変数の値を私たちが指定する必要がある。
これらの値が、プログラムへの入力となる。
したがって、Iswimでの入力は「名前付き」または「ラベル付き」入力となる。

より正確には、 Aを代数とし、 Tを入力変数(自由変数)を持つIswim( A)プログラムとする。
 Tへの入力は、各入力変数に Aの宇宙の要素を割り当てる関数である。
 rを、すべての変数にランク0を与えることで Aシグネチャを拡張するランク付けとする(したがって Ttex: r]-式となる)。
入力 iを与えられた Tの出力は、入力変数 V i(V)を割り当てるような、任意の A r-解釈 Iに対する Tの値である。


ということで、Iswimの構文とその意味論。
言ってることはシンプルなはずなんだけど、どうしてここまで難しい話になるのか・・・

ようは、

  • Iswimの式は、単独の変数/定数、演算や関数の適切な数の引数での適用、where節を伴った式からなる
  • Iswimの式の値は、単独の変数/定数ならその値、演算や関数の適用なら適用させた値、where節を伴った式ならwhere節での解釈を使った式の値
  • where節は入れ子になるので、そこ(式から見える一番内側)に定義があればそれを使い、なければより外側の定義を探しにいく
  • 計算で得られない値(=エラー)は \perpとなる
  • 自由変数は入力として扱われる

ということだけのはずなんだけど(^^;

今日はここまで!

「Lucid, the Dataflow Programming Language」を翻訳してみた。(その8)

前回の続き。

2.1 代数

プログラミング言語についての質問で最もよくされるものの1つは、「その言語にはどのようなデータ型があるのか?」というものである。
言い換えれば、どんな種類のデータがその言語では扱われるのか、ということである。
この質問に対する典型的な答えは、整数、実数、真偽値、文字列、レコード、リスト、複素数、配列、表などである。
しかし、「Iswimにはどのデータ型があるのか?」という質問に対する回答は、「何でもある」となる。

この回答は正しいものの、それの意味するところは、Iswimが想像を絶するほど大きな言語であり、考えられるすべてのデータ型を利用可能である、といったことではない。
そうではなく、この回答の意味するところは、Iswimは(単一の言語ではなく)言語ファミリであり、各メンバは基本的なデータ構造の選択によって決定されるということである。
このファミリの異なるメンバは非常によく似ていて、私たちが「外側の構文(outer syntax)」と呼んでいるものについて、同じものになっている。(※after Wilkesという表現が使われているのだけど、よく分からない・・・)
この統一性は、Iswimの基本的な機能であるwhere節がデータ型やその操作に依存しないことから生まれている。
IswimはLandin(1966)が「あるものを他のもので表現する方法(a way of expressing things in terms of other things)」と「与えられたものの基本集合(a basic set of given things)」と呼んでいるものを分離している。
ファミリの各メンバは、(Landinの言葉でいえば)「うまくマッピングされた空間の、ある一点(a point in a well-mapped space)」となっている。

Iswim空間内の任意の点の座標(=Iswimファミリの任意の言語の1つ)は、与えられた(あるいは原始的な)ものの集合にちょうどなっている。
このプリミティブなものの集合は、一連のデータの集合、その集合上での操作の集合、および、それらのデータと操作を示す記号の集合で構成されている。
言い換えれば、Iswimファミリのメンバは、代数によって決定される。
代数 Aが与えられると、それによってファミリのあるメンバが定まり、そのメンバはIswim( A)となる。

(※以下、専門的な数学用語が出てきて、雰囲気しか訳せてない)

これをもう少し正確に書くと、以下のようになる。
シグネチャ(signature)とは、個体定数(individual constant、ものを示すための記号)と操作定数(operation constant、操作を示すための記号)の集合を意味していて、操作定数はそれぞれランク(rank)(あるいはアリティ(arity))(=引数の数)が決まっている。
シグネチャ Sが与えられると、S-代数である代数 Aが、そのデータの集合( Aの宇宙(universe、個体定数と操作定数によって覆われる代数構造の入った集合))とその上での演算によって構築される。
個体定数の任意の再帰的定義を解決するために、宇宙はドメイン(cpo、完備半順序)の構造を持ち、代数によって提供されるすべての操作は連続的でなければならない。
言い換えれば、Iswimファミリのメンバの座標は連続代数(continuous algebra)である。

この考えを説明するために、算術演算の入った整数という、とてもシンプルな代数 Zを示す。
シグネチャの個体定数は0と1である。

 Zの宇宙(これは完備半順序でなければならない)は、すべての整数と特別なオブジェクト \perpの集合である。
オブジェクト \perpは( Zの宇宙の要素の順序で)すべての整数の下に置かれる(ただし、この順序で整数自体は比較できない)。
したがって半順序(partial ordering)は以下のようになり、ここから \perpは「底(bottom)」と呼ばれることもある。

f:id:yamaimo0625:20190604001656p:plain

操作定数とそのアリティは、以下の通り:

操作定数 アリティ
abs 1
eq 2
ne 2
and 2
or 2
+ 2
- 2
* 2
div 2
< 2
> 2
mod 2
if-then-else 3

 Zがこれらの記号に割り当てる演算は、以下の通り:
(一般に、 \mathtt{k}_Aは代数 Aが演算 \mathtt{k}にそのシグネチャで割り当てた演算である)

  •  \mathtt{abs}_Zは、引数が整数の場合はその絶対値を返し、そうでなければ \perpを返す。
  •  \mathtt{not}_Zは、引数が1の場合は0を、引数が0の場合は1を返し、それ以外の場合は \perpを返す。
  •  \mathtt{eq}_Zは、両方の引数が整数で、値が等しければ1を、等しくなければ0を返し、そうでない場合は \perpを返す。
  •  \mathtt{ne}_Zは、両方の引数が整数で、値が等しいければ0を、等しくなければ1を返し、そうでない場合は \perpを返す。
  •  \mathtt{and}_Zは、引数が両方とも1の場合は1を、最初の引数が0、あるいは1つ目の引数が1で2つ目の引数が0の場合は0を返し、それ以外の場合は \perpを返す。
  •  \mathtt{or}_Zは、引数が両方とも0の場合は0を、最初の引数が1、あるいは1つ目の引数が0で2つ目の引数が1の場合は1を返し、それ以外の場合は \perpを返す。
  •  \mathtt{+}_Zは、引数がどちらも整数であればその合計を返し、そうでなければ \perpを返す。
  •  \mathtt{-}_Zは、引数がどちらも整数であればその差を返し、そうでなければ \perpを返す。
  •  \mathtt{*}_Zは、引数がどちらも整数であればその積を返し、そうでなければ \perpを返す。
  •  \mathtt{div}_Zは、引数がどちらも整数で、2つ目の引数が正の場合、引数の商(あまりは捨てられる)を返し、そうでなければ \perpを返す。
  •  \mathtt{\gt}_Zは、引数がどちらも整数で1つ目の引数が2つ目の整数より大きい場合は1を、1つ目の引数が2つ目の引数以下の場合は0を返し、そうでなければ \perpを返す。
  •  \mathtt{\lt}_Zは、引数がどちらも整数で1つ目の引数が2つ目の整数より小さい場合は1を、1つ目の引数が2つ目の引数以上の場合は0を返し、そうでなければ \perpを返す。
  •  \mathtt{mod}_Zは、引数がどちらも整数で、2つ目の引数が正の場合、1つ目の引数を2つ目の引数で割ったあまりを返し、そうでなければ \perpを返す。
  •  \mathtt{if}-\mathtt{then}-\mathtt{else}_Zは、最初の引数が1ならば2つ目の引数を、0ならば3つ目の引数を返し、そうでなければ \perpを返す。

この代数では、整数1、0をそれぞれ真、偽と解釈し、andorをそれぞれ論理積論理和と解釈する。
 \perpは、不適切な引数(0で割るなど)を演算に適用した結果に使用されるが、それ以外の明確な用途はない。
その本当の意味は後で説明される。

したがって、Iswim( Z)という言語は「整数Iswim」となっている(この言語については以前に言及したことを思い出して欲しい)。

より興味深い例として、"POP"代数 Pがある。
これは、この本で使用される最も重要なインデックス代数となっている。
 Pは、POP-2言語(Burstall, Collins, and Popplestone、1971)で提供されるデータ型および演算(の一部)に基づいている。
 Pの宇宙は、数値(有理数を含む)、文字列、単語(LISPでのアトムのようなもの)、リスト、および、エラーとデータの終わり(end-of-data)を指し示す2つの特別なオブジェクトで構成されている。
付録のpLucidマニュアルでは、これらのオブジェクトと操作について詳しく説明している。
また、POP-2に限りなく近い具体的な構文も説明している。

以下は、"POP-2"代数 Pによって定まるIswimファミリのメンバであるIswim( P)で書かれたプログラムである:

perms([a b c d])
where
    perms(L) = if L eq []
               then [[]]
               else allinserts(hd(L), perms(tl(L)))
               fi;
    allinserts(x, J) = if J eq []
                       then []
                       else inserts(x, hd(J)) <> allinserts(x, tl(J))
                       fi;
    inserts(x, M) = if M eq []
                    then [%[% x %]%]
                    else (X :: M) :: h(hd(M), inserts(x, tl(M)))
                        where
                            h(m, K) = if K eq []
                                      then []
                                      else (m :: hd(K)) :: h(m, tl(K))
                                      fi;
                        end
                    fi;
end

このプログラムは、リスト[a b c d]のすべての順列の一覧を出力する。
具体的な構文規則はpLucidのものとなっていて、たとえば、::はリストのコンストラクタ操作を示している。


ということで、Iswimと代数の関係について。

何やら難しい話だけど、ようはIswimは単一の言語ではなく、複数の言語からなるファミリであり、Iswimとして定められているのは共通する文法の部分だけで、それぞれのIswimで扱えるデータ型や演算は、与えられた代数によって変わってくるよ、という話。

今日はここまで!