プログラミング言語論’10 ＃ 4
久野靖∗

2010.5.6

はじめに• (defflavor フレーバ名 各種情報…) で「クラス」
を定義□ 前回やったこと• flavor のインスタンス→オブジェクト• (send オブジェクト セレクタ 引数…) → メッセー

• オブジェクト指向の考え方

ジ送信• 継承、動的分配などの重要な概念• 基本的な実装方式□ ここまでのところは Smalltalk と本質的におなじ• 別のオブジェクト指向言語: Dolittle□ 今回やること

1.2• Lisp 系のオブジェクト指向言語

多重継承□ Flavors による重要な拡張の 1 つ。flavor には複数の• オブジェクト指向と型

親 flavor が指定できる→多重継承• 継承と委譲• 多重継承では小クラスは親クラスすべてからインス
タンス変数、メソッド群を引き継ぐ→「混ぜる」こ• プロトタイプ方式 (Dolittle でやった…)• (Java の入れ子クラスと内部クラス) ←説明省略?

とによる干渉もあり使い方は難しい• 実例: Clojure□ 実際には、「通常の (インスタンスを作る) クラス」と、「他のクラスにまぜて機能を追加するクラス」(mixin ク

Lisp 系のオブジェクト指向言語

1ラス) を区別して使い分けることが通例• 例: ウィンドウクラスに対し、「窓枠をつける mixin
クラス」などを混ぜて機能の増えたウィンドウを作っ□ Smalltalk は最初から Lisp によく似た側面を備えていたて行く• そのため、Lisp 屋は Lisp にオブジェクト指向を導入することで Smalltalk のようなよい言語/環境を• このような操作を mixin 操作と呼ぶ

入手できるのではと考えた

1.3• 実際、多くの Lisp ベースのオブジェクト指向言語が作られた

メソッド結合□ Flavors で提案されたもう 1 つの重要な拡張。

• その際、Smalltalk や Simula になかった新しい概• Smalltalk では子クラスのメソッドは親クラスの同名メソッドを置き換え→親クラスのメソッドの動作

念も多く考案された□ 現在でも標準として残っているのは CLOS(Common Lisp

「も」利用したい場合は「super セレクタ …」によObject System) →ただし今後の Lisp はどれもオブジェ

り明示的に呼び出し

クト指向機能は持つようになる (CLOS 方式かどうかは• 多重継承では親が複数あるから上の方法ではいまいち分からないが)• C++では「どの親の同名メソッド」という形で呼べるが、この方法で十分かどうかは?

1.1

Flavors□ Flavors では、通常のメソッド (primary) のほかに、

deamon メソッド (before daemon、after daemon) が□ Zetalisp (Lisp Machine System で採用した Lisp のある (実際にはもっとあるがこれらが主に使われる)方言) 上のオブジェクト指向機能。クラスのことを

flavor と呼ぶ□ 多重継承とメソッドのオーバライドがある状態では…、次の順でメソッド群が呼ばれる∗ 筑波大学大学院経営システム科学専攻1• まず、before daemon が親クラスから子クラスへの順で呼ばれる

オブジェクト指向と型

2□ Simula は強い型の言語だったが、その後、Smalltalk、• primary method はこれまで通りのオーバライドな

Flavors、等はすべて弱い型の言語

ので一番最近に定義された子クラスのものだけが呼□ C 言 語 に オ ブ ジェク ト 指 向 を → や は り「 オ ブ ジェク

ばれるト型」はすべて一緒、というタイプが多かった (例:• 最後に after daemon が子クラスから親クラスへの順で呼ばれる□ 何のためにこうなっている?

Objective-C) →強い型ではない□ 10 年以上たって、ようやく「強い型のオブジェクト指→ before daemon は「前向言語」が当たり前になった (C++が代表的)

しまつ」、after daemon は「後しまつ」を行い、それらは順番に結合されて各レベルのクラスの仕事を実行する

2.1□ 使いこなせば便利なのかも知れないが、やっぱり難しい(と思う)

強い型の概念と利点/弱点□ 強い型とは?

→ コンパイル時にすべての式や変数の型が定まっている1.4

CLOS とマルチメソッド方式□ CLOS (Common Lisp Object System) → CommonLisp• 利点:

コンパイル時検査、設計の手段• 弱点:

繁雑、めんどくさい???

の言語仕様のうちの、オブジェクト指向機能部分をいう □ 中庸もある: 例 CommonLisp → 型はなくてもいいけ(後から追加されたもの)
ど、指定してもいい。指定すると効率がよいかも/コン
パイル時検査が可能□ 最大の変化→汎用関数 (generic function) に基づく
メソッド呼び出し• Flavors:

2.2(send オブジェクト セレクタ …) →弱い型のオブジェクト指向言語「どのメソッドか」は「オブジェクト」と「セレクタ」□ 変数にも式にもコンパイル時の型はないで決まっていた• 最初の引数 (レシーバ) のみを重視しすぎ? → 「す □ しかし、実行時には型 (==クラス) がある
べての引数がメソッドの決定に関与する」• 「anObject message.」→「OK である」か「そのメ(defclass X ....)(defclass Y ....)(defmethod method1 ((a X) (b Y)) ... *1 )(defmethod method1 ((a X) (b X)) ... *2 )...
(method1 anX anY) → *1 が呼ばれる(method1 anX anX) → *2 が呼ばれる

ソッドはない!」かどちらか。

• 「そのメソッドはない」がどこで起こり得るかを予め知る方法はない→製品としてソフトを作るときには弱点となり得る• 「OK である」ならよいのか?

→ たまたまそういう名前のセレクタが利用可能、だったらもっとたちが• 「メソッドがクラスに付属していない」「構文的には悪い?

普通の関数みたいな見え方」→特徴的 (好みも分かれる)• 多重継承やメソッド結合は Flavors 以来引き継がれ

2.3

ている

1.5

強い型のオブジェクト指向言語□ Simula が既にそうであった• 「型」と「クラス」は同じものとみなす (ちょっとは本節のまとめ違うが)□ Lisp 系のオブジェクト指向言語→メジャーではないが• 「ある型の変数/式」は実行時に「そのサブクラスの様々な試み

値も持つことができる」□ この規則は通常の「強い型の言語」からはだいぶ離れている• 多重継承• メソッド結合

Pascal ... x:integer := 1;x の型:integer、1 の型:integer
Java ... o:Object := new Integer(1);o の型:Object、式の型: Integer(Object のサブクラス)• メタオブジェクトプロトコル (MOP)• マルチメソッド

2• Object 型には任意のオブジェクトが入れられてしまう• コンストラクタを定義することで自動型変換が可能になる• 代入の左辺と右辺の型は同じでなく包含関係→複雑• 代入時も別扱いしたければ代入演算子も定義

さの原因• ただし、メソッドを呼ぶときは型が合わないとだめ• 基本型や既にあるクラスを「変換先」にしたい…コンストラクタは定義できない→その場合は「型変換int i = o.intValue(); ... ×
int i = ((Integer)o).intValue(); ... ○演算子」を定義 (「operator T()」…自分クラスの値を T 型に変換)• これらの複数の機能のどれを使うか→言語仕様で規定 (複雑)• このキャストは「実行時の型検査を伴うキャスト (ダウンキャスト)」であって C のキャストとは違う (も#include <iostream>using namespace std;

ともとのオブジェクトが Integer ないしそのサブク
ラスでなければ例外が発生)

2.4

class Test {int val;public:Test() { val = 9; }//Test(int i) { val = i+1; }//void operator=(const int& i) { val = i+2; }//operator int() const { return val+3; }int value() { return val; }};型情報を利用した自動型変換□ すべての式や変数に型がある→それらに「不一致」があることも• 数値型など基本型の間では一定範囲で「自動変換」が提供されているint main() {Test x;//Test x = 10;//x = 20;cout << x.value() << "\n";//cout << (x+0) << "\n";}• オブジェクト型どうしでは「上位の変数に下位のオブジェクトが入れられる」という規則が「原則」• オブジェクトと基本型→相互変換行わないのが「原則」• 便利さのためのこの「原則」をやめて変換を可能とする場合がある□ Java の AutoBoxing/Unboxing

2.5

実行時の型情報• Java では「1」(値) と「new Integer(1)」のように基本型とそれに対応するオブジェクト型の両方を □ 前記のキャストのようなことができる←実行時にもある程度の型情報が保持されている
用意→相互に変換することが多い• Boxing --- 「箱に入れる」: 値をオブジェクトに □ Java の場合: 上記のダウンキャスト、およびある型に属するかどうかを判定する instanceof 演算子
する• Unboxing --- 「箱から出す」: オブジェクトから
if(o instanceof Integer) ...
値を取り出す• もっと自由に型の情報そのものを扱う→自己反映機
Integer i1 = new Integer(1); ← boxing← unboxing

能 (後述)• JDK 1.5 以 降 で こ れ ら を「 自 動 化 」:boxing /unboxing が必要になったときは自動的に行う (演算 □ C++の場合: 実行時の型情報は virtual メソッドのあなどの場合も)るクラスに対してのみ利用可能。そうでない場合は実行
int j1 = i1.intValue();

Integer i1 = 100, i2 = 200; ← auto boxing
int j1 = i1, j2 = i1+i2; ← auto unboxing
int j3 = i2++; ← auto unboxing+boxing

時には情報がない。

• チェ ック の あ る ダ ウ ン キャス ト →dynamic cast<T>(値)• しかし「==」「!=」はオブジェクトどうしが等しいという意味がもともとあるので auto unbox されない

• 実 行 時 に 情 報 が な い 場 合 の キャス ト →static cast<T>(値)ことに注意• const の あ る 型 か ら
const cast<T>(値)• メソッド呼び出しのために autoboxing されることはない。「3.toString()」はダメ

const

を 外 す →• 何 を 何 に で も 変 換 で き る キャス ト →□ C++の自動変換機能

reinterpret cast<T>(値) ← こ れ ま で の「(型)値」というキャストと同等。これまでのキャストは• 多くの場合「変換」しなくても演算子オーバロード
で済む

危険な上に目立たないのでやめさせたいという考え

3

2.6

インタフェースと型• 動的分配の前提として、「A 型の変数に A のサブクラ
スがいろいろ入れられる」ということが必要□ 単一継承でサブクラス階層だけだと型の包含関係は非常□ しかしよく考えると、これは「実装の継承」が先にあり、にシンプル

その結果として「たまたまどれでも同じように取り扱え□ 多重継承があると 1 つのインスタンスが複数の親の型にる」ようになっているだけとも思える。この「たまたま」含まれることになるが、まだ包含関係は明らか

は気持ち悪い□ インタフェースではそれを実装しているすべての型を「くくる」という点で不規則な構造が作れてしまう (た

継承の実装

3.2

だしループは許さない)• インタフェース自体にも包含関係があるのでさらに □ ごく素直な継承の実装方法→オブジェクトの構造が重要。
インスタンス変数を定義された順に並べておく→子クラ
面倒なことに…
スでインスタンス変数を追加した場合は後ろにつけ加え

2.7

ていく

本節のまとめ□ 強い型はコンパイラによる誤り検査や設計の手段として有用

uclass U

x

int x
int y

□ 「お仕事の言語」では強い型のものが主流

y□ オブジェクト指向以前の「強い型」とオブジェクト指向w

metod A { ... }metod B { ... }の「強い型」はやや違っている (型の包含関係)• より広い型の変数に狭い型の値 (インスタンスを入
れられる)

x

• 広い型から狭い型に戻すことができる (実行時チェッ

t

y

クが必要)

v

class W
int x
int y
int t
metod A { ... }metod B { ... }metod D { ... }class V
int x
int y
int z
metod A { ... }metod B { ... }metod C { ... }

x

継承と委譲

3

y
z

□ 継承 (inheritance) → Simula、Smalltalk 以来の「由緒正しい」やりかた• この方法であれば、クラス A のどのサブクラスでも• 使っているうちに、色々問題もあることが分かってA までで定義されている変数のオフセットは同一 →オフセットで直接アクセス可能

来た• メソッドのコードがそのまま利用可能□ 委譲 (delegation) →継承の代替として使えるメカニ
ズム• 分かりやすく、効率がよい。ただし多重継承に対応できない• 複数オブジェクトの組合せ (composition) と相性がよい→継承よりも委譲を使うことが増えている

3.3
3.1

継承の意味づけメソッド探索□ メソッド呼び出しで実際にどのメソッドが動くかはオブジェクトのクラスによって変化→メソッド探索□ 継承がやってることは何かというと…

• Smalltalk では、最初にそういう呼び出しがあった• インスタンス変数とメソッドを引き継ぐ

ときに探索を行い、その情報をキャッシュに保持。ク

• その結果として、呼べるメソッドの集合や外から見たオブジェクトの振る舞いを引き継ぐラス構造が変化したときはキャッシュをご破算にしてやりなおす。動的にクラスが変化する環境ならでは• たとえば B が A のサブクラスであれば、「A のインス

• C++、Java などのコンパイルする言語では、メソッド表を作ってそれに基づいて分岐すればよい。表そタンス」の代りに「B のインスタンス」を与えてもそのまま動く (建前としては)のもののスロットも変数と同様にして管理可能

4• インタフェースの場合は 1 つのクラスがさまざまな
インタフェースを実装しているので面倒。Java では• C++、Java など→サブクラスでいじれる変数、いじ
れない変数を区別可能に (private →サブクラスで呼び出し時に探索しているが、工夫次第では「表引もいじれない変数、protected →サブクラスでもいき」にもできる

じれる変数) →それが問題の解決になってるのかど
うか??

3.4

多重継承の実装□ 機能追加の制約• 単一継承の場合、1 つずつしか機能を追加して行けない→たとえば図形に「動く機能」「大きさが変わる□ 多重継承→ C++ではサポート。Java では単純化のため
禁止。
□ 問題:

機能」を追加したいとすると、それぞれの機能のあ

複数の親が共通の親クラスを持っていたらどう

るなしごとにクラスを分けることに→機能が N 種類

するか?

あったら 2 の N 乗必要→組合せ爆発• 案 1: 親のインスタンスが 2 個埋め込まれる→その場所は親クラスとしてそのまま扱える (ただしポイ• 多重継承があれば「動く機能」「大きさが変わる機能」などをそれぞれ別のクラスにして「混ぜる」ことでンタ逆変換の問題がある)(C++)• 案 2:

必要なクラスが作れる。ただし混ぜたものの干渉が共通部分は「統合」される→「どこに親が埋心配まっているか」のポインタをそれぞれのインスタン□ 継承は静的スに埋める必要 (C++)• 案 2 の別解→最初に名前で探索し、その場所を覚え• 実行時に機能を追加したり外したりといったことはできない。

る (Flavors)• しかし場合によっては実行時に機能の調整を行ない□ C++では案 2 は「virtual 親クラス」と呼ばれている。

たい。たとえば「動かない円」を作っておいたがそ両方あるのは C++らしいが複雑。

れを途中で動くようにしたい等。

• とくに、コンストラクタによる初期設定が複雑。
「子」
が共通の「親」を初期設定すると 2 回初期設定され□ 例:

GUI 部品と動作てしまう。このため、virtual 親クラスになるもの• たとえば「ボタン部品」を考えてみる。ボタンにはには「引数無しの」ものが用意されている必要。こ「ボタンを押した時の動作」があるはず。その動作はれが最初に呼ばれる。
「子」のコンストラクタでは親

アプリケーション固有のコンストラクタは呼ばないのが通例。

• しかし汎用の Button クラスには当然、アプリケー
ション固有の動作は入っていない

3.5• ではどうするか? → 継承を使って、「動作」メソッ
ドをオーバライドして、そのメソッド中でアプリケー

継承の問題点□ 継承にはさまざまな問題点があった→何が問題か、分かション固有の動作を行なわせるります?

• Java は JDK 1.0.x ではそうしていたが、JDK 1.1
からはやめている→動作 1 つごとにサブクラスを作□ 実装と界面の混同るのが煩雑、複数の場所 (例:• 継承は実装を引き継ぐことで、インタフェースを共通にすること、多相性 (polymorphism、動的分配)

メニュー項目やキー

ボードショートカット) から同じ動作を起動するときの共有ができない、実行時に割り当てを変更できを提供することはまた別の問題だが混同されがちない• Java のようにインタフェースを別にすることが必要(ただし Java でも継承すると多相性がついてくるので中途半端)

3.6□ カプセル化の破壊

委譲 (delegation)□ 継承は「親のインスタンス変数やコードを取り込んで来て自分の一部として実行させる」→カプセル化が壊れる• サブクラスを作ると、その中では親クラスの変数が自由にいじれてしまう→カプセル化の破壊。

等の問題□ 別オブジェクトの機能が必要なら、それを別のインスタンスとして持っていて、これを普通に呼び出すことでも利用可能→委譲の考え方

5• 簡単に言えば、自分で実装しないメッセージを「た class Circle extends SimpleFigure {int rad;らいまわし」にする

public Circle(int x, int y, int r, Color c) {super(x, y, c); rad = r;}public void draw(Graphics g) {g.setColor(col);g.fillOval(gx-rad, gy-rad, rad*2, rad*2);}□ 委譲のさまざまな利点• カプセル化が壊れない• 委譲先を実行時に動的に切り替えることができる• 多重継承の実装が容易 (多重継承の実装として、一}部に委譲を使うことも)□ 例:

class MyAdapter1 implements ActionListener {Applet app; Figure fig;
public MyAdapter1(Applet a, Figure f) {app = a; fig = f;}public void actionPerformed(ActionEvent e) {fig.moveTo(fig.getX()+5, fig.getY()+5);
app.repaint();}}

GUI 部品の「ボタン」に動作をつける場合• JDK 1.1 以降では各ボタンは次のメソッドを持つ
public void addActionListener(ActionListener l)• ActionListener は次のようなインタフェース
public class ActionListener {public void actionPerformed(ActionEvent e);}• 各ボタンの動作は ActionListener インタフェース

class MyAdapter2 implements ActionListener {Applet app; Figure fig;
public MyAdapter2(Applet a, Figure f) {app = a; fig = f;}public void actionPerformed(ActionEvent e) {fig.moveTo(fig.getX()-5, fig.getY()-5);
app.repaint();}}を実装するクラスのインスタンス (アダプタオブジェクト) のメソッド actionPerformed() として用意• ボタンは押されるとアダプタオブジェクトの上記メ
ソッドを呼び出す□ 例題:

図形を動かすボタンをつけてみる

import java.applet.*;import java.awt.*;
import java.awt.event.*;//← import 追加!

□ アダプタオブジェクトは「オブジェクト」なので、その中に「呼ばれた」時に必要なデータを持っておくことがpublic class Sample26 extends Applet {Figure f1 = new Circle(50, 50, 30, Color.red);
Button b1 = new Button("B1"); // ボタン
Button b2 = new Button("B2"); // ボタン
public void init() { //←初期設定メソッド
setLayout(null);//←自動配置 offadd(b1); b1.setBounds(10, 10, 60, 30); //配置
add(b2); b2.setBounds(10, 50, 60, 30); //配置
b1.addActionListener(new MyAdapter1(this, f1));
b2.addActionListener(new MyAdapter2(this, f1));} // ↑ボタンにアダプタを設定
public void paint(Graphics g) { f1.draw(g); }}できる□ 上の例では 2 つのアダプタクラスはほとんど一緒→ 1 つで済ませる方がスマート。だが、複数あってよいという例のつもりで 2 つ用意した

3.7

Self: プロトタイプ方式のオブジェクト指向言語□ ここまでは「委譲」をコーディング上の手法として考え

interface Figure {public void draw(Graphics g);
public void moveTo(int x, int y);
public int getX();public int getY();}て来たが、言語機構として継承の代わりに委譲のみを使う言語も→ Self
□ クラスがなく、「ひな型」のオブジェクトを複数コピーすることでインスタンスを作る

abstract class SimpleFigure implements Figure {int gx, gy; Color col;public SimpleFigure(int x, int y, Color c) {gx = x; gy = y; col = c;}public abstract void draw(Graphics g);
public void moveTo(int x, int y) {gx = x; gy = y;}public int getX() { return gx; }public int getY() { return gy; }}• 委譲した場合でも「元のオブジェクト (プロトタイプ)」を覚えておいて、自分自身に対してメッセージを送った場合元から探す。これがないと次のようなメソッド (抽象メソッド) が使えない
moveRight | n |self turn 90.
self forward n.

□ Self 言語は「プロトタイプ方式の」「委譲に基づく」オブ
ジェクト指向言語が有用だという実証の意味が大。コー

6

ドの性能も動的コンパイル (よく使う/高速な組合せだ

4.2

けをコンパイルしていく) などの技術により優れていた

Java の内部クラス□ インタフェースを使ってアダプタクラスを作るような□ もう 1 つの (極めて普及している) プロトタイプ方式のオ場合:

ブジェクト指向言語→ JavaScript(次回に取り上げる)• 小さいクラスが多数できてしまうので面倒→これに

3.8

対しては入れ子クラスを使えばよい

本節のまとめ• アダプタクラスが保持するデータはインスタンス変数として保持する必要→その宣言や初期設定等が結□ 継承はオブジェクト指向言語の大きな特徴として注目

構面倒• もっとも「継承のないオブジェクト指向言語」も可• そこでどう考えたか…

能だしあるが□ 2 種類のメソッド□ 委譲 (たらいまわし) は最初は「単なる別のオブジェク• static のついたメソッド (クラスメソッド) →イン

トの呼び出し」だったが、次第に継承に代わる機構とし

スタンス変数にはアクセスできない、独立した関数て認識のようなもの□ オブジェクト指向プログラミング全体が継承指向から委• static のつかないメソッド (インスタンスメソッド)

譲指向に変化→インスタンスに付随していて、インスタンス変数
を読み書きでき、他のインスタンスメソッドをそのJava の入れ子クラスと内部クラス

4

まま呼び出せる□ 入れ子クラス (クラスの中にクラス) については既に説 □ これにならって、入れ子クラスも 2 種類にする!
明したが、再度整理しておく• static のついた入れ子クラス→インスタンス変数に□ 入れ子クラスの特別な場合である「内部クラス」についはアクセスできない、独立したクラスと同様

ても説明

4.1

• static のつかない入れ子クラス→*外側の*クラスのインスタンス変数にアクセスでき、外側クラスのイ
ンスタンスメソッドをそのまま呼び出せる→つまり、入れ子クラス

外側クラスのインスタンスを「覚えて」いる→その代□ クラスの中に別のクラスを書けるようにしたオブジェク

わり new で作り出せるのはインスタンスメソッドやト指向言語はまだあまり多くない。

コンストラクタの内側→「内部クラス」と名付けた• さらに内部クラスの中からは、インスタンス変数だ
けでなく final 指定の引数や局所変数 (つまり値が• クラスはモジュールのようなもので、ある程度の完結性があるから、あるクラスの中に別のクラスを入変更されない変数) も参照できる←ということは必れると言うことはあんまり必要がないと思っていた?

要なら「メソッドの途中」にも書けるわけ□ しかし Java ではクラスの中に「そのクラスだけが使う• これらの機能のため、内部クラスを使うといちいち
アダプタクラスにインスタンス変数を持たせなくて下請けクラス」が書ける→入れ子クラス

もよい場合が多い→記述が簡単• これは、Java が「必要があればどんどんクラスを作って使う」方向だから← Smalltalk などもそういう文 □ これを使って先の例を書き換えてみた化だが、クラスだらけでごちゃごちゃになりがち• Java ではパッケージ (階層的な名前を持つ) →クラス→入れ子クラスという 3 段階だからだいぶいい。
入れ子クラスの中にさらにクラスを入れることもで

import java.applet.*;import java.awt.*;
import java.awt.event.*;
public class Sample27 extends Applet {Figure f1 = new Circle(50, 50, 30, Color.red);
Button b1 = new Button("B1");
Button b2 = new Button("B2");
public void init() {setLayout(null);add(b1); b1.setBounds(10, 10, 60, 30);add(b2); b2.setBounds(10, 50, 60, 30);
b1.addActionListener(new MyAdapter1());
b2.addActionListener(new MyAdapter2());

きる (まず見かけないが)7Button b2 = new Button("B2");
public void init() {setLayout(null);add(b1); b1.setBounds(10, 10, 60, 30);add(b2); b2.setBounds(10, 50, 60, 30);
b1.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {f1.moveTo(f1.getX()+5, f1.getY()+5);
repaint();}});
b2.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {f1.moveTo(f1.getX()-5, f1.getY()-5);
repaint();}});}
public void paint(Graphics g) { f1.draw(g); }}public void paint(Graphics g) { f1.draw(g); }class MyAdapter1 implements ActionListener {public void actionPerformed(ActionEvent e) {f1.moveTo(f1.getX()+5, f1.getY()+5);
repaint();}}class MyAdapter2 implements ActionListener {public void actionPerformed(ActionEvent e) {f1.moveTo(f1.getX()-5, f1.getY()-5);
repaint();}}}(以下同じにつき略)□ 外側クラスのインスタンス変数に直接アクセス→そのた}めコンストラクタも変数も不要→さっきよりずっとコン (以下同じにつき略)
パクトに□ ボタン以外の GUI 部品を使った例も挙げておこう。

4.3• Button →ボタン

無名クラス

• Label →表示ラベル□ 上の例では MyAdapter1 等の名前を使う個所はそれぞれ• TextField →入力欄

1 個所ずつしかない• Choice →選択メニュー□ 1 個所でしか参照しないようなアダプタクラスの名前を□ 選択メニューについては、選択肢は add() で別途追加

いちいち考えるのは嬉しくないする□ このような場合には、なおかつ「そのクラスが 1 つのク
ラスを extends しているか、または 1 つのインタフェー

import java.applet.*;import java.awt.*;
import java.awt.event.*;

スを implements しているだけであれば」名前を省略できる→無名クラス□ 具体的には次のような形

public class Sample29 extends Applet {Label l0 = new Label("");
Choice c0 = new Choice();
Button b0 = new Button("Calc");
TextField t0 = new TextField("");
public void init() {setLayout(null);add(l0); l0.setBounds(10, 10, 280, 30);add(c0); c0.setBounds(10, 50, 60, 30);c0.add("F to C"); c0.add("C to F");
add(b0); b0.setBounds(110, 50, 60, 30);add(t0); t0.setBounds(10, 90, 120, 30);b0.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {try {float x =(new Float(t0.getText())).floatValue();float y = (c0.getSelectedIndex() == 0) ?
(5.0f/9.0f * (x - 32.0f)) :(9.0f/5.0f * x + 32.0f);
l0.setText("Result: " + y);} catch(Exception ex) {l0.setText(ex.toString());
}
}
});
}
}

public class X {... new Y(); ... ←ここでだけ使用
class Y implements I {内部クラスの定義}}□ このとき、使用しているところに Y の定義本体を直接埋め込んでしまうことで名前を書かなくする
public class X {... new I() {内部クラスの定義} ...
}□ 先の例を無名内部クラスを使うように直すと:import java.applet.*;import java.awt.*;
import java.awt.event.*;
public class Sample28 extends Applet {Figure f1 = new Circle(50, 50, 30, Color.red);
Button b1 = new Button("B1");

8• 動 か し 方: コ マ ン ド「clojure」→ プ ロ ン プ ト「user=>」が出る□ 記述が短く簡潔になるという点は便利だが、最初はちょっと分かりづらい• 止め方:□ 従来の言語で「クロージャ」と呼ばれる機能を持つもの

Control-D を打つと終わるがある• 打ち込むもの:• クロージャ = 関数 + 環境

• 基本形: 「(関数名 パラメタ…)」例:→ 6• Smalltalk のブロックも一種のクロージャ

Lisp の式• カンマをつけてもよい (C) 例:• そのクラス版が Java の内部クラスだと考えられる(+ 1 2 3)(+ 1, 2, 3) → 6□ 基本的な型と評価方法

4.4• 数値や文字列は評価してもそのまま。 3 →本節のまとめ3、"abc" → "abc"□ Java の入れ子クラス→クラスの中に下請けクラスが書• 単一の文字は「＼文字」。

ける• 名前は「記号 (symbol)」。記号は値を持てる。
「(def
x 1)」

□ 内部クラス→外側インスタンスの情報や、外側メソッドの局所変数などに中から直接アクセスできる→クロー• 記号を評価すると記号が持っている値になる。x →ジャ機能を実現したものと言える

1• うまく使えば記述がコンパクトになるが、一方でどこで何をアクセスしているか分かりにくくなる恐れ• リスト:

ムを表すのにも使う。

もある□ ついでに:• リストは関数呼び出しとして評価される。(* 1 2 3)
→ 6

オブジェクト指向言語の諸側面のまとめ• 記 号 や リ ス ト を「 そ の ま ま 」(評 価 せ ず に) 使 い• 非常に多数の機能がある、ということは分かったと思う

た い 場 合 は「’」を 前 置 。’x → x、’(1 2 3) →(1 2 3)、(first ’(1 2 3)) → 1、(rest ’(1 2• それらすべてに「発明された理由」はもちろんある

3)) → (2 3)• しかし「それらがすべてが今いるのか?」は別の問題• 「’…」は「(quote …)」の短縮記法 (読み込み時に展開)。’’x → (quote x)• 言語の多様な機能を使えば使うほど「難しく」もなる→「機能の増加」と「簡潔に/分かりやすく記述できる」のトレードオフについて常に考える (例:「(…)」。リストは「並び」だがプログラ• 「:」で始まる名前は「キーワード」。評価するとそのまま。プログラム上のさまざまな指定のために使Javaうことが多い。:a → :avs C++)• [値…] --- ベクタ。リストとは別種の「並び」。

5

実例: Clojure□ true、false、nil --- 真、偽、「ない」ことを表す値。

nil と ’() は別のもの (C)。

□ Lisp ベースの言語• 「?」で終わる関数は述語 (最近の流行) --- (nil?

• Lisp、記号計算

nil) → true, (nil?
10) → true, (char?

• JVM に強く統合→ Java API を自由に使用可能• 関数型 (普通の Lisp と違い副作用は非常に限定)• if などで論理値として使う時は「false」「nil」は偽、それ以外は真として扱われる• ｛キー 値 キー 値 …｝--- マップ、＃｛値…｝ --セット。これらは関数としても使える• 遅延評価• 並行処理に対する対応

5.1(def s1 #{:a :e :i :o :u})(s1 :a) → :a; (s1 :x) → nil(def s2 {:a 1 :e 2 :i 3 :o 4 :u})(s2 :e) → 2; (s2 :x) → nil

Clojure 入門□ Read-Eval-Print-Loop (REPL) から使う

• Read:式 を 読 む 。Eval:

式 を 評 価 ( 実 行) 。

Print: 結果を打ち出す。Loop:に繰り返す。

’()) → false, (even?
:a) → false

以上 3 つを無限□ 関数定義 --- (defn 名前 [パラメタ] 本体)(defn fact [n] (if (< n 1) 1 (* n (fact (- n 1)))))

パラメタをベクタで表現するところは変わっている (C)9• 上記は if による枝分かれ。多方向の枝分かれは cond 5.2 シーケンスだが、普通の Lisp よりかっこが少ない (Lisp の「かっこのお化け」をできるだけ避けようとしている)□ 「並んだもの」(リスト、ベクタ、ファイル、ディレクト
リ、パターンマッチ結果、…) → すべて「シーケンス」(defn test1 [x](cond (> x 0) ’positive(< x 0) ’negativetrue ’zero))として統一• すべてのシーケンスを操作する共通の関数• 長くなった場合は→ファイル「なんとか.clj」に入(first ’(1 2 3)) → 1(rest ’(1 2 3)) → (2 3)(cons 1 ’(2 3)) → (1 2 3)

れておき「(load "なんとか")」で読み込ませる• 無名関数は「(fn [パラメタ] 本体)」(C)• シーケンスに「都合のいいように」要素 (群) を追加する関数(filter (fn [x] (> x 2)) ’(1 2 3 4)) → (3 4)□ もう 1 つの関数定義の書き方として、パラメタ個数が異なる複数のケースを選択する形で定義するものがある。

(conj ’(1 2 3) 4 5)) → (5 4 1 2 3)
(conj [1 2 3] 4 5)) → [1 2 3 4 5]
(into ’(1 2 3) ’(4 5)) → (5 4 1 2 3)
(into [1 2 3] [4 5])) → [1 2 3 4 5]

(defn test ([] 1)([x] (inc x)))

□ パラメタの「ある位置以降」をリストとして受け取る仕組み (L)• シーケンスの具体的な型は様々だし、操作によって変わる(defn sum [& l] (apply + l))(sum 1 2 3) → 6• 静的な型検査のない言語→「使い方」さえ合ってい
ればどの型でも動く□ 再帰関数• こういうのを「Duck Typing」と呼ぶこともある (強い型でも可能という説もあり)• 普通の関数型言語処理系だと→単純再帰はループに翻訳される(defn nums([n] (nums n []))([n r] (if (zero? n)r(nums (dec n) (conj r n)))))

5.3□ 遅延評価 (lazy evaluation) --- 式の値を「必要になった時はじめて」評価すること• このように 1 引数と 2 引数を分けて扱える→補助関数を別にしないでもすむ• しかし!

遅延評価と遅延シーケンス• 対義語:

先行評価 (eager evaluation)。普通の言

語はこれClojure は JVM に頼っているので単純再• (def x (+ 1 y)) --- 通常 (E):1 と y を計算してx に束縛。遅延 (L): x に「この式を」保持。x の値

帰を自動的にループにはできない→上記の定義は「10000」とかでも動かない (スタックオーバフロー)• recur を使って「これは再帰だよ」と明示すればループとして動作を取り出そうとした時に計算• 遅延評価の利点 --- 必要の無い計算は行わないで良い、複数回同じ式を計算しても最初の 1 回だけ計

(defn nums([n] (nums n []))([n r] (if (zero? n)r(recur (dec n) (conj r n)))))

算、無限の長さのデータ構造を扱うことができる• 副作用のない言語では、いつ評価しても値は同じ (参• 次のような「素朴な」版だと recur に書き換えられない (前の値を取っておいて再帰から戻ってきてから追加する必要)(defun nums [n](if (zero? n) [] (conj (nums (dec n)) n)))照透明性) →遅延評価が導入しやすい• 例:

Haskell --- 「すべて遅延評価」な言語□ Clojure は…「ある特定の」データ構造の中だけ遅延評価。具体的には一部のシーケンスの中身• loop という形式を使うと関数の先頭でなく途中の位
置に recur で戻れる
(defun nums [n](loop [x n r []](if (zero? x)r(recur (dec x) (conj r n))))• この loop では変数を書き換えてはいないが、次々に書き換えているかのようなイメージでプログラムす• 例: (iterate f i) :…) を計算 (無限列)(i (f i) (f (f i))(take 10 (iterate inc 1))→ (1 2 3 4 5 6 7 8 9 10)(defn twice [x] (* x 2))(take 10 (iterate twice 1))→ (1 2 4 8 16 32 64 128 256 512)(nth (iterate twice 1) 10)→ 1024• 必要になった時計算される→無限で構わない

ることが可能10• ここではロックのアンチテーゼとして今日注目され• take や nth を使って一部を取り出して打ち出す (そている枠組みについて紹介のままだと全部打ち出そうとするので…)• 頭を押えないようにする (不要な部分を持ったまま□ メッセージパシング (アクター、エージェントなどともだとメモリを圧迫)呼ばれる)(nth (iterate inc 1) 100000000) : 無問題(def l (iterate inc 1))(nth l 100000000) : メモリ不足で死ぬ□ 遅延シーケンスを自分で作る:• 例:• 実行主体どうしはメッセージを送り合うだけでデータは共有しない(lazy-seq 本体)• 共有しないから競合もしない

フィボナッチ数• すべての状態更新をメッセージで表現しないといけないのが面倒 (?)(defn fib([](concat [0 1] (fib 0 1)))([x y](let [z (+ x y)](lazy-seq (cons z (fib y z))))))□ ソフトウェアトランザクショナルメモリ (STM)• トランザクション --- 一群の共有状態に対する参照/更新をグループ化したもの (データベース、OLTP• 上記はフィボナッチ数列を返す関数から来ている):(take 20 (fib))(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
610 987 1597 2584 4181)□ まとめ:ACI(D)• 1 つのトランザクションは外からは「一瞬」で終わる(Atomic)• 各トランザクションは整合した状態のみを見る/見せる (Consistent)

遅延評価を使うことで「いくつまで」を気にしなくて済む (必要ならその要素を取ろうとすると計算• 各 ト ラ ン ザ ク ション は 相 互 に 隔 離 さ れ て い る

される)(Isolation)

5.4• データベースでは「Commit したトランザクションに並行制御

よる更新は無くならない」(Durable) というのがあるが、プログラミング言語ではそれはなし□ 今日の CPU →単一コアの速度向上は頭打ち、マルチコア• トランザクションは上記の条件が破られそうになったらアボートして再試行。最後まで無事完了したも化によるスループットの向上が主流に• プログラム内でも並列性がないとマルチコアの恩恵が受けられないのは commit• 多くの言語/システムではマルチスレッドによる並列□ STM の実現方法

性の記述• 悲観的並行制御:
から開始• 並列プログラムの難しさ→スレッド間で共有される
データの協調的な更新□ 伝統的な並行制御のツール:• 楽観的並行制御: 各トランザクションはバージョン
番号を取得して開始し、参照/更新するデータに対しロックてバージョン番号を記録→更新そのものは手元のコ• ロックを取れるのは一人だけ: 「ロックを取る→排ピーに対して実施→最後にバージョン番号が自分の記

他的にデータを参照/更新→ロックを解放」

録したもののままなら競合はないので手元のコピーを書き戻して commit□ ロックには多くの問題点• 誰かが間違ってロックを取ったままになると→すべて止まってしまう• 誰も間違っていなくても取り方が悪いとデッドロック• 多くの並列実行は互いに独立→楽観的並行制御はオー
バヘッドが少なく有利 (とされている)□ STM の得失• 「ある条件が成り立つまで待つ」機能が必要だが難しい□ 根本的な問題:

必要なものすべてロックを取ってロックによる並行制御はデータ抽象などの形でのカプセル化と直行している (カプセル化でき• ロックを取ることを考えなくてよい→「必要なデー
タの参照/更新をすればいい」だけ→一般のプログラ
マ向きとの説 (cf.

ロックを正しく扱うのは一般のプログラマには極めて難しいとの説)• 並行制御だけで条件同期が無いが、「条件が合わなけ
ればしばらく待って再度実行」などの手法が提案/実ていない)• 久野の研究もあります…

装されている (Haskell STM)

11• 旧来の言語で書く場合、全ての「読み/書き」を STM
オペレーションとして実行するのが繁雑→新しい言
語で STM が組み込まれていればとくに問題なし

5.6□ Lisp 系の言語 --- 構文が単純 (全部 S 式) →マクロによる拡張が自由• STM 操作とその他の副作用を持つ操作が両方書ける言語だと、トランザクションの再試行が外から見え• Lisp 系のマクロの動作: 引数をそのままの形でマクロに渡す→マクロは式を組み立てて返す→その返て混乱を招くという説もされた式を改めて Lisp 処理系が評価• よくある例: unless を作る --- 関数ではだめ• 競合が激しくなった時には性能上問題があるという
説も

5.5

マクロ

(defn unless [cond body] (if cond nil body))(def x 0)(unless (odd? x) (print "Y")) → Y をプリント(unless (even? x) (print "Y")) → Y を プ リ ン ト(!!)

Clojure の並行制御機能□ atom --- 「瞬時」に終わる動作は並行制御しなくても大丈夫• 関数は引数を常に評価してしまう→副作用があればそれが起きてしまう (副作用がないとしても計算は• (def 名前 (atom 初期値)) --- 初期値を指定してatom 値を生成し定義

起きてしまう)• マクロで定義すれば「そのまま」取り扱われるのでこの問題がない• (reset! 名前 新しい値) --- 新しい値を書き込む• (swap! 名前 関数 引数…) --- (関数 前の値 引数…) を計算し、その結果を新しい値として書き込
むとともに返値として返す(defmacro unless [cond body](list ’if cond nil body))(unless (even? x) (print "Y")) → Y は打ち出されない(macroexpand-1’(unless (even? x) (print "Y")))→ (if (even? x) nil (print "Y")) …これを実行(def counter (atom 1))(swap! counter inc) → 2□ agent --- 後で非同期的に更新する機能• (def 名前 (agent 初期値)) --- 初期値を指定し □ マクロは「Lisp プログラムを組み立てる関数」て agent 値を生成し定義• (send 名前 関数 引数…) --- atom の更新と同様• →実際には「ほとんど Lisp プログラムをそのまま書くが、一部だけパラメタとして受け取ったものを埋だが後で実行• (await 名前…) --- agent(必要なら複数) の更新
完了を待つ

め込む」形
• →バッククォート記法ないし構文クォート (上記の作業を簡単にこなす仕掛け)(def counter (agent 1))(send counter inc) → agent オブジェクトが帰る(await counter)@counter → 2 …「@名前」で状態を読み出せる‘(…そのまま… ~x …そのまま…) ←要素埋め込み‘(…そのまま… ~@l) ←リストの残りを接合□ ref --- 参照変数 (書き換え可能な変数):• これを使うと先の unless は次のように書ける(defmacro unless [cond body](if ~cond nil ~body))

トランザ

クションの中でのみ参照/更新が可能□ マクロを使うと「言語の自在な拡張」が可能になる ←• (def 名前 (ref 初期値)) --- 初期値を指定してLisp 族ではプログラムがすべて S 式というデータの形になっていることがそれを可能にしている

ref 値を生成し定義• (deref 名前) --- ref 値の内容を読み出し…「@名前」でも同じ□ マクロによくある問題→記号の捕捉 (capture)• (ref-set 名前 値) --- ref 値に新しい内容を書き込む• (alter 名前 関数 引数…) --- atom の更新と同様• (dosync 式 …) --- ref の参照/更新はすべてこの中で行う必要 (トランザクション)• マクロの中で識別子を導入したときに起こる(defmacro myor [e1 e2]‘(let [tmp ~e1] (if tmp tmp ~e2)))(def tmp 0)(myor (> tmp 0) tmp) → ???

• 普通の Lisp だとこれは次のように展開(let [tmp (> tmp 0)] (if tmp tmp tmp)) → true

□ Clojure では STM などの並行制御機能を提供• さまざまな材料が提供されている→分かっていて混ぜるのならいいが、安易に混ぜるとはまりそう• 基本的な問題: マクロの中で作業用に使っている
tmp とパラメタで渡されて埋め込まれる tmp とか衝
突/混同

12

• よくある対処法: マクロ中では「普通使わなそうな」名前を使う→根本的な解決でない

5.8• Scheme の synatx-case --- 非常にややこしい• Clojure のアプローチ: ランダム番号つきの名前を自動生成する□ 通常のメソッド呼び出し (Java も) →レシーバの種別(クラス) に応じた動的分配• より柔軟に→ (1) 複数の引数に応じたメソッド選択、(2) メソッド選択をユーザがコーディング(defmacro myor [e1 e2]‘(let [tmp# ~e1] (if tmp# tmp# ~e2)))(macroexpand-1 ’(myor (> tmp 0) tmp))→ (clojure.core/let [tmp__43__auto__ (> tmp 0)](if tmp__43__auto__ tmp__43__auto__ tmp))• (defumlti 名前 選択関数) :• (defmethod 名前 選択値 [引数…] 本体) :• 選択関数 : 引数を受け取り、選択値のどれかを返す• 例: Java の「+」と同様、「引数どちらから文字列
なら連結、そうでなければ加算」するメソッド mysum□ Clojure の特徴の 1 つ→ JVM 上の言語→ Java オブジェ

(defn sum-dispatch [x y](cond(isa? (. x getClass) String) :string(isa? (. y getClass) String) :string
true :other))(defmulti mysum sum-dispatch)(defmethod mysum :string [x y](. (. x toString) concat (. y toString)))(defmethod mysum :other [x y](+ x y))クトを自在に活用• (.

メソ

ッドを 1 つ定義

Java との連携• (new パッケージ/クラス 引数…) :クトの生成

マルチメソッドであることの宣言• 必要なら捕捉が起きるようにすることも可能

5.7

マルチメソッドJava オブジェ

オブジェクト メソッド 引数…) :Java オブジェクトのメソッド呼び出し• (パッケージ/クラス/メソッド 引数…) :メソッドの呼び出し• 型は適当に変換して渡してくれる

static□ 自由度、柔軟性は非常に高まるが…これほどの柔軟性が必要な場面にはめったに遭遇しない (よくある例としては、ここで挙げたような演算の選択)(def win (new javax.swing.JFrame))(. win setSize 600 400)(. win setVisible true)(def pane (new javax.swing.JPanel))(. win add pane)(. pane setLayout nil)(def btn1 (new javax.swing.JButton "Button 1"))(. pane add btn1)(. btn1 setBounds 50 20 120 40)(def lab1 (new javax.swing.JLabel "1"))(. pane add lab1)(. lab1 setBounds 50 80 120 40)

6

レポート課題□ 出席に加えて、レポートを 1 件出していただきます。
□ 課題:

本講義で取り上げた言語、ないしその他の言語のうちから、「自分が普段使い慣れていないもの」を 1
つ選び、その言語の何らかの (自分にとって目新しい、ないし興味深い) 特徴を確認できるようなプログラムを□ Java のクラスを作り出すには→ proxy

作って解説しなさい。下記の内容が含まれること。

• (proxy 名前 [クラス or インタフェース…] [変数…] 関数…)(def btn1action(proxy [java.awt.event.ActionListener] [](actionPerformed [evt](let [i (Integer/parseInt (. lab1 getText))](. lab1 setText (. (inc i) toString))))))(. btn1 addActionListener btn1action)• proxy は既存のクラスやインタフェースに従うオブ
ジェクトを作るだけだが、自前のクラスを生成することも可能 (ここでは略)• 動機:

どのような言語の、どのような特徴を確認しようと思ったか。また、なぜそれを選んだか。

• 方針: どのようなプログラムを作れば確認が行えると考えたか。
• 本体: 実際に作ってみたプログラム、およびその実行のようす。
• 解説:

プログラムおよび実行のようすの解説。

• 考察: 実行のようすからどのようなことが分かるか。とりわけ、確認しようと思った特徴がどのように結果に反映されているか。

□ Clojure のデータも実はすべて Java オブジェクト ←• 感想など。
JVM 上で動いているのだから、基本型でないものはすべて Java のオブジェクト□ 期限: 6 月末日、提出方法:

メール (PDF) で久野宛送るか、または紙 (神保町 2F の久野のボックス)13