トップ 最新 追記

Onion開発日記

2004|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|

ToDo:


2006-10-17

_ [Nemerle]Nemerleはじめました

以前からドキュメントやPaper読んだりして、言語仕様の概要についてはある程度調べていたが、処理系自体はほとんど触ったことが無かったので、これを機会に触ってみることにする。とはいえ、普通のプログラムを書いてもつまらんので、Nemerleの特徴であるマクロを使ったプログラムを中心に書いていこうと思う。

階乗計算

まずは、C++などのメタプログラミングの話になると必ずと言っていい程でてくる階乗計算計算の例。

fact.n

macro fact(n :int){
  def f(n){
  | 0 => 1
  | _ => n * f(n - 1)
  }
  <[ $(f(n) :int) ]>
}

macroキーワードでマクロを定義し、それに続いてマクロ名としてfactを、仮引数としてnを指定している。その後に:intとあるのは、このマクロが引数として整数定数を取ることを表している。

本体の方では、まず普通に階乗を計算する関数fをパターンマッチを使って定義し、それをマクロ中で呼び出すことで、コンパイル時計算を実現している。ここで、<[]>はLisp系言語のquoteに相当し、$()はunquoteに相当する。

このマクロを使用するコードは次のようになる。

use_fact.n

using System;
Console.WriteLine("fact({0}) = {1}", 5, fact(3));

これで一応コンパイル時に階乗が計算できているわけだが、これだけだと、実際にコンパイル時に計算されているかどうかがわからない。そこで、実際にコンパイル時に計算した階乗の値を印字するようにしてみる。今度は、コードは次のようになる。

fact2.n

macro fact(n :int){
  def f(n){
  | 0 => 1
  | _ => n * f(n - 1)
  }
  def r = f(n);
  Console.WriteLine("fact({0}) = {1}", n, r);
  <[ $(r :int) ]>
}

上のuse_fact.nをこのマクロを使ってコンパイルすると、コンパイル時に

fact(5) = 120

と表示される。

さて、これだけでコンパイル時に階乗を計算することはできているわけだが、このマクロには、fact(n)のように変数を渡してマクロを呼び出すとコンパイルエラーになってしまうという問題がある。そこで、渡されたのが定数でなければ実行時に計算するように改良してみる。

fact3.n

public class Fact {
  public static f(n :int) :int {
  | 0 => 1
  | n => n * f(n - 1)
  }
}
macro fact(n){
  match(n){
  | $(m :int) => { 
      def r = Fact.f(m);
      Console.WriteLine("fact({0}) = {1}", m, r);
      <[ $(r :int) ]>
    }
  | _ => <[ Fact.f($n) ]>
}

今度はわざわざクラスを定義して、その中のstatic関数を間接的に呼び出すようになっているが、これは階乗を実行時に計算するパターンのときに、マクロの内部で定義した関数を呼び出す形式に展開するようになっていると、マクロの呼び出し側で関数名が見つからずに、コンパイルエラーになるためだ。

このプログラムで、ちゃんと変数を引数に取るようなマクロの呼び出しが実行時に計算されることを確かめるために、次のようなサンプルプログラムを書いた。

use_fact3.n

using System;
Console.WriteLine("fact({0}) = {1}", 5, fact(5));
def n = 10;
Console.WriteLine("fact({0}) = {1}", n, fact(n));

このプログラムをコンパイルすると、コンパイル時には

fact(5) = 120

とだけ表示され、fact(n)の方は実行時に計算されていることがわかる。

正規表現リテラル

階乗のコンパイル時計算だけだとアレなので、今度はもうちょっと実用的に役に立ちそうなマクロを作ってみる。作るのは、コンパイル時に正規表現が正しいかどうかチェックして、正しく無い場合コンパイルエラーにするマクロ。

正規表現が正しいかどうかのチェックは、自前でやることもできるが、既に.NET Frameworkに正規表現ライブラリがあるのでそれを利用して、サクっと作ってしまうことにする。以下がそのコード。

pattern.n

using System.Text.RegularExpressions;
macro pattern(s){
  match(s){
  | <[ ($s_ :string) ]> => { _ = Regex(s_); <[ Regex($s) ]> }
  | _ => <[ Regex($s) ]>
}

コンストラクタRegexは引数に渡した文字列が正しく無い場合、ArgumentExeptionをthrowするので、マクロに渡した文字列リテラル が正規表現として正しく無い場合、マクロ内でArgumentExceptionがthrowされ、その結果、コンパイルエラーになる。

このマクロを利用したコードは、次のような感じになる。

use_pattern.n

def pat = pattern("***");

このコードをコンパイルすると、コンパイル時にArgumentExceptionが発生し、コンパイルエラーになる。

本日のツッコミ(全3件) [ツッコミを入れる]

_ wqj4lf5@search.com [funny ringtones ]

_ Masd Austy [Hut is big ]

_ ifouapr@lycos.com [funny ringtones]

本日のリンク元 | 704 | 355 | 261 | 85 | 28 | 17 | 12 | 12 | 10 | 10 | TrackBack(0)

2006-10-18

_ [Nemerle]マクロ内で型チェックをする方法

Nemerleのマクロ内で式の型チェックをする方法がわからない…。 Meta-programming in Nemerleの例では、TypedExprというのを使って、式 -> 型付き式に変換してるんだが、手元のNemerleコンパイラでは使えないし。

追記:どうやらNemerle.MacrosネームスペースのImplicitCTX()マクロを呼び出すと、式に型を付けるために使えるTyperクラスのオブジェクトが取得できるっぽい。Typerクラスのインスタンスが取得できたら、あとはTyper#TypeExpr(PExpr)を使って、式に型付けを行うことができるようだ。参考: Macro tips

本日のツッコミ(全1件) [ツッコミを入れる]

_ fugcpqm@ebay.com [funny ringtones ]

本日のリンク元 | 1052 | 764 | 712 | 702 | 697 | 693 | 604 | 532 | 353 | 106 | TrackBack(0)

2006-10-19

_ [授業]システム情報工学セミナー

シス情セミナーの教室変更があったのをすっかり忘れていたおかげで、授業に出席し損なってしまった。おかげで来週までに欠席レポート書くはめにorz

_ [Java]ObjectInputStreamでカスタムクラスローダを使う

ObjectInputStream#readObject()はデフォルトでは、system classloaderを使ってクラスをロードしているようなのだが、自前のクラスローダなどを使ってクラスを読み込めないと困る事態が起きてしまった。

なんとかObjectInputStream#readObject()のときに、カスタムクラスローダを使えないか調べていたところ、ObjectInputStreamをサブクラス化して、

Class<?> ObjectInputStream#resolveClass(ObjectStreamClass)

をオーバーライドしてやれば良いらしい。参考: Can't use custom ClassLoader with ObjectInputStream

で、こんな感じでカレントスレッドのコンテキストクラスローダを使うようにすることで、ObjectInputStream#readObject()をうまく動作させることができた。

package jp.gr.java_conf.mizu.javaflow.servlet;

import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectStreamClass;
public class CustomObjectInputStream extends ObjectInputStream { public CustomObjectInputStream(InputStream in) throws IOException { super(in); }
@Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { try { return Class.forName(desc.getName(), true, Thread.currentThread().getContextClassLoader()); }catch(ClassNotFoundException e){ return super.resolveClass(desc); } } }
本日のリンク元 | 1402 | 1049 | 1043 | 950 | 860 | 765 | 700 | 687 | 686 | 676 | TrackBack(0)

2006-10-21

_ [Java][Ruby]Ruby流のブロックをJavaで実現するときの問題点

航海日誌より引用:

Ruby流のブロックをパターンで というのは何故議論されないのかな. 一応クロージャを導入しなくとも,anonymous classでできますよ(ほんのちょっと冗長だけど).前に書いたソースを再掲すると,

現在のJavaでもanonymous classでRubyのブロック相当のものを実現できるというのは賛成で特に異論は無いんですが、現在のJavaの言語仕様の範囲ではchecked exceptionに対して透過的なブロックを作るのが難しいという問題点があるんじゃないでしょうか。

例えば、keisukenさんのInputStreamBlockだと、run()の中からIOException以外のExceptionを投げることができません。runのthrows節にExceptionを追加するか、RuntimeExceptionにラップしてthrowするという解決策は考えられますが、それだと今度はchecked exceptionの利点を殺してしまうことになりかねません。あるいは、おっしゃられるように型パラメータを使って、

import java.io.*;
public class Test {
  static abstract class InputStreamBlock<E extends Exception> {
    InputStream in;
    public InputStreamBlock(InputStream in) throws IOException, E {
      this.in = in;
      try {
        run();
      } finally {
        in.close();
      }
    }
    abstract void run() throws IOException, E;
  }
  ...
}

のようにすることで、IOException以外の種類の例外を投げられるようになりますが、これではthrowsできるcheced exceptionが1種類に限定されてしまいます。実用上は1種類だけでもなんとかなるケースが多いとは思いますが、リフレクション系APIなど、いくつもの種類の例外を投げるメソッドを使った場合に困ることもあるのではないかと思います。

ちなみに、Neal GafterのブログのエントリNominal Closures for Java (version 0.2)中の 「5. Exception type parameters」で、その問題に 対する解決策が議論されています。

本日のツッコミ(全3件) [ツッコミを入れる]

_ keisuken [「Ruby流のブロックをJavaで実現するときの問題点の例外の件」についてなんですが,実はGenericsだけではだ..]

_ みずしま [たぶんわかっておられると思うのですが、念のため補足しておくと、Nominal Closures for Javaの話..]

_ keisuken [みずしまさん: > Nominal Closures for Javaの話は、Dolphinでそういうのを入れる事を..]

本日のリンク元 | 3121 | 2319 | 2221 | 2192 | 2186 | 2131 | 2089 | 1900 | 1886 | 1739 | TrackBack(0)

2006-10-27

_ [研究]ベンチマーク

4年生以降になってたぶん初めて、自分の研究のためにベンチマークを取った。しかし、Javaでベンチマークを取るのは難しい。うまくやらないと、動作途中にJITコンパイラの最適化が始まったりして、入力の長さと性能の関係がわかりにくくなる。特に今回は、入力が比較的短い場合について測定したので、なおさらだ。結局、-Xintオプションでインタプリタモードにしてベンチマークを取ってしまったorz

_ [Nemerle]言語紹介

友人が企画した(今のところ)学内のみのイベント未来会議で、Nemerleについて紹介する発表をしてきた。

自分で言うのもなんだが、誰を対象にした発表なんだかよくわからないものになってしまった。関数型言語とかマクロについてあまり知らない人を対象にするなら、基本的な部分についてもっとちゃんと説明すべきだったし、知っている人を対象にするならその辺は、はしょってしまうべきだった。結局、どっちつかずになってしまった。

本日のツッコミ(全1件) [ツッコミを入れる]

_ らふにん [そういう人たちが混在する場でしたからねぇ。 といっても、そこら辺がわからない方が少数派だったと思いますが。 ]

本日のリンク元 | 42 | 34 | 18 | 17 | 15 | 13 | 11 | 10 | 10 | 8 | TrackBack(0)

2006-10-28

_ [Java]JavaでGenericなVisitor

絶対誰かが既にやってそうだけど、あえてやってみる。

JavaでVisitorパターンを使う場合の欠点として、

  • visitメソッドに渡すコンテキスト情報を表す引数の型を、Visitorクラスを作るときに決定しなければならないため、個々のVisitorのサブクラスごとに異なる型の情報を渡すのが面倒
  • visitメソッドの返り値の型をVisitorクラスを作るときに決定しなければならないため、個々のVisitorのサブクラスごとに異なる型の値を返したり、個々のサブクラスでvisitメソッドごとに異なる型の値を返したりできない

というのがあったのだが、Java 5にはせっかくGenericsがあるんだから、この辺をもっとうまく書けるんじゃなかろうかということで、作ってみた。要点は、次の3つ。

  • Visitorインタフェースをvisitメソッドに渡すコンテキスト情報の型でパラメータ化する
  • 個々のvisitメソッドは、Visitorインタフェースで宣言する段階では、 返り値の型をパラメータ化した多相メソッドとして宣言しておく。
  • visit対象のオブジェクトのクラスでは、acceptを返り値の型とコンテキスト情報の型でパラメータ化した多相メソッドとして宣言しておく。

実際に作ってみたソースは以下。Visitorインタフェースでは、visitメソッドの返り値の具体的な型もコンテキスト情報の型も指定せず、実際に処理を行うVisitorの実装クラスCalculatorで個々のvisitメソッドの型とコンテキスト情報の型を指定できている。

しかし、この方法には欠点がある。acceptメソッドが返す値の型はVisitorの実装クラスのvisitメソッドの型に依存しているため、acceptの呼び出し結果の値を入れる変数の型を間違えると、実行時エラーになってしまうのだ。つまり、この方法を使って作ったVisitorは型安全では無くなってしまう。

ただ、コンパイラはvisitメソッドの実装箇所で、型安全じゃないという旨の警告メッセージを出してくれるので、acceptを呼び出す部分で注意さえしていれば、わりと使えるテクニックなのではないかと思う。

import java.util.*;

public class Main { public static void main(String[] args){ Exp exp = new BlkExp( new SetExp("x", new IntExp(2)), new SetExp("y", new IntExp(3)), new SetExp("z", new IntExp(4)), new MulExp(new AddExp(new VarExp("x"), new VarExp("y")), new VarExp("z")) ); Integer result = exp.accept(new Calculator(), new Env()); //String s = exp.accept(new Calculator(), new Env()); と書くと実行時にClassCastException発生 System.out.printf("{ x = 2; y = 3; z = 4; (x + y) * z } = %d%n", result); } }
interface Visitor<C> { <R> R visit(AddExp e, C c); <R> R visit(SubExp e, C c); <R> R visit(MulExp e, C c); <R> R visit(DivExp e, C c); <R> R visit(IntExp e, C c); <R> R visit(VarExp e, C c); <R> R visit(SetExp e, C c); <R> R visit(BlkExp e, C C); }
class Calculator implements Visitor<Env> { public Integer visit(AddExp e, Env env){ Integer lval = e.left.accept(this, env); Integer rval = e.right.accept(this, env); return lval + rval; } public Integer visit(SubExp e, Env env){ Integer lval = e.left.accept(this, env); Integer rval = e.right.accept(this, env); return lval - rval; } public Integer visit(MulExp e, Env env){ Integer lval = e.left.accept(this, env); Integer rval = e.right.accept(this, env); return lval * rval; } public Integer visit(DivExp e, Env env){ Integer lval = e.left.accept(this, env); Integer rval = e.right.accept(this, env); return lval / rval; } public Integer visit(IntExp e, Env env){ return e.value; } public Integer visit(VarExp e, Env env){ Integer value = env.lookup(e.name); if(value == null) throw new RuntimeException("undefined variable '" + e.name + "'"); return value; } public Integer visit(SetExp e, Env env){ Integer value = e.exp.accept(this, env); env.set(e.name, value); return value; } public Integer visit(BlkExp e, Env env){ Integer value = 0; for(Exp element : e.exps) value = element.accept(this, env); return value; } }
class Env { private Map<String, Integer> table = new HashMap<String, Integer>(); Integer lookup(String name){ return table.get(name); } void set(String name, Integer value) { table.put(name, value); } }
abstract class Exp { abstract <R, C> R accept(Visitor<C> v, C c); }
abstract class BinExp extends Exp { final Exp left, right; BinExp(Exp left, Exp right) { this.left = left; this.right = right; } }
class AddExp extends BinExp { AddExp(Exp left, Exp right){ super(left, right); } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }
class SubExp extends BinExp { SubExp(Exp left, Exp right){ super(left, right); } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }
class MulExp extends BinExp { MulExp(Exp left, Exp right){ super(left, right); } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }
class DivExp extends BinExp { DivExp(Exp left, Exp right){ super(left, right); } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }
class IntExp extends Exp { final int value; IntExp(int value){ this.value = value; } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }
class VarExp extends Exp { final String name; VarExp(String name){ this.name = name; } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }
class SetExp extends Exp { final String name; final Exp exp; SetExp(String name, Exp exp){ this.name = name; this.exp = exp; } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }
class BlkExp extends Exp { final List<? extends Exp> exps; BlkExp(Exp... exps){ this.exps = Arrays.asList(exps); } <R, C> R accept(Visitor<C> v, C c){ return v.<R>visit(this, c); } }

_ [Java]キャスト推論

引き続き、Genericsネタ。ただし、今度のは全く役に立たないが。

Javaの多相メソッドでは、メソッド呼び出しを書く際に、型引数について、メソッド呼び出しの引数や返り値を代入する変数の型からある程度推論してくれる。このことを利用すると、例えば2005-10-03の日記のような型安全なリストリテラルを簡単に作るメソッドを作ることができる。

今回のネタは、この推論機構を悪用して、どの型にキャストするのかを書かずにキャストを行うことができるメソッドを定義するというもの。

まず、キャストを行うメソッドの定義は、次のようになる。

public class Util {
  private static <P, R> R cast(P param){
    return (R)param;
  }
}

メソッドの定義は実にシンプルで、2つの型をP, Rを型引数に取り、P型の値をただ単にR型にキャストする多相メソッドcastを定義しているだけ。

一方、これを使う側のコードは次のようになる。

public class Main {
  public static void main(String[] args){
    Object o = "Hello, World";//Object型に文字列を入れておく
    String s = Util.cast(o);//Util.<Object, String>cast(o);と推論される
    System.out.println(s);
  }
}

このようにキャストする型名を書かずにキャストできるわけだが、Genericsを使う前提ならキャストはほとんど書かないし、castメソッドの呼び出し結果を変数に代入しないとちゃんと推論してくれないため、実用性はほとんど無い。

本日のツッコミ(全1件) [ツッコミを入れる]

_ testeras [People who know Bob Miller here? need icq nubmer of bob m..]

本日のリンク元 | 428 | 348 | 347 | 347 | 346 | 346 | 345 | 344 | 341 | 339 | TrackBack(0)

2006-10-29

_ [その他]体調不良

今日は一日、なんか体調が悪い。やたらに鼻水が出るし、体がだるい。風邪引いたかなあ。

本日のリンク元 | 677 | 433 | 425 | 403 | 344 | 343 | 341 | 340 | 334 | 332 | TrackBack(0)

2006-10-30

_ [Nemerle]printfマクロ

今日のお題はprintf相当のマクロ。といっても、フルスペックのprintfを実装するのは大変だし、既にNemerleには標準でPrintfマクロがあるので、仕様は最小限のものに留めておく。

具体的には、大体次のような仕様にする。

  • 変換指定子は%dのみ
  • 桁数の指定などの機能は一切無し
  • 変換指定子に対して、引数の数が多すぎたり少なすぎたりした場合は、エラーメッセージを表示する
  • 変換指定子に対して、合わない型の引数が渡された場合はエラーメッセージを表示する

仕様としては少ししょぼすぎるが、今回は、マクロ内での型チェックの方法と可変長引数のマクロの処理の方法を学ぶのが目的なので、これでいいのだ。というわけで、以下がソース。Nemerleにまだそんなに慣れていないせいもあって、予想外に行数が増えてしまった。

printf.n

using System;
using System.Text;
using Nemerle.Collections;
using Nemerle.Macros;
using Nemerle.Compiler;
using C = System.Console;
using P = Nemerle.Compiler.Parsetree;

macro Printf(format :string, params args : array[expr]){ def tokenize(f: string) : list[string] { def flag() { when(f.Length == 1){ throw ArgumentException("flag name must be specified after '%'"); } match(f[1]){ | 'd' => ("%d", f.Substring(2)) | '%' => ("%%", f.Substring(2)) | _ => throw ArgumentException( "illegal flag '" + f.Substring(0, 2) + "'" ) } } def cont() { def x = match(f.IndexOf('%')){ | -1 => f.Length | n => n } (f.Substring(0, x), f.Substring(x, f.Length - x)); } if(f.Length == 0){ []; }else{ def (token, rest) = match(f[0]){ | '%' => flag() | _ => cont() }; token :: tokenize(rest); } } def parse(tokens :list[string], args :array[P.PExpr]) :list[P.PExpr] { mutable x = 0; def exprs = List.Map(tokens, fun(t :string){ | "%d" => if(x >= args.Length){ Message.Error("not enough arguments"); <[ C.Write("%d") ]> }else{ def arg = args[x]; x++; <[ C.Write($arg : int) ]>; } | "%%" => <[ C.Write("%") ]> | _ => <[ C.Write($(t :string)) ]> }); when(x < args.Length){ Message.Error("too many arguments"); } exprs; } def exprs = parse(tokenize(format), args); <[ {.. $exprs} ]>; }

これを使う側のソースは、次のような感じになる。

use_printf.n

def a = 10;
def b = 20;
Printf("%d + %d = %d\n", a, b, a + b);

コンパイルして実行すると、無事、以下のように表示される。

10 + 20 = 30

また、次のように間違った型の引数を与えると、

def a = 10;
def b = 20;
Printf("%d + %d = %d\n", a, b, "Hello");

ちゃんとエラーメッセージが表示される。

use_printf.n:3:1:3:7: [01;31merror [0m: expected int, got string in type-enforc
ed expression: System.String is not a subtype of System.Int32 [simple require]
本日のツッコミ(全6件) [ツッコミを入れる]

Before...

_ みずしま [すみません。確かにvalidなRDFになっていなかったようなので、修正しておきました。 ]

_ testeras [People who know Bob Miller here? need icq nubmer of bob m..]

_ BALSALAZIDE-DISODIUM-1670551867 [ <h1>balsalazide disodium</h1> %pdf-1.2 <br>% <br>18..]

_ ATENOLOL--1220585207 ["next <br>generic name: ]

_ BUPROPION-HYDROCHLORIDE--1812860574 [ <h1>bupropion hydrochloride</h1> <br>bupropion <br..]

本日のリンク元 | 435 | 418 | 341 | 341 | 340 | 335 | 335 | 331 | 22 | 15 | TrackBack(0)

2006-10-31

_ [Java]Java Programming Tips - クラスローダーを自作する方法

航海日誌経由。

クラスローダを自作する方法についての簡単な紹介。クラスローダを自作したい人にとってのとっかかりとして、良いと思う。ただ、はてなブックマークでもつっこまれてたが、これだとURLClassLoaderでいいじゃんとなってしまい、独自クラスローダを作るメリットがイマイチわかりにくいのではという気もする。

ちなみに、JVM上で動作してバイトコードにコンパイルできるスクリプト言語処理系(Pnuts/Rhino/Groovy/...)は、ほとんどの場合、独自クラスローダを使ってその仕組みを実現している。Onionでもスクリプトを直接実行するモードでは独自クラスローダを作っており、ソースコードをコンパイルしたバイト列からクラスをロードして実行するようにしている。

本日のツッコミ(全2件) [ツッコミを入れる]

_ IKeJI [ライブラリインジェクション(DLLインジェクションみたいなの)ができる? ]

_ みずしま [ライブラリインジェクションという言葉が具体的に 何を指しているかによると思うけど、DLLインジェクション みたいに別..]

本日のリンク元 | 2679 | 1674 | 1670 | 1458 | 1357 | 1324 | 1019 | 1018 | 1005 | 905 | TrackBack(0)

Mizushima Kota/e-mail: i021216{at}coins.tsukuba.ac.jp/SKype ID: mizu_standard