Java : 述語論理
[id:m-hiyama:20060411:1144727533]について
Javaは型付けやらなんやらで、こういう説明をするには向いていないとはいえ、これはあまりにもアレではないですか。もうちょっとJavaを賢く使ってやらないとかわいそうな気がします。
ぱっと見て、
- static importを使う
- メソッド名を工夫する
- staticフィールドを使う
ことでかなり見やすくなるはずです。
では、やってみましょう。まずは、Predicateクラス。staticフィールドとstaticメソッドを必要なだけ列挙しておきます。また、Predicateだけで何をするクラスかすでに分かっているので、メソッド名はできる限り簡潔にします。ここではHaskell風に「$」を使ってみます。
package logic; public abstract class Predicate<X> { public abstract boolean $(X x); public static final Predicate<Integer> positive = new Predicate<Integer>() { public boolean $(Integer x) { return x > 0;} }; public static final Predicate<Integer> zero = new Predicate<Integer>() { public boolean $(Integer x) { return x == 0;} }; public static final Predicate<String> empty = new Predicate<String>() { public boolean $(String x) { return x.equals("");} }; public static<X> Predicate<X> and (final Predicate<X> p, final Predicate<X> q) { return new Predicate<X>() { public boolean $(X x) { return p.$(x) && q.$(x);} }; } public static<X> Predicate<X> or (final Predicate<X> p, final Predicate<X> q) { return new Predicate<X>() { public boolean $(X x) { return p.$(x) || q.$(x);} }; } public static<X> Predicate<X> not (final Predicate<X> p) { return new Predicate<X>() { public boolean $(X x) { return ! p.$(x);} }; } }
次にテスト実行クラス。static importでスタティックフィールドをインポートしておきます。なお、System.out.println()をprintln()メソッドに置き換えるのは、単に短く書きたいだけではなくて、出力先やデコレーションを変更したいときのことを考えてのことです。
import logic.Predicate; import static logic.Predicate.*; public class PredicateLogicDemo_01 { public static void println(String s) { System.out.println(s);} public static void main(String[] args) { Predicate<Integer> positiveOrZero = or(positive, zero); Predicate<String> notEmpty = not(empty); int[] intData = {1, 0, -2, 3, -1}; String[] strData = {"Hello", "", "World"}; for (int n : intData) { boolean truth = positiveOrZero.$(n); println("PositiveOrZero " + n + " = " + truth); } for (String s : strData) { boolean truth = notEmpty.$(s); println("NotEmpty " + s + " = " + truth); } } }
Predicateクラスはまだまだアレですが、テスト実行クラスの方はJavaScriptと比べて全然遜色ないくらい簡潔になっているではないですか。せめてこのくらいのコードを書いてから、「述語論理はJavaScriptを使うべきだった」と言って欲しいです:p
Concurrent Clean : 再帰を繰り返しに : wget
昨日の話([id:lethevert:20060413:p1])は、以前にwgetのようなプログラムをCleanでどのように書くかという話([id:lethevert:20051126:p1]、[id:lethevert:20051124:p1])をした時の話につながります。
実際には動かないCleanプログラムの例として、次のようなものを挙げていました。
// Webはリンクでつながった再帰的な構造をしたデータとして表現される ::Web = Web WebPage [Web] getWeb page = WebPage page (map getWeb pages) where pages = getLinkedPages page // wgetの本体 wget firstpage = saveWeb 0 www where // Webのデータを、無限に再帰的なデータとして、先に宣言しておく www = getWeb firstpage // 宣言されたデータを使って、wgetの処理を書く。 saveWeb MAXDEPTH _ = done saveWeb depth (Web page links) # printWebPage page = map (saveWeb (depth+1)) links
これは、昨日と同型の問題なので、同じような発想が適用できます。
::Web = Web WebPage [(*Socket -> *(Web, *Socket))] getWeb :: Url *Socket -> (Web, *Socket) getWeb url socket # (page, socket) = browse url socket urls = getLinkedUrls page = (Web page (map getWeb urls), socket)
という形で、先にデータの取得部分を用意して、
saveWeb :: Int Web (*Socket, *File) -> (*Socket, *File) saveWeb MAXDEPTH (socket, file) = (socket, file) saveWeb depth (Web page links) (socket, file) # file = printWebPage page file (linkedWebs, socket) = mapU links socket = reelU (map (saveWeb (depth+1)) linkedWebs) (socket, file)
というようにsaveWebの処理を書きます。ここでは、saveWebの部分は再帰的に書きましたが、これも同じような形に展開して、saveWebの中から処理の流れ(制御)を外側に取り出すことも可能です。
このようにすることで、副作用を排除しながら、モジュール化されたプログラムを書くことが可能です。これは、おそらく、もっと汎用的なパターンにまとめられるのではないかと思います。