Hatena::ブログ(Diary)

プログラミング言語を作る日記

2010-02-02 Diksam基礎文法最速マスター

[]Diksam基礎文法最速マスター

Diksamの文法一覧です。Java, C++, C#等の言語をある程度知っている人はこれを読めばDiksamの基礎をマスターしてDiksamを書くことができるようになっています。

――はい、便乗です。なにやらはてダのトップの人気記事が基礎文法最速マスターだらけになっていたので。まあ、需要があるとも思えませんが。

Diksamって何だ、って? 私が作った自作言語です。

言語が似ているだけに、下記はJava基礎文法最速マスター - いろいろ解析日記をベースとさせていただきました。

言語仕様はこちら:

http://kmaebashi.com/programmer/devlang/dls_0_4_01.html

この本の後半で扱っている言語でもあります。

1.基礎

プログラムの構成

プログラムは宣言部、トップレベル、関数定義または宣言、型定義、定数定義から構成されます。実行は、起動時に指定されたソースファイルの、トップレベルに記述された文が順に実行されます。よって、"hello, world."と表示するプログラムは単に書きます。

println("hello, world.");

Javaにおけるimportに相当するものとしてrequire宣言があります。これは宣言部(プログラムの冒頭)に記述します。

requre diksam.lang;
requre diksam.math;

ただし、diksam.langデフォルトでrequireされているので、ユーザが陽にrequireする必要はありません。

文字列を描画する関数

改行なしのprint()関数、改行ありのprintln()関数が使用できます。

print("hello, ");
println("world.");

コメント

コメントです。

// 一行コメント
/*
   複数行コメント
 */

変数の宣言

変数の宣言です。変数の宣言時にはデータ型を指定します。

// 変数
int num;

データ型

Diksamのデータ型には基本型と派生型、クラス/インタフェース、列挙型、delegate型があります。以下は基本型のデータ型です。

// int(整数)型
int num;
// double(倍精度浮動小数点)型
double value;
// boolean(論理)型
boolean flag;
// string(文字列)型
string str;

クラス、配列は参照型のデータ型です。

// File型
File f;
// 配列型
int[] array;

プログラムの実行

Diksamはコンパイルは不要です。プログラムを実行するには、コマンドラインで以下のようにします。

diksam hoge.dkm

2. 数値

数値の表現

int、double型の変数に数値を代入できます(floatはありません)。Javaとは異なり、intに小数を代入することもできます(もちろん値は切り捨てられます。Cと同じ)。

int i = 2;
int i = 100000000;

double num = 1.234;

四則演算

四則演算です。

num = 1 + 1;
num = 1 - 1;
num = 1 * 2;
num = 1 / 2;

商の求め方です。割る数と割られる数が両方とも整数の場合、計算結果の小数点以下が切り捨てられます。

num = 1 / 2;  // 0

割る数と割られる数のどちらかが小数の場合、計算結果の小数点以下が切り捨てられません。

num = 1.0 / 2;    // 0.5
num = 1 / 2.0;    // 0.5
num = 1.0 / 2.0;  // 0.5

余りの求め方です。

// 余り
mod = 4 % 2

インクリメントとデクリメント

インクリメントとデクリメントです。

// インクリメント
 i++;

// デクリメント
 i--;

前置のインクリメント、デクリメント演算子はありません。

3. 文字列

文字列の表現

文字列はダブルクォートで囲みます。

string str = "abc";

文字列操作

各種文字列操作です。

// 結合
  string join = "aaa" + "bbb";

// 長さ
  int length = "abcdef".length();

// 切り出し
  "abcd".substr(0, 2);  // ab

4. 配列

配列変数の宣言

配列です。

// 配列の宣言
int[] array;

配列の生成

配列の生成です。配列の生成時には要素数を指定するか、配列リテラルを指定します。

int [] array;

// 要素数を指定して配列を生成
array = new int[5];

// 初期データを指定して配列を生成。
// 配列リテラルの型は、最初の要素の型の配列になる。
array = {1, 2, 3};

配列の操作

配列の操作です。

// 要素の参照
array[0]
array[1]

// 要素の代入
array[0] = 1;
array[1] = 2;

size = array.size(); // サイズの取得
array.resize(10);    // サイズの変更
array.insert(3, 5);  // 挿入(array[3]の前に5を挿入)
array.remove(3);     // 削除(array[3]を削除し、詰める)
array.add(5);        // 末尾への要素追加

5. 制御文

if文

if文です。Java等とは異なり、波括弧は省略できません。

if ( 条件 ) {

}

if 〜 else文

if 〜 else文です。

if ( 条件 ) {

} else {

}

if 〜 else if 文

波括弧が省略できないので、Javaのようにelseの後に単文のifを続けることができません。そのため、予約語elsifが導入されています。

if ( 条件 ) {

} elsif ( 条件 ) {

}

while文

while文です。

int i = 0;
while ( i < 5 ) {
    
    // 処理
    
    i++;
}

for文

for文です。

for (int i = 0; i < 5; i++) {

}

なお、foreachはまだありません。

switch case文

switch caseです。CやJavaのようなfall throughにはなっていません。

  switch (a)
  case 1 {
      // aが1の時に実行される。
  } case 2, 3 {
      // aが2または3の時に実行される。
  } default {
      // aが1でも2でも3でもない場合に実行される。
  }

6.定数/関数/クラス/列挙/delegate定義

関数定義

関数定義です。

// 引数としてふたつの整数を受け取り、その和を返す関数
int sum(int a, int b) {
    return a + b;
}

// 引数として配列とインデックスを受け取り、
// その個所の要素を返す関数。
int getAt(int[] array, int index)
  throws ArrayIndexOutOfBoundsException {
    return array[index];
}

定数定義

定数はキーワードconstで定義します。

const MINUTES_A_DAY = 24 * 60;

型の指定がいらないことに注意。

なお、以下のようにfinalを使って書くこともできますが、final指定の変数は、実際にそこが実行されないと代入されません。

final int MINUTES_A_DAY = 24 * 60;

クラス定義

クラスは、以下の形式で定義します。

クラス修飾子 class クラス名
  : スーパークラス, 実装するインターフェイスの並び {
    フィールド、メソッド、コンストラクタ定義の並び
}

クラス修飾子には以下のものがあります。

  • アクセス修飾子。publicを指定するか無指定のいずれか。public指定すると別パッケージ(別ソースファイル)から参照可能となる。
  • abstract修飾子。抽象クラスを意味する。指定するかしないかのいずれか。

Diksamはクラスについては単一継承のみ可能です。また、Diksamでは、抽象クラス以外は継承できません

インタフェース定義

アクセス修飾子 interface {
    // メソッド定義の並び
}

フィールド定義

アクセス修飾子 final修飾子 型 フィールド名 初期化子;

フィールドに対するアクセス修飾子は、public、 private、もしくは無指定です。 publicは別のパッケージから使用可能、無指定は同一パッケージ内から使用可能、 privateはクラス内のみで使用可能であることをそれぞれ意味します。

finalを指定することで、そのフィールドは代入不能となります。

Diksamにはstaticなフィールドはありません。

メソッド定義

メソッド修飾子 型 メソッド名 (パラメータの並び) throws節 {
    文の並び
}

関数定義と同様、throws節は省略可能です。また、abstract修飾子を付けた場合、

関数本体(文の並びを含むブロック)は記述できません。

メソッド修飾子には以下のものがあります。

  • アクセス修飾子。public、private、 無指定のいずれか。
  • abstract修飾子。 指定するかしないかのいずれか。
  • virtual修飾子、またはoverride修飾子。

Diksamのデフォルトはnon virtualです。virtual指定のないメソッドはオーバーライドできない。また、オーバーライドする場合は、必ずoverrideを指定しなければなりません。

Diksamにはstaticなメソッドもありません。

コンストラクタ

Diksamでは、コンストラクタの定義には、constructor修飾子を使用します。

public constructor initialize() {
    // コンストラクタの処理を記述する。
}

constructor修飾子は、型名と同じ場所に記述します。よってpublicやvirtual等のメソッド修飾子よりは後ろに記述しなければなりません。

constructor修飾子により、Java, C++, C#等とは異なり、コンストラクタに任意の名前を付けることができるようになっています。オーバーライドも可能です。インスタンスをnewする際に、以下の形式でコンストラクタを指定します。

  // myinit()はユーザが作成したコンストラクタ
  Point p = new Point.myinit(x, y);

メソッド名を指定せず、単にnew Point(x, y)のように記述した場合、 initializeという名前のコンストラクタが呼び出されます。

列挙型

列挙型は以下のように定義します。

enum Fruits {
  APPLE,
  BANANA,
  ORANGE,
};

使うときは以下のように参照します。

  Fruits f = Fruits.ORANGE

delegate型

delegate型は関数を指す参照型です。予約語delegateの後ろに関数シグネチャ宣言と同じ形式で定義します。

  delegate boolean MouseButtonClicked(int x, int y, ButtonKind kind);

式の中に関数名を単に記述することで、関数型の値が生成されます。関数型の値は、代入互換性のある delegate型変数に代入可能できます。関数引数として渡したり、戻り値として受けることもできます。

boolean mouseButtonHandler(int x, int y, ButtonKind kind) {
    ……
} 

// buttonオブジェクトのsetMouseButtonHandlerメソッドに、
// mouseButtonHandler関数を引数として渡す。
button.setMouseButtonHandler(mouseButtonHandler);

delegate型変数には、関数だけでなくメソッドも代入可能です。その場合、delegate型変数は、メソッドに対応するインスタンスを併せて保持します。

関連記事

Perl基礎文法最速マスター - サンプルコードPerl入門

Route 477(2010-01-25)

PHP基礎文法最速マスター - Shin x blog

Python基礎文法最速マスター - LazyLife@Diary

VBA基礎文法最速マスター - いろいろ解析日記

Java基礎文法最速マスター - いろいろ解析日記

Bash基礎文法最速マスター - いろいろ解析日記

Haskell基礎文法最速マスター - think and error

Brainf*ck基礎文法最速マスター - このブログは証明できない。

VBScript 基礎文法最速マスター - CX's VBScript Diary - VBScript グループ

JavaScript基礎文法最速マスター - gifnksmの雑多なメモ

untitled - notes plastiques

2010-01-14

[][][]例外処理について、私はこう思う

他人の意見の翻訳ばかりでもアレなので。

例外処理機構についての私の考え方は、Diksamがそうなっているように、

  1. 例外処理機構自体は必要
  2. 検査例外も必要

というものです。

例外処理自体の有用性について

Joel Spolsky氏の記事の翻訳の感想でも書いたのですが、例外処理機構は必要だと思います。「戻り値でちまちまエラーケースを上位に戻していってうまくいくと思えるほど、私は(自分を含む)プログラマを信用していない」ためです。

Joel氏は

例外を上げるかもしれない関数を呼び、それをその場でcatchしないときはいつも、あなたは、データを整合性のない状態にしたまま突然中断された関数や、あなたが考慮しなかった別のコードの実行経路による驚くようなバグが発生する機会を作り出しているのだ。

と書いていますが、こういうケースは実際に存在します。たとえば(これは「プログラミング言語を作る」に書いた例ですが)何らかのツリー構造を作っているとして、

  node.childlen = new Node[5];
  for (i = 0; i < 5; i++) {
      node.children[i] = new Node();
  }

子のノードを5個追加しようとしていますが、これが3個目で失敗した場合、3回目の「new Node()」で例外が発生することになります。この例では、親ノードに対し「node.childlen = new Node[5];」を先に実行してしまっていますから、node.children[3]以降がnullになることになります。データ構造上、これを許さないケースは多いでしょう。子がいないなら、node.childrenはnullにするとか、長さ0の配列を割り当てておくのが普通です。たとえば以下のように書く必要があるはずです。

  Node[] temp = new Node[5];
  for (i = 0; i < 5; i++) {
      temp[i] = new Node();
  }
  // node.chlidrenを最後まで触らないので、
  // node.childrenが子がいないことを示す状態で初期化されていれば、
  // 例外が発生しても、データの不整合を起こすことはない。
  node.childlen = temp;

でも、例外処理機構を使っていると、こういうところの検証がえらく難しい、というのがJoel氏の指摘なのだと思います。

しかし、じゃあリターンコードでステータスをちまちま上位に返していけばこういうところが万全になるかというと、私にはちょっとそうは思えない。化数やメソッドの呼び出し階層を、「上司が部下に仕事を依頼する」ことにたとえるとするならば、リターンステータスで異常を返すことは、部下が上司に異常を報告していることを意味します。それは結構なのですが、上司は、いとも簡単にそれをもみ消すことができる。しかも、リターンステータスをいちいちチェックしなければ「自動的にもみ消す」わけですから、これはデフォルトがもみ消すほうに振ってある状態です。こういうやり方では、下っ端が、たとえば原子炉の放射能漏れに気付いたりとかしても、上司から上司へ報告しているうちにいつの間にかもみ消されてしまうのが目に見えています。とても推奨できない。

例外処理機構があれば、(検査例外の有無に関わらず)例外を「もみ消す」時には、どこかの階層で、明示的に「もみ消す」コードを書かなければなりません。例外をもみ消すのなら、陽に、その階層の責任においてやれよ、ということかと思います。もっとも、Javaでも、Exceptionを捕まえる空のcatch節を書いて、(Error以外の)あらゆる例外をもみ消すコードは山ほど見ましたから、いっそ例外クラスはnewされた時点で処理系レベルで強制的にログぐらいは吐いたほうがよいのかもしれませんね。

検査例外の話

検査例外と言うか、Javaで言うところのExceptionとRuntimeExceptionの使い分けの話になりますが。

これについては、私はかつて「Java謎+落とし穴徹底解明」で以下のようなことを書きました。

  1. Errorは、「回復が難しいか不可能」なところに使えとJLSに書いてある。
  2. RuntimeExceptionは、「どこでも発生し得るからいちいちthrowsに書いてはいられないが、アプリケーションがcatchする可能性がある例外」、ぶっちゃけバグ*1
  3. それ以外のExceptionは、「ユーザの誤操作など、プログラムバグがなくても発生する可能性があり、かつそれに対して必ずプログラムがきちんと対応しなければならない場合に使うべき」

この考えは今も変わっていません。

おそらく同じようなことを、赤間さん*2は以下のように書いておられます。

.NETとJavaの例外処理の違い – とあるコンサルタントのつぶやき

Java には検査例外と実行時例外と呼ばれる 2 種類の例外が存在しており、言語仕様として業務エラーを例外(検査例外)として取り扱える仕組みを持っている

  • メソッドシグネチャとして throws 句を書かないとダメ。
  • 呼び出し側で try-catch を書かないとダメ。

この 2 つの特徴は、要するにこの検査例外が、「必ず処理ルートとして考慮しなくちゃいけないケースである」ということを意味しており、この特徴はそのまま業務エラーに当てはまります。つまり、業務エラーとはそもそもどのようなものだったのかというと、

メソッド側(上の例でいうと BC 側)では、インタフェース仕様(メソッド仕様)の一部として定義しなければならないもの。

呼び出し側(上の例でいうと UI 側)では、必ず後処理してメッセージなどを表示しなければならないもの。

でした。CLR 系言語(C#VB)では、言語仕様としてこのような業務エラーを体系的に取り扱える仕組みがないため、やむなく enum 値や構造体クラスなどを使って業務エラーを表現していたのですが、Java の場合には、検査例外を使えば言語仕様として業務エラーを体系的に取り扱える、ということになります。

これについてはまったく同意で、まさにわが意を得たり! と思ったのですが――

JavaのInteger.parseInt()について以下のような記述があって、

さらにつぶやいておくと、このことからわかるように、基本的な考え方として、汎用クラスライブラリ戻り値を設計する際には、

「業務エラーかどうかが状況次第で変わるものについては、かたっぱしからアプリケーションエラーに倒して設計しておくべき。」

なのだと思います。この点に関しては Java のクラスライブラリには問題があって、特に I/O 系の業務エラー(RemoteException や SQLException)が片っ端から検査例外として実装されてしまっているのはかなり困りものなのですよね....。(実装コードが非常に書きにくくなるため。これは Java をいじっているときの不満事項の一つでした。) 検査例外が .NET にないのは悔しいものの、不適切な検査例外の利用は逆にデメリットにもなるので、この辺のトレードオフが悩ましいところです。

これには首をひねったのでした(私は、5冊しかない著書の実に2冊において、「NumberFormatExceptionが検査例外じゃないのはどう考えても設計ミスだろ、ということを書いていますので)*3

「安全側に倒す」なら、例外をもみ消してはいけない。前述の通り、例外をもみ消すなら、陽に、その階層の責任においてやれよ、と私は思いますので。

それよりも

いやさ、JavaとかC#とか、ついでにDiksamとかの例外処理機構の使いにくさは、検査例外云々よりもむしろ、catch節の書きにくさにあると思うのですが…… 例外A, B, Cが飛んでくる可能性があって、どのケースでもとりあえずログは吐きたい、かつCのケースだけ特別な処理を書きたい、という場合、現状のtry catchの構文だと、ログを吐くコードを3つのcatch節にコピペする必要があります*4。面倒だからExceptionをcatchして、catch節の中でinstanceofで処理を分けたくなります。このへんをうまく書ける構文があれば、Diksamにも採用したいと思うのですけれど。

書ききれなかった

検査例外を不要とする主張に対する反論として書きたいことがあったのですが*5、眠いのでギブアップです。数日中に書きます。

あと、まあ、あれだ。

宣伝ですが、ぜひこちらも。

*1:というわけでDiksamには、RuntimeExceptionはありませんが、BugExceptionというクラスを処理系が提供しています。これは本来catchを禁止すべきだと思うのですが、アプレットバグでWebブラウザが死んだり、サーブレットバグサーブレットコンテナが死んだりしては困るのでcatch可能としています。

*2:.NETに関係すれば絶対にこの方の本を読むことになります。

*3:それはそれとして、「アプリケーションエラー」といえば一般には「業務エラー」を指すのではないでしょうか。システムエラーとアプリケーションエラーの違いがよくわかりません。.NET Frameworkにおいてはいつの間にかApplicationExceptionの継承が非推奨になっていますが、Diksamでは、ApplicationExceptionクラスは存在し、かつそれは検査例外です。

*4:なお、例外を投げる側の想定と、それを使う側の想定はたいてい食い違うので、例外のクラス階層はcatchする側ではあんまり役に立ちません。

*5:や、たいしたことじゃないです。今まで書いているのと同じことです。

2009-11-02 継承ツリーの最上位クラス

[]継承ツリーの最上位クラス

カメよりも遅い歩みですがいちおうDiksamは機能追加を続けているので、たまには言語の話も。

JavaC#には、継承ツリーの最上位クラスとして「Object」がいます。型なし言語にもたいていは同様のクラスがありますね。

C++にはありませんが、それは、template(JavaでいえばGenerics)がその役目を果たしているから、ということであると私は理解しています。

ではDiksamではどうすべきか。まず、Objectというのは、「何でも指せる参照」であり、Cで言えばvoid*に相当する最も邪悪なポインタです。なるべくならこんなものの導入は避けたい。でも、templateやGenericsを実装するのはひとりでやるのは骨なので、残念ながらそう簡単に導入できるとは思えない。

じゃあ実利的な意味からObjectを導入するか、と、そこまではいいのですが、JavaのObjectの仕様にはもうひとつ気になっているところがあります。

かつて、JavaHouseの以下の記事あたりで話題になったのですが、

no title

たとえばHogeというインタフェースがあったとき、「Hoge hoge;」として宣言された変数hogeについては、なぜかObjectのメソッドであるtoString()等が呼べます。もちろん、実際にインスタンス化されたとき、そのオブジェクトは絶対にObjectのサブクラスであるわけですが、インタフェースであるHogeのクラス階層を辿ってみてもObjectは登場しない。にも関わらず「Hoge hoge;」と宣言されたhogeのtoString()が呼べてしまうのは、言語仕様上特別扱いされているからであって、私もやっぱり「汚い仕様ですよね」と思います。

no title

ここはやっぱりJavaHouseで議論されているように、Objectableインタフェースを導入のうえ、すべてのインタフェースとObjectクラスが暗黙にObjectableをimplementsするようにするのがよいのか、あるいはObjectableインタフェースを導入しすべてのインタフェースとクラスが暗黙にObjectableをimplementsするようにすればObjectなんか要らないのか、その辺をどうすべきか逡巡しているところです。

2009-06-28 Diksamウイジェットセット

[]Diksamウイジェットセット

いつまでもエロゲの話が上がっているのも何なので、Diksamの現状報告など。

現在のDiksamでは、ここで書いたように、ウインドウを開いて文字や線等の図形や画像を表示することができます。

プログラミング言語を作る/Diksam on Windows

ただ、実用上はボタンのようなGUIも必要なので、ウイジェット(Windows用語ならコントロール、Java用語ならコンポーネント、か?)を使えるようにしようと思っています。

f:id:kmaebashi:20090628105137p:image

以下、わかる人ならたぶんわかる簡単な説明。

  1. 上部はPanel, 下部はCanvasであり、このふたつのレイアウトをBorderLayoutが管理しています。BorderLayoutの仕様はJavaのそれに似ているので、ウインドウをリサイズすれば、Canvasの幅と高さ、およびPanelの幅が変動します。
  2. Panel内のレイアウトはHBoxLayoutが管理しており、そこにボタンを2つ配置しています。
  3. ボタンそのものの大きさは、内部の文字列に合わせて自動的に調整されます。「大き目のボタン」は、ボタン作成後、陽に高さだけいじっています。

こんなのを、「Cによる無理やりオブジェクト指向」で作り、Diksam向けのラッパーを書いて……いるところで正月休みが終了し、以後半年放置していたのでした。今年に入ってからの私の生活の荒みっぷりが想像出来ようというものです。

今後はぼちぼちすすめて行きたいと思いますので、どうぞよろしくお願いいたします。

それはさておき、「Cで無理やりオブジェクト指向」はやっぱりやるもんじゃないなあ、と、過去にも何度かやってるくせに改めて教訓を得たのでした。

2009-01-09 Diksam ver.4.0.03を公開しました

[]Diksam ver.4.0.03を公開しました

Diksamのバグフィックスのため、ver.0.4.03を公開しました。

http://kmaebashi.com/programmer/devlang/diksam_on_windows.html

修正点は以下の2点です。