2009年9月30日水曜日

日本史のサマリ

何となく英語の年表を探していたら、80個程度の少ない項目で日本史の概略をまとめてあるのを見つけた。→リンク
見慣れた日本史のキーワードが英文で書かれているのを見ると、何か新鮮で面白い。結構覚えているのも多いが、量も手ごろな事だし、この際西暦を全部覚えてしまう事にした。
Japanese Chronological Table
{ "height":160,"width":400,"opacity":0.9,"answer-bg-color":"#FFEEDD","question-bg-color":"#FFEEDD","answer-color":"#006622","question-color":"#002266","caption":"暗記帳 アルファ版"}


Start of human activity on the Japanese archipelago.

ca.100,000B.C.



Spread of Late Paleolithic Culture.

ca.30,000B.C.



First use of pottery on Japanese archipelago.

ca.10,000B.C.



First production of clay images.

ca.7,000B.C.



First large Jomon communities.

ca.3,000B.C.



Establishment of Yayoi Culture in northern Kyushu. Start of rice cultivation.

ca.300B.C.



King Na of Wa (Japan) sends tribute to Eastern Han dynasty and is bestowed gift of a gold seal.

57 A.D.



First construction of large graves with burial mounds in western Japan.

ca.200



Queen Himiko of Yamatai in Wa sends emissaries to Wei in China.

239



Keyhole-shaped burial mounds are constructed in many parts of western Japan.

ca.300



Wa sends troops to the Korean peninsula (monument of Haotai Wang).
Religious rites begin around this time on Okinoshima off the coast of Kyushu.

391



King of Wa starts in this year to send tribute to the Southern Court in China.

421



Forging of the iron swords later unearthed at the Inariyama Burial Mound in Saitama Prefecture.

471



Buddhism introduced from Paekche on the Korean peninsula (538 according to one theory).

552



King of Wa sends emissaries to the Sui Dynasty in China.

600



Prince Shotoku creates the Seventeen-Article Constitution.

604



First emissaries sent to the Tang Dynasty.

630



Taika Reforms.

645



Japanese troops defeated by troops of Tang and Silla on the Korean peninsula. Defenses of western Japan are strengthened.

663



Completion of the Taiho Ritsuryo Code. Establishment of a state based on the Ritsuryo laws.

701



Capital is moved to Heijokyo (Nara).

710



Construction of Tagajo Castle in Tohoku region (monument of Tagajo) .

724



Completion of the Great Buddha at Todaiji Temple.

752



Belongings of Emperor Shomu moved to Todaiji. Construction of Shosoin.

758



Hyakumanto Darani (Dharani of the Million Towers) is completed.

770



Transfer of the capital to Heiankyo (Kyoto).

794



Practice of sending emissaries to China is discontinued.

894



Fujiwara Michinaga takes hold of power. Flourishing of the Fujiwara family.

995



Murasaki Shikibu completes The Tale of Genji.

1005



Sutra mounds created in great numbers out of beliefs that Mappo (The Age of Decline) has started.

1052



Minamoto Yoritomo establishes his headquarters in Kamakura.

1180



Yoritomo becomes Seii tai shogun ("Barbarian Conquering General") and establishes the Kamakura shogunate.

1192



Monk Shinran founds the Jodo Shinshu Sect (Ikko Sect).

1224



Monk Ippen founds the Ji Sect.

1275



Collapse of Kamakura shogunate. Kamakura enveloped in the flames of war.

1333



Ashikaga Takauji becomes Seii tai shogun and founds the Muromachi shogunate.

1338



Japanese pirates begin raid along China's coastline.

1350



Ashikaga Yoshimitsu restores relations with Ming China. Ashikaga is recognized by the Ming court as the "King of Japan".

1401



Sho Hashi unites all of Okinawa and becomes the Ryukyu King.

1422



Do ikki uprising develops in Kyoto and results in the Tokusei edict. Tea ceremony begins to flourish at this time.

1441



Onin War. Kyoto is enveloped in flames of war.

1467



Ikko ikki breaks out in Kaga and assumes control of the entire Kaga Province.

1488



Asakura Norikage quells the Ikko ikki.

1531



Date Tanemune enacts the Jinkaishu which established domain. Daimyo in different areas begin at this time to enact their own legal systems.

1536



Portuguese bring guns to Tanegashima.

1543



Francis Xavier arrives in Japan to begin missionary activities.

1549



Fall of the Asakura daimyo family in Ichijodani, Echizen Province.

1573



Toyotomi Hideyoshi unites all of Japan.
Start of printing of Christian books in Romanized Japanese.

1590



Start of the system of Shuinsen or "red seal ships". Hideyoshi sends troops to Korea.

1592



Tokugawa Ieyasu becomes Seii tai shogun, establishes the Edo shogunate.

1603



Ieyasu has bronze movable type forged to print the Suruga edition.

1607



The hishigaki kaisen (lozenge-fenced cargo ships) begin to sail regularly between Edo and Osaka.

1619



Shogunate forbids Japanese to travel overseas.
Start of the sankin kotai system which required daimyo to reside alternate years in Edo.
Construction of national roads.

1635



Entry of Portuguese ships forbidden. Start of sakoku, a period in which Japan was closed off to the outside world.

1639



Dutch Trading Mission is moved to Dejima in Nagasaki which becomes the only port in Japan where foreign trade is allowed.

1641



Promulgation of the Keian no ofuregaki, a document outlining the duties and conduct of the farmers.

1649



Great Edo Fire. Following this, large spaces are created in the city to prevent further fires.

1657



Ainu rebellion in Ezochi (Hokkaido).

1669



Kawamura Zuiken opens eastern sea route. Western sea route is opened in following year.

1671



Start of Genroku Period. Development of Edo culture including Kabuki and Bunraku.

1688



Kyoho Famine. Rice stores broken into as prices on rice soar.

1732



Russian ships land in Ezochi, request trade.

1778



Ino Tadataka produces first accurate maps of the Japan's coastline.

1821



Franz von Siebold, a German physician of the Dutch Trading Mission, is banished from Japan for bringing maps of Japan out of the country.

1828



Admiral Perry arrives in Uraga and demands that Japan opens its ports.

1853



Japan concludes friendship treaties with the United States, Britain, Russia, France and the Netherlands. The ports of Hakodate, Shimoda and Nagasaki are opened to foreign trade.

1854



Meiji Restoration. Edo's name is changed to Tokyo ("Eastern Capital") .

1868



The Ryuku Islands become Okinawa Prefecture.

1879



Promulgation of the Constitution of the Empire of Japan.

1889



Outbreak of Russo-Japanese War.

1904



Korea becomes a Japanese colony.

1910



Japan presents China with a set of 21 demands for expanding its rights in China.

1915



Start of a financial panic. Government declares moratorium on gold standard.

1927



The so-called Liutaochu Incident is used as a pretext for the start of military maneuvers in northeast China. Start of invasion of China.

1931



Start of Pacific War.

1941



Promulgation of the Constitution of Japan. Agrarian reforms.

1946



Signing of the Treaty of San Francisco.

1951



Enactment of the Basic Agricultural Law.

1961



Start of the New Tokaido Railway (Shinkansen). Beginning of free economic system.

1964



Outbreak of itaiitai sickness in Toyama Prefecture (first case of pollution related diseases due to industrial wastes).

1967



一通りやってみると、「この項目はあるのにあれは無いな」とか、「本当にこの年でいいんだっけ」とか、いろいろ思う事もあったが、まあ良しとした。

2009年9月27日日曜日

Scala で待ち行列

しばらく Scala を触っていなかったので思い出す目的と、Actor を試してみる目的で、以前Erlang で書いた待ち行列のシミュレーションを Scalaで書き換えてみる事にした。
package exercise.actors

import scala.actors.Actor
import scala.actors.Actor._
import scala.collection.mutable.Queue;
import scala.actors.TIMEOUT  

case class Done(diff:Long)

object ExpRandom {
 def rand(m:Double):Double = {
      -m * Math.log(Math.random)
 }
}

class Service(observer: Observer) extends Actor {
  val μ = 0.5 //平均サービス率: 平均サービス時間2秒の逆数
  val queue = new Queue[Long]
  def serviceTime(): Double = {
    ExpRandom.rand(1.0 / μ)
  }
  val worker: Actor = new Actor {
    def act() {
      loop {
        receive {
       case 'serve =>
         Thread.sleep(Math.round(serviceTime() * 1000))
         sender ! 'afterProcessed
     }
      }
    }
  }
  worker.start
  def act() {
    var inService: Boolean = false
    loop {
      receive {
        case 'customerIn =>
          queue.enqueue(System.currentTimeMillis())
          Console.println("queue.length=" + queue.length)
          self! 'doService
        case 'afterProcessed =>
          inService = false
          queue.dequeue
          self! 'doService
        case 'doService =>
          if (!inService) inService = doService()
      }
    }
  }
  def doService(): Boolean = {
 if (queue.isEmpty) false
    else {
      val elapsed = System.currentTimeMillis() - queue.front
      observer! Done(elapsed)
      worker! 'serve
      true
 }
  }
}

class Customer(service: Service) extends Actor {
  val λ = 0.4 // 毎秒0.4個のリクエストが到着
  var interval: Double = 0

  def arrivalInterval(): Double = {
 ExpRandom.rand(1.0 / λ)
  }

  def act() {
    loop {
      receiveWithin(Math.round(interval * 1000L)) {
        case TIMEOUT =>
          service! 'customerIn
          interval = arrivalInterval()
      }
    }
  }
}

class Observer extends Actor {
  var requestCount:Int = 0
  var totalWaitingTime:Double = 0
  def act() {
    loop {
   receive {
     case Done(time)=>
          totalWaitingTime += time
          requestCount += 1
       Console.printf(
         "リクエスト%d; 待ち時間:%dms; 平均待ち時間=%dms;%n", 
         requestCount, 
         time, 
            Math.round(totalWaitingTime / requestCount))
       }
    }
  }
}

object QueueTheoryTest extends Application {
  val observer = new Observer
  val service = new Service(observer)
  val customer = new Customer(service)
  
  observer.start
  service.start
  customer.start
}
(シンボルを表すアポストロフィが文字列をくくるクオーテーションマークに認識されているらしく、Prettify が上手く動かない・・・) actor customerから平均到着率(λ)0.4個/秒でリクエストが届き、actor service が平均サービス率(μ)0.5個/秒でリクエストを処理する設定にしてみた。ρ=0.4/0.5、Ts=1/0.5 なので、平均待ち時間は Tw = ρ / (1 - ρ) * Ts = 8秒 = 8000ms となることが期待されるが、以下のような結果になった。
リクエスト1998; 待ち時間:19875ms; 平均待ち時間=8527ms;
queue.length=10
リクエスト1999; 待ち時間:25484ms; 平均待ち時間=8535ms;
リクエスト2000; 待ち時間:25047ms; 平均待ち時間=8544ms;
リクエスト2001; 待ち時間:25266ms; 平均待ち時間=8552ms;
queue.length=8
リクエスト2002; 待ち時間:22531ms; 平均待ち時間=8559ms;
なんか微妙。期待値8000msより結構大きい値になった。リクエスト2000個でもなかなか予想する値に落ち着かない。λ=2、μ=2.5にして回転率を上げてみるとリクエスト5000個で期待値1600ms に対して 1455ms と少し小さめの数値になった。なんだろう?もっと待てば予想する値に近づくのか、Java の乱数の分布やJava/Scalaのリアルタイム処理のクセなのか、あるいはコードに間違いがあるのか良くわからない。ただ今回は Actor の使い方がざっくりと解ればいいので、余り深く突っ込むのは止めておいた。

Erlang から Scala に移植するに当たって、erlang:send_after に当たるものが、どうも Scalaには見当たらなかったので、もう一つの Actor を使ってThread.sleepさせてメッセージ送信を遅延させるコードを書いた。「actor {}」ではなくて「new Actor」を使っているが、前者だと上手く動かず、調べておきたいところだが今回は時間切れ。あと react を使ってメッセージをやりとりしているときにThread.sleep()すると、他の Actorまで止まってしまうのだろうと予想していたが、この実験ではreceive でも react でもあまりはっきりした違いは見られなかった。後でちゃんと調べておこう。

まあ、せっかく勉強し始めた Scala の忘却防止と、少量の知識追加としては、今回はこんなもんだろう。あと Scala に移植しているうちに Erlang側コードに間違いやらまずいコードがあったのに気付いたので、これも後で直しておく。

2009年9月23日水曜日

JavaScriptから生のMIDI信号を出力させてみる

ふと思い立ってJavaScriptからMidiを鳴らす方法を検索していたら、Midiファイルの再生ではなく Midi 信号をデバイスに送るやり方がどうも見当たらない。というわけで自分でなんとかしてみた。 やり方としては、JavaScript から Java Applet 経由でMidiデバイスを操作することにした。Applet の仕様は、とりあえず以下のような簡単なものとした。
  • MIDIレシーバの取得と解放は、Appletのイベントハンドラ、init() と destroy() で行う。
  • MIDI ショートメッセージの送信を対象とする。特にNOTE_ON, NOTE_OFF, PROGRAM_CHANGE はそのためのメソッドを用意する。
ソースは以下のような感じ
package xad.midi.applet;

import java.awt.*;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.MidiUnavailableException;

import static javax.sound.midi.ShortMessage.*;

public class MidiApplet extends java.applet.Applet {
 Receiver receiver = null;

 public void init(){
  try {
   receiver = MidiSystem.getReceiver();
  } catch (MidiUnavailableException e) {
   e.printStackTrace();
  }
 }

 public void destroy(){
  if (null != receiver) receiver.close();
 }

 public void sendNoteOn(int channel, int noteNo, int velocity) {
  send(NOTE_ON, channel, noteNo, velocity);
 }
 
 public void sendNoteOff(int channel, int noteNo) {
  send(NOTE_OFF, channel, noteNo, 0);
 }

 public void setProgram(int channel, int programNumber) {
  send(PROGRAM_CHANGE, channel, programNumber, 0);
 }

 public void send(int command, int channel, int data1, int data2) {
  try {
   ShortMessage on = new ShortMessage();
   on.setMessage(command, channel, data1, data2);
   receiver.send(on, -1);
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
}
このアプレットを例えば以下のように HTML に組み込む。見えても仕方が無い Applet なので幅と高さを 0 にした(最初、css style の display に none を指定してみたが、見えなくなるだけでなく動作もしなくなってしまったので止めた)。
<applet
  codebase=コードベースのURL code=アプレットのクラスファイル 
  name="midiReceiver" style="width:0;height:0;"/></applet>
これを、例えば次のような JavaScript で動かす。
function getValueById(id) {
  return document.getElementById(id).value;
}
function play(duration) {
  noteNo = getValueById('noteOnNoteNo');
  velocity = getValueById('noteOnVelocity');
  document.midiReceiver.sendNoteOn(0, noteNo, velocity);
  setTimeout("document.midiReceiver.sendNoteOff(0, " + noteNo + ")", duration);
}
これを組み込んだのが以下の一群のテキストボックスとボタンで、以下のような動きにになる。
  • program change ボタンを押すと、テキストボックスに入力した楽器番号に対応する楽器に音色を変更する。番号と楽器の対応は General MIDI とか言う仕様で、たとえばピアノは 1~8、バイオリンは41というように定義されている。
  • note on ボタンを押すと、左のテキストボックスで指定した音番号の音を、右のテキストボックスで指定したベロシティで出力する。音番号もベロシティも0から127までの整数で、音番号は中央のCを60とする。この音を NOTE_ON で出力した後、JavaScript の SetTimeout で2秒待ってからNOTE_OFFで音を止める

まあ、なんとか思い通りに音が出てくる。この程度でも JavaScriptで音感訓練用の音当てゲーム的なものはできそう。

2009年9月22日火曜日

Drools で法定相続分を求めてみる

Drools の自由研究3回目。遺産相続時の各相続人の法定相続分を推論するルールを書いてみた。
遺産相続のルールは、簡単に言うと以下のような分け方になる。
  • 子がいる場合
       
    • 配偶者がいるなら、配偶者が1/2、子が残りを等分  
    • 配偶者がいないなら、子が全てを等分

  • 子がいなくて直系尊属がいる場合
       
    • 配偶者がいるなら、配偶者が2/3、直系尊属が残りを等分  
    • 配偶者がいないなら、直系尊属が全てを等分

  • 子も直系尊属もいないが、兄弟姉妹がいる場合
       
    • 配偶者がいるなら、配偶者が3/4、兄弟姉妹が残りを等分  
    • 配偶者がいないなら、兄弟姉妹が全てを等分

  • 子も直系尊属も兄弟姉妹もいない場合
       
    • 配偶者がいるなら、配偶者が全額取得  
    • 配偶者がいないなら国庫へ

ただし現実のルールよりも、以下のような点で簡略化してある。
  • 直系尊属は本当は最近親ということになるが、ここでは親のみとした
  • 嫡出子と非嫡出子で本当は取り分が違うが、区別しないことにした
  • 子が死んでるときに代襲相続が発生するが、省略した
package com.sample

global com.sample.Person decedent;

function void printSuccession(Succession s) {
 System.out.println(
  String.format("%s:%s %d/%d",
   s.getRelation(), 
   s.getPerson().getName(), 
   s.getNumerator(), s.getDenominator()));
}

rule "配偶者の識別"
 when
  $spouse: Person(spouse == decedent)
 then 
  insert(new Succession($spouse, "配偶者"));
end

rule "子の識別"
 when
  $oyako: ParentsAndChildren(parents contains decedent)
  $child: Person()
  eval($oyako.getChildren().contains($child)) 
 then 
  insert(new Succession($child, "子"));
end

rule "直系尊属の識別"
 when
  $oyako: ParentsAndChildren(children contains decedent)
  $parent: Person()
  eval($oyako.getParents().contains($parent)) 
 then 
  insert(new Succession($parent, "直系尊属"));
end

rule "兄弟姉妹の識別"
 when
  $oyako: ParentsAndChildren(children contains decedent)
  $sibling: Person()
  eval($sibling != decedent && $oyako.getChildren().contains($sibling)) 
 then 
  insert(new Succession($sibling, "兄弟姉妹"));
end

rule "配偶者と分けるときの子の取り分"
  salience -100
 when
  Succession(relation=="配偶者")
  $child: Succession(relation=="子")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="子"), count(s))
 then
  $child.setRatio(1, c * 2);
  printSuccession($child);
end

rule "配偶者と分けるときの直系尊属の取り分"
  salience -100
 when
  Succession(relation=="配偶者")
  not Succession(relation=="子")
  $parent: Succession(relation=="直系尊属")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="直系尊属"), count(s))
 then
  $parent.setRatio(1, c * 3);
  printSuccession($parent);
end

rule "配偶者と分けるときの兄弟姉妹の取り分"
  salience -100
 when
  Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  $sibling: Succession(relation=="兄弟姉妹")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="兄弟姉妹"), count(s))
 then
  $sibling.setRatio(1, c * 4);
  printSuccession($sibling);
end

rule "子のみの場合の取り分"
  salience -100
 when
  not Succession(relation=="配偶者")
  $child: Succession(relation=="子")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="子"), count(s))
 then
  $child.setRatio(1, c);
  printSuccession($child);
end

rule "直系尊属のみの場合の取り分"
  salience -100
 when
  not Succession(relation=="配偶者")
  not Succession(relation=="子")
  $parent: Succession(relation=="直系尊属")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="直系尊属"), count(s))
 then
  $parent.setRatio(1, c);
  printSuccession($parent);
end

rule "兄弟姉妹のみの場合の取り分"
  salience -100
 when
  not Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  $sibling: Succession(relation=="兄弟姉妹")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="兄弟姉妹"), count(s))
 then
  $sibling.setRatio(1, c);
  printSuccession($sibling);
end

rule "子と分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  exists Succession(relation=="子")
 then
  $spouse.setRatio(1, 2);
  printSuccession($spouse);
end

rule "直系尊属と分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  not Succession(relation=="子")
  exists Succession(relation=="直系尊属")
 then
  $spouse.setRatio(2, 3);
  printSuccession($spouse);
end

rule "兄弟姉妹と分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  exists Succession(relation=="兄弟姉妹")
 then
  $spouse.setRatio(3, 4);
  printSuccession($spouse);
end

rule "配偶者のみで分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  not Succession(relation=="兄弟姉妹")
 then
  $spouse.setRatio(1, 1);
  printSuccession($spouse);
end

rule "相続人なしの場合"
  salience -100
 when
  not Succession()
 then
  System.out.println("全て国庫へ");
end
Java コードでは親子関係と配偶関係で家系図表現し、その中の死んだ人をグローバル変数 decedentに設定し、その上でルールを実行する。親子関係のモデリングはいろいろやり方があると思うが、親側と子側に複数の個人を保持するクラス ParentsAndChildrenを定義して、これを用いて表現してみた。(親子関係のモデリングなんて一見自明なようだけど、離婚とか養子縁組とかを考え始めると、意外と複雑で、いろいろと工夫や割り切りが必要になる事に、やってみて改めて気付いたりする)

ルールの適用は、まず最初の段階で故人(decedent)に関連する相続人候補を整理し、次の段階で相続分を判定すると言う形で、2段階になるようにしてみた。そのため salience を用いて順序を指定している。本当は、ルール定義で使う型を declare キーワードを用いてルールの一部として定義したかったが、Drools 5.0 のバグで accumulate からの count で誤ったClassCastException が発生する(Drools 5.1で直るらしい)ということで、仕方なくJava コードで定義した。

上述のとおり、実際の法律より単純化してはいるが一応上手く動く。例えば、子と親は無いが、配偶者と2人の兄弟がいる場合、コンソールの以下のように出力される
兄弟姉妹:C 1/8
兄弟姉妹:D 1/8
配偶者:B 3/4
ほかのパターンもどうやら上手く動いている。

今回やってみて、やはりエンドユーザなりビジネスパーソンなりが自分でルールを書くのは、ちょっと敷居が高い気がする。そもそも、そういうものではないのかもしれない。これでもかなり現実の相続ルールを簡略化しているのだけど、プログラマの自分でもそう簡単にスラスラとは書けるわけではない(まあ触り始めたばかりというのもあるけど)。本来のルールだと例えば最近親をちゃんと求める事も必要になり、おそらく再帰的なロジックが必要になるはずだけど、Drools でどう表現したらいいのかすぐにはイメージできなかったりする。

ドキュメントを見ると、個々のドメインに更に特化した DSL を定義する事もできるようだけど、これはこれで開発者側の負担が大きいはず。やはりドメイン知識を相当程度理解している Drools 技術者がルールを書くのが現実的かもしれない。あとは ビジネスパーソンには SBVR なんかを使ってルールを書いてもらって、それから Droolsのルール定義を自動か半自動で生成するような仕組みもありかも。まあ、それも開発者の負担が大きいか・・・

2009年9月19日土曜日

Drools で所得税額計算

Droolsの練習の一環として、国税庁のサイトの所得税率のページを見ながら、以下のようなルール定義ファイルを書いてみた。

package com.sample

function long trunc1000(long value) {
 return value / 1000 * 1000;
}

function long zeigaku(long shotoku, int zeiritsu, int kojo) {
 return trunc1000(shotoku) * zeiritsu / 100 - kojo;
}

function boolean inRange(long shotoku, long lower, long upper) {
    return lower < shotoku && shotoku <= upper;
}

function void print(long shotoku, int zeiritsu, int kojo) {
 System.out.println(
     String.format("所得:%1$,10d -> 税額:%2$,9d", 
      shotoku, zeigaku(shotoku, zeiritsu, kojo)));
} 

rule "195万円以下"
  when
    $shotoku: Shotoku(name == "所得", $v: value)
    eval (trunc1000($v)<=1950000)
  then
    print($v, 5, 0);
end

rule "330万円以下"
  when
    $shotoku: Shotoku(name == "所得", $v: value)
    eval (inRange(trunc1000($v), 1950000, 3300000))
  then
    print($v, 10, 97500);
end

rule "695万円以下"
  when
    Shotoku(name == "所得", $v:value)
    eval (inRange(trunc1000($v), 3300000, 6950000))
  then
    print($v, 20, 427500);
end

rule "900万円以下"
  when
    Shotoku(name == "所得", $v:value)
    eval (inRange(trunc1000($v), 6950000, 9000000))
  then
    print($v, 23, 636000);
end

rule "1800万円以下"
  when
    Shotoku(name == "所得", $v:value)
    eval (inRange(trunc1000($v), 9000000, 18000000))
  then
    print($v, 33, 1536000);
end

rule "1800万円超"
  when
    Shotoku(name == "所得", $v:value)
    eval(trunc1000($v) > 18000000)
  then
    print($v, 40, 2796000);
end
これを以下のようなJava コードで実行してみると・・・
    private static void insertShotoku(WorkingMemory wm, long shotoku) {
     wm.insert(new Shotoku(shotoku));
    }

    public static final void main(String[] args) {
        try {
・・・略
           WorkingMemory wm = ruleBase.newStatefulSession();
                   
            insertShotoku(wm, 1000000);
            insertShotoku(wm, 1950000);
            insertShotoku(wm, 1950999);
            insertShotoku(wm, 1951000);
            insertShotoku(wm, 3300000);
            insertShotoku(wm, 6950000);
            insertShotoku(wm, 9000000);
            insertShotoku(wm, 18000000);
            insertShotoku(wm, 18001000);

            wm.fireAllRules();      
・・・略
次のようなコンソール出力を得る
所得:18,001,000 -> 税額: 4,404,400
所得:18,000,000 -> 税額: 4,404,000
所得: 9,000,000 -> 税額: 1,434,000
所得: 6,950,000 -> 税額:   962,500
所得: 3,300,000 -> 税額:   232,500
所得: 1,951,000 -> 税額:    97,600
所得: 1,950,999 -> 税額:    97,500
所得: 1,950,000 -> 税額:    97,500
所得: 1,000,000 -> 税額:    50,000
出てきた税額はまずまず合ってるっぽい。

上で使った税率は平成21年度現在のものだけど、同サイトでは平成18年以前の速算表も載っている。やはり、こういった税率やらを含む制度はしょっちゅう変わるわけで、こんなのをいちいち Javaソースにハードコードするのも馬鹿馬鹿しい。ルールはルールで別途外部のリソースとして置いて、実行時に読み込むような形にするべきなんだろうと思う。

ところで、理想的にはユーザ側のドメインエキスパートがルール定義ファイルを書くのが良いと思うのだけど、どの程度こういった DSL に対応できるのだろう・・・?個人差はあるだろうけど、まったくのプログラム未経験のビジネスパーソンには、ちょっと難しいような気もする。

それと、自分が Drools を触り始めたばかりで、まだまだ良く分かっていないという事もあるのだろうけど、どうもいまいち Java コードとルール定義がスッキリ分離しきれない気がする。Javaコードを書くプログラマとルールを定義するビジネスパーソンの間で、上手いこと住み分けと協調をやってかないと、かえって面倒くさい事になりそうな気がしないでもない。何か評価の定まったベストプラクティス的なものがあればいいんだけど。

2009年9月18日金曜日

FP2級受験

例えば、「年金試算Webアプリ」、「管理会計パッケージ」、「証券ポートフォリオ管理アプリ」のようなものを作るとなると、それぞれ年金、簿記、証券といったドメインの基礎知識が必要になるが、こういったドメイン知識が無い技術者には、ユーザとの深い会話が難しい。そこで、いわゆる「業務SE」やら「上級SE」なる人達が要件を取りまとめる事が多い。

ところが、こういった方々は、たいてい70~80年代くらいのメインフレームのパラダイムしか知らない元コボラーだったりするので、単に要件を定義してもらうはずが手続き型のステップを書き連ねたドキュメントが上がって来る事がある(困ったことに、彼らの張り切り具合に比例してそうなりがち)。更に、彼らの方が年齢や組織でのポジションが上であることも多く、リーダやらマネージャを兼務している事もあるから、なかなか指摘して矯正しづらい事もある。

で、そんな人たちと協働する開発でも、対象システムはJava や.Netのオブジェクト指向プラットフォームだったりするから、彼らの作った「what」じゃなくて「how」が書かれてしまっている要件定義書から、いったん「手続き」を取り除いてドメインのルールやらファクトやらを逆算・抽出して非公式なメモなんかにまとめてから、折り返して改めてオブジェクト指向の分析設計に入ったりするような非効率的な羽目になる事もある。

本当はこういった無駄な「中間業者」は最小限にして、オブジェクト指向技術者がドメインレベルの話を最短距離でシステムに落とし込めればいいのだけど、そうなると冒頭にも書いたように、まったくの業務素人にはやはり敷居が高い局面も多々ある。

結局、単純な話になるが、ユーザ側の業務エキスパートとの意思疎通の円滑性を担保した上で、なおかつ「業務SE」を排して合理化するには、オブジェクト指向開発者がドメイン知識を予め備えてさえいればいいという事になる。そうすればオブジェクト指向技術者が、モデリングの面でも開発プロセスの面でも一貫して主導権を持つことができ、健全に開発プロジェクトを進めることができる。

と言うわけで数年前から、主なシステム化対象ドメインについて、教科書レベルの基礎知識くらいは、プロジェクトに先立って予め教養として脳に収めておこうと思って勉強しているのだが、門外漢が漠然と「勉強」などと言ってもモチベーションが維持できないので、各種資格の取得をマイルストーンとして用いている。

その一環として、このあいだの日曜日(09/13)、ファイナンシャルプランナー2級の試験を受けてきた。内容は、年金、保険、金融商品、不動産、税金、相続など。学科試験と実技試験があり、実技はいくつかの科目を選択できるが、個人資産相談業務を受けてみた。

持ち帰った問題用紙にメモしておいた回答を、試験団体で公表されている模範解答を見ながら、自己採点してみたところ、学科 36/60(60%)、実技36/50(72%)だった。満点の6割が基準なので、たぶんギリギリ合格だと思う。(学科の方は36点が合格ラインなので、もしもマーキングミスが一個でもあったらダメと言う事になるが・・・)

難しいと言われている実技がわりと簡単で、そうでもないと思っていた学科が物凄く難しく感じられた。まあ仮に学科で落ちても、どんなテストか分かった事だし、1月に再受験すればまず落ちることはないと思う。(今回は、過去問を一度も解かずにぶっつけ本番でトライして、こんなにギリギリの点数になってしまったが、ちょっと追加で勉強すれば1・2割の安全マージンは確保できると思う。)

だいぶ前に取得した簿記2級も合わせて、そろそろ本番のプロジェクトで、知識を使ってみたくなってきた。常々、ドメインの話題をオブジェクトのコラボレーションとしてモデリングするのが、オブジェクト指向の醍醐味のような気もしている事だし。