ToDo:
以前からドキュメントや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が発生し、コンパイルエラーになる。
Nemerleのマクロ内で式の型チェックをする方法がわからない…。 Meta-programming in Nemerleの例では、TypedExprというのを使って、式 -> 型付き式に変換してるんだが、手元のNemerleコンパイラでは使えないし。
追記:どうやらNemerle.MacrosネームスペースのImplicitCTX()マクロを呼び出すと、式に型を付けるために使えるTyperクラスのオブジェクトが取得できるっぽい。Typerクラスのインスタンスが取得できたら、あとはTyper#TypeExpr(PExpr)を使って、式に型付けを行うことができるようだ。参考: Macro tips
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); } } }
航海日誌より引用:
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」で、その問題に 対する解決策が議論されています。
絶対誰かが既にやってそうだけど、あえてやってみる。
JavaでVisitorパターンを使う場合の欠点として、
というのがあったのだが、Java 5にはせっかくGenericsがあるんだから、この辺をもっとうまく書けるんじゃなかろうかということで、作ってみた。要点は、次の3つ。
実際に作ってみたソースは以下。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); } }
引き続き、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メソッドの呼び出し結果を変数に代入しないとちゃんと推論してくれないため、実用性はほとんど無い。
今日のお題はprintf相当のマクロ。といっても、フルスペックのprintfを実装するのは大変だし、既にNemerleには標準でPrintfマクロがあるので、仕様は最小限のものに留めておく。
具体的には、大体次のような仕様にする。
仕様としては少ししょぼすぎるが、今回は、マクロ内での型チェックの方法と可変長引数のマクロの処理の方法を学ぶのが目的なので、これでいいのだ。というわけで、以下がソース。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]
_ みずしま [すみません。確かに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..]
航海日誌経由。
クラスローダを自作する方法についての簡単な紹介。クラスローダを自作したい人にとってのとっかかりとして、良いと思う。ただ、はてなブックマークでもつっこまれてたが、これだとURLClassLoaderでいいじゃんとなってしまい、独自クラスローダを作るメリットがイマイチわかりにくいのではという気もする。
ちなみに、JVM上で動作してバイトコードにコンパイルできるスクリプト言語処理系(Pnuts/Rhino/Groovy/...)は、ほとんどの場合、独自クラスローダを使ってその仕組みを実現している。Onionでもスクリプトを直接実行するモードでは独自クラスローダを作っており、ソースコードをコンパイルしたバイト列からクラスをロードして実行するようにしている。
_ wqj4lf5@search.com [funny ringtones ]
_ Masd Austy [Hut is big ]
_ ifouapr@lycos.com [funny ringtones]