2011年12月22日木曜日

WYDKYDK が読めたよ! ワーイヾ(´ρ`)ノ゛ワーイ

Alistair Cockburn の本に出てくる変な綴りの言葉。意味は分かるけどなんて発音するのか、ずっと分からなかったけど、何の気に無しにググってたら見付けた。

なんかソフトウェア開発とは全然関係ない、オレゴン州の自治会か何かの議事録らしき文書だけど、WYDKYDK ("wid-kee-dik")とある。カタカナにするとウィドキーディクみたいな感じだろうか。

綴りが珍しいのは略語だからで、もとはこんな文。
What you don't know you don't know

簡単な英語だけど、構文が分からない人が意外と多い。順を追って見てみると、

You don't know it.
あなたはそれを知らない
What you don't know.
あなたが知らないもの
You don't know you don't know it.
あなたはそれを知らないという事を知らない
What you don't know you don't know.
あなたが知らないという事を知らないもの
といった感じで、入れ子になってたりして、プログラマには親和性が高い気がするんだが。

ところで、現場ではこの WYDKYDK への認識が足りない人が、例えば WBS を作ったり作らせたりすると、困った事になる。

「これだけ詳細化したんだから、見積りは正確なはずだ」なんて思考様式なんだけど、そもそも認識されていない事を詳細化しようもないし、予測に組み込みようもない。

正解は、何らかの形で現実に実行してみて「知らない事」を「知らなかった事」に変えていく事なんだけど、下手な人はこの当たり前の事ができず、日程にバッファを組み込むといった程度の、稚拙な手に走ってしまう。 まあ現場ごとの文化とか、プロジェクトごとの事情とかもあるけどね。

2011年12月21日水曜日

小事の思案と割れ窓理論

葉隠と言えば、「武士道といふは死ぬ事と見付けたり…別に仔細なし。胸すわって進むなり。」で有名だけど、正直、自分みたいな「すくたれもの」には難しい。

だけど実は葉隠には、そんなムリなやつばかりじゃなくて、普通に実践的なアドバイスも結構ある。

『大事の思案は軽くすべし。/小事の思案は重くすべし。』

これについて自分は、やっぱり開発者で飯を食っているせいか、前半をリスク管理の問題、後半を品質管理の問題と捉えてしまう。

前半の『大事の思案』がなぜ軽いかについては、非常にクリティカルな事態に関しては、いざというときに判断に迷って対応が遅れたりしないように、普段から方針を定めておいて、即断即決できるように備えておくべきだという解釈になる。

後半の『小事の思案』がなぜ重いかについては、三島由紀夫が葉隠入門の中で、アリの穴から堤防が崩れるように、瑣末なことを軽んじる事によって生活の体系や重大な思想までもが台無しになると解説している。

で、もちろん前半も後半もどちらも大事なんだけど、どちらかというと自分なんかには後半の方が、より切実にピンと来る。(管理の比重が大きい人は前者かもしれない。)

例えば、コーディング規約なんかみたいなものでも、「ぎりぎり読めりゃいいじゃん」みたいに適当にしないで、きっちり律儀に守ってかないといけない。と自分は思う。

そこを疎かにするから、規約違反の放置->->内部品質の低下->->外部品質の低下->->製品品質の低下 ってな感じで、結局、また地獄になってくんじゃないかと。


で、この小さい事こそ大事ってのを、別角度から現代風に理論化したのが、割れ窓理論だと思う。

一般には、ジュリアーニ市長の時代にニューヨーク市が治安を回復したときにベースになった理論として有名なアレだけど、Wikipedia によるとこうなる

「建物の窓が壊れているのを放置すると、誰も注意を払っていないという象徴になり、やがて他の窓もまもなく全て壊される」

ささいに見える Duplicate code の放置が、コピペだらけの巨大な糞コードの山に繋がっていくという、まあ、ありがちなあれと同じじゃないだろうか。

で、この割れ窓理論をベースにした政策が「ゼロトレランス」だけど、開発現場に当てはめてみると、さしずめ Refactoring Mercilessly といったところか。

なんて事を考えていると、例えば、「途中return は是か非か」みたいな、一見不毛でアホらしい事についてムキになって議論するのも、けっこう大事というか、度を越さない範囲ならむしろ健全だとも思えてくる。もちろん自分もわりとムキになる方だ。

2011年12月20日火曜日

Spring 3.1 の Cache Abstraction

Spring 3.1 が GA になった。最後に Spring を触ったのはこれを書いたときだから、もう2年近く前になるけど、こんときは 3.0 が出た頃だった。あんまり進んでなかったのかも。

いろいろググってみると、キャッシュがどうかしたとか書いてあるので、寝る前にちょっと試してみる。

package spring.trial1.ex1;

import java.util.HashMap;
import java.util.Map;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

class Bar {
   Map<Integer, String> map = new HashMap<>();
   {
      map.put(1, "one");
      map.put(2, "two");
   }
   @Cacheable("bar")
   public String findString(int key) {
      System.out.printf("findString(%d) called%n", key);
      return map.get(key);
   }
}

public class Foo {
   public static void main(String[] args) {
   ApplicationContext ctx = new ClassPathXmlApplicationContext(
         "applicationContext.xml");
   Bar bean = ctx.getBean(Bar.class);

   System.out.println(bean.findString(1));
   System.out.println(bean.findString(2));
   System.out.println(bean.findString(3));
   System.out.println(bean.findString(1));
   System.out.println(bean.findString(2));
   System.out.println(bean.findString(3));
   }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
  <cache:annotation-driven />

  <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
      <set>
        <bean
          class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
          p:name="bar" />
      </set>
    </property>
  </bean>
  <bean id="bar" class="spring.trial1.ex1.Bar"/>
</beans>
pom.xml の dependency に spring-context 3.1.0.RELEASE と、cglib 2.2.2 を指定して、ビルド→実行したら、以下のような結果が得られる。 (既に Maven Central に入ってるらしく、特に repository を指定したり、ローカルリポジトリに入れたりとかしなくても、普通に 3.1 がダウンロードされる。)
findString(1) called
one
findString(2) called
two
findString(3) called
null
one
two
null

findString の 中身が呼ばれるのは最初だけで、2回め以降はキャッシュが使われているらしいのが分かる。

■ 雑感

JavaConfig/JSR-330 で xml を書かずに済むようになったと思ってたけど、この <cache:annotation-driven> に関しては、やり方が分からない。

仕方なく、昔ながらのapplicationContext.xml を書いてしまった。もしかすると、ちゃんとしたやり方があるのかもしれないけど、部分的にアノテーションで部分的にXMLみたいにマジでなるとしたら、結構イヤかも。

2011年12月18日日曜日

gtk2hs で落書きしてみる

前回の書き込みで、gtk2hs の準備ができたので、もう一歩進めてみる。

10年以上前、Visual C++を使ってた頃、MFC のチュートリアルで Scribble というのがあった。これを、Haskell で試してみた。

ただし、MDI とか ドキュメントの保存とかは割愛して、とりあえず描画の API とマウスイベントのハンドリングだけを気にすることにした。

仕組みは簡単で、マウスボタンが押下されたときから放されるまでのマウスポインタの軌跡を記録し、ウィンドウ描画時に各点を線でつないでいくというもの。コードは以下のようになる。

import qualified Graphics.UI.Gtk as G
import Graphics.UI.Gtk.Gdk.GC
import Graphics.UI.Gtk.Gdk.Events
import Data.IORef
import Graphics.UI.Gtk.Gdk.Drawable

main = do
    G.initGUI
    w <- G.windowNew
    G.onDestroy w G.mainQuit

    da <- G.drawingAreaNew

    isDrawingIORef <- newIORef False
    figuresIORef   <- newIORef [[]]

    G.onExposeRect da (const $ do dw <- G.widgetGetDrawWindow da
                                  gc <- gcNew dw
                                  f <- readIORef figuresIORef
                                  drawFigure dw gc f)

    G.onMotionNotify da True $ \Motion {eventX = x, eventY = y}-> do
      isDrawing <- readIORef isDrawingIORef
      case isDrawing of
        True  -> do  modifyIORef figuresIORef (\f->addPoint f x y)
                     G.widgetQueueDraw da
        _     -> return ()
      return True
  
    G.onButtonPress da $ \Button {eventX = x, eventY = y}-> do
      writeIORef isDrawingIORef True
      modifyIORef figuresIORef (\f->[point x y]: f)  
      return True

    G.onButtonRelease da $ \Button {eventX = x, eventY = y}-> do
      writeIORef isDrawingIORef False
      modifyIORef figuresIORef (\f->addPoint f x y)
      return True
  
    G.containerAdd w da
    G.widgetShowAll w
    G.mainGUI

  where
    point x y = (round x, round y)
    addPoint figures x y = (point x y: head figures): tail figures
    drawFigure _ _ [] = return ()
    drawFigure d g (f:fs) = do {
      G.drawLines d g f; drawFigure d g fs }

Haskell 自体にまだ慣れていないので、いかにも未熟な感じだけど、イベント処理と描画の作法がなんとなく分かってきた。ネット上で API を調べる事にも、だんだん慣れてきた。

ただ、IORef というのが、なんだかしっくり来ない。結局、状態を持ってしまってる事になり、どうも気持ち悪い。初心者なりに勝手に純粋関数型言語に期待していたのは、もっと、引数と戻り値だけでつながっていく感じなんだけど、まだまだ勉強が足りないのかもしれない。

結果としては、マウスボタンの判別をしていないので、右でも左でも書けてしまうけど、一応思ったとおりに動作するものができた。スプーの絵もこんな風に書く事ができる(頭頂部の突起がやや足りない事を除けば、我ながらよく描けたと思う)。

gtk2hs と Glade の相性

gtk2hs を Glade と組み合わせて使うときのメモ。

Haskell の GUI プログラミングを試したくて、テキストボックスに文字列を入れて Enter を押したらラベルに表示されるような簡単なプログラム echo を、gtk2hs を使って書く実験をしてみた。

で、まず Glade というツールで GUI を定義する XMLを生成して、これを適当に書いた Haskell プログラムに読ませたら、こんなエラーが出た

$ ./echo 

(echo:4887): libglade-WARNING **: Expected <glade-interface>.  Got <interface>.

(echo:4887): libglade-WARNING **: did not finish in PARSER_FINISH state
echo: user error (Pattern match failure in do expression at echo.hs:8:5-12)

なんか Glade が生成した GUI定義XML の形式に問題があるっぽい。

調べてみると、Glade の出力形式には libglade と GtkBuilder の二通りの方法があり、gtk2hs が対応しているのは前者という事らしいのだが、ファイルを保存するときに libglade を選んでも、事態は全然変わらない。

さらに調べてみると Glade の3.8系 と 3.10系 で大きな違いがあって、出力XML に関してだと 3.10系では libglade 形式が無くなってるらしい。自分は、特に意識しないまま 3.10 を使っていた模様。

この2つのバージョン間で、保存時のダイアログに以下のような違いがある。

3.10系
3.8系
XML出力の違いは以下のような感じ
3.10系
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="2.24"/>
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <child>
    …
3.8系
<?xml version="1.0" encoding="UTF-8"?>
<glade-interface>
  <widget class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <child>
    …

というわけで、Glade の 3.8 系を使えば、gtk2hs がちゃんと処理できる形式の XML になる。

ただし、うちの環境で使ってる Fedora16 なんかでは、「ソフトウェアの追加/削除」から普通に Glade をインストールすると 3.10系が入って来てしまう。従って、この場合 3.8系を別途インストールする必要がある。

これは、ここから glade3-3.8.1.tar.xz を落としてきて、tar Jxf glade3-3.8.1.tar.xz -> ./configure -> make -> make install のようにすれば、/usr/local/bin/glade-3 が使えるようになる。

ちなみに、以下が実験で使った Haskellコード

module Main where

import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade
import Graphics.UI.Gtk.Gdk.Events as Evt

main = do
    initGUI
    Just xml    <- xmlNew "echo.glade"
    window      <- xmlGetWidget xml castToWindow "window1"
    onDestroy window mainQuit
    label       <- xmlGetWidget xml castToLabel "label1"
    entry       <- xmlGetWidget xml castToEntry "entry1"
    onKeyPress window $ \(Evt.Key _ eventSent _ _ _ _ _ _ name _) -> do
      keyPressHandler label entry name
      return eventSent
    widgetShowAll window
    mainGUI

keyPressHandler :: Label -> Entry -> String -> IO ()
keyPressHandler label entry "Return" = do
  name <- get entry entryText
  set label [labelText := name]
keyPressHandler _ _ _ = return ()
たかだかこれだけのコードを書くのに、かなり骨を折るが、いちおう動作する。

2011年12月14日水曜日

現場でよくある Agile 認識の齟齬

現場で、開発プロセスをどうしましょうかなんて話をしていると、対話の流れを止めて指摘するほどではないけど、認識の違いが引っかかって嫌なときがある。今日は、そんな事象のメモ。

★ Agile プロセスはユルいものだという認識

ユルいキビしい
なんか違うAgile全般ウォーターフォール
たぶんこうAdaptive Software Development、Crystal ClearXP、Scrum、ウォーターフォール

Agile が総じてユルいのではなく、実はユルいのもキビしいのもある。この見方は、確かものすご〜く前に読んだ Cockburn の本に載ってたものだけど、仮に読んでなかったとしても同じ認識になってた気がする。

ちなみに、同じ High-Discipline プロセスの中でも、XP と ウォーターフォールでは、前者が高効率・高難度で後者が低効率・低難度って事になると思う。寛容な方のグループの Crystal Clear なんかは、ユルい分、XP のような究極的な生産性は得られないけど、その分、負担が少ないって事になると思う。

★ 反復型といえば軽量プロセスだという認識

軽量重量
なんか違う反復型プロセス全般ウォーターフォール
たぶんこうAgile系UP系(AgileUPは除く)、ウォーターフォール

反復型のプロセスはどれもみな軽量だという思い込みもよくあるけど、たいてい「軽量プロセスはドキュメントを作らない」ってドクサも併発してるから、とりあえず反復型にしてみませんかなんて提案しても、習慣による心理的抵抗が大きい。

ウォーターフォール色の強い組織でも、ドキュメント体系を差し当たり尊重したまま、やや長めの期間の反復型プロセスに切り替えた上で、反復型が浸透してきた頃合いを見て、段々と期間を短くしたり文書や儀式を減らして、段階的に軽量にしていくというやり方もある。

2011年12月13日火曜日

Alloy 本

土曜に Amazon のお急ぎ便で注文したら、日曜の夜に届いた。 Alloy本
日曜は暇がなかったので、今日、帰宅してから早速 Alloy Analyzer をダウンロードして、テキストに取りかかる。(単なる jar なので java -jar ですぐ動く。)

下図の左側ペインのようにコードを書き込んで、[Execute]>[Show Metamodel]を実行すると、、、

、、、このようなダイアグラムが得られる。

このダイアグラムは、四角形の配置を変えたり、色やフォントを変えたりして、ある程度見せ方を好きなように変えられるようだけど、どうもダイアグラムはそれほど本質的なものではないっぽい。

まあ、1時間ほどいじっただけだし何も理解できてないけど、上の上の図で左側のペインに書いてある Alloy コードがやっぱり主役っぽい。
これに述語(pred)やファクト(fact)をちょっと書き足しては、それに対してアサーション(assert)をちょっと付け加えて、そしたら実行して右側のペインで確認して、って流れになる模様。そうだとしたら、どこか TDD に似ていてとっつきやすい。

とは言っても、関心の重点は実装ではなくむしろ仕様であって高い抽象レベルで仕様をモデリングする事が目的の技術らしい。
ちなみに、抽象度をレベル分けするときに、伝統的に分析レベル、設計レベル、実装レベルなんて言ったりするけど、Alloy と同じ雰囲気で、なおかつレベルの違う他言語を持ってくると、分析レベル:Alloy、設計レベル:LePUS3、実装レベル:Coq てな感じに当てはまるんじゃないかと思う。

まあ、いきなりいろんな事をやり始めなくても、とりあえず Alloy だけでも、例えば金融システムのビジネスルールとかに適用して、仕様バグを取り除くような事は、結構すぐにでも始められそうな予感。

2011年12月11日日曜日

Haskell+HDBC+MySQL で Hello World

Haskell で MySQL を使ってみるウォーミングアップ。

こんな環境
  • Fedora16
  • GHC 7.0.4
  • MySQL 5.5.18

====

まずテーブルを準備する。適当に mysql で MySQL に接続して以下のようにする。

mysql> create database hdbc_test;
mysql> connect hdbc_test;
mysql> create table greeting (id int not null auto_increment, text varchar(50), primary key(id));
mysql> insert into greeting(text) values ('Hello, world!');
mysql> select * from greeting;
+-----+---------------+
| id  | text          |
+-----+---------------+
|   1 | Hello, world! |
+-----+---------------+
1 row in set (0.00 sec)
テーブルの準備ができたら、次は使用するモジュールだけど、この サイトによると、Haskell から DB を使うには、HDBC、HSQL、HaskellDBと言った選択肢があるらしい。とりあえず HDBC でいってみる。
$cabal install HDBC
$cabal install HDBC-mysql

※ cabal 自体は、Fedora のアプリケーションの追加/削除でインストールした。
※ ghc-pkg という低レベルのコマンドもある。
※ MySQL以外に HDBC でサポートされてるやつを調べるにはここ

できたら、MySQL に接続してみる。
まず ghci を立ち上げて、モジュールを取り込んでからDB接続を取得する。

ghci>  :m +Database.HDBC Database.HDBC.MySQL
ghci> conn <- connectMySQL MySQLConnectInfo{mysqlHost="localhost", mysqlDatabase="hdbc_test", mysqlUser="root",mysqlPassword="XXXX", mysqlPort=3306, mysqlUnixSocket="/var/lib/mysql/mysql.sock"}
ghci>

mysqlUnixSocket に設定するファイル名は、/etc/my.conf に書いてあるのでそれを指定すればいい。成功したら、上のようにエラーメッセージなしで次のプロンプトに移る。

ちなみに、省略時値が既に設定されている defaultMySQLConnectInfo を使えば、この場合だと mysqlHost, mysqlUser, mysqlPort を省略して以下のようにできる。

ghci> conn <- connectMySQL defaultMySQLConnectInfo{mysqlDatabase="hdbc_test", mysqlPassword="XXXXX", mysqlUnixSocket="/var/lib/mysql/mysql.sock"}

※ソースを読みたかったら、ここで見られる

接続が得られたら、いよいよ HelloWorld。

ghci> quickQuery' conn "SELECT * from greeting" []
[[SqlInt32 1,SqlByteString "Hello, world!"]]
うん。できたっぽい。

ちなみに エラー無しで取得できたと思った Connection を、いざ使おうとしたら "No instance for (IConnection Connection)"なんて言われる事がある。自分もそうなったけど HDBC 関連のパッケージを更新したら解消した(参考URL

====

ついでに、もうちょい他の事もやってみる。

■ prepared statement

ghci> stmt <- prepare conn "INSERT INTO greeting(text) VALUES (?)"
ghci> executeMany stmt [[toSql "Good-bye, world..."], [toSql "Hello, another world2!"]]
ghci> quickQuery' conn "select * from greeting"[]
[[SqlInt32 1,SqlByteString "Hello, world!"],[SqlInt32 2,SqlByteString "Good-bye, world..."],[SqlInt32 3,SqlByteString "Hello, another world!"]]
ghci> commit conn
JDBC やってるのと同じだね。

■ メタ情報

ghci> describeTable conn "greeting"
[("id",SqlColDesc {colType = SqlIntegerT, colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Just False}),("text",SqlColDesc {colType = SqlVarCharT, colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Just True})]
うーん…colType は良いけど、colSize がNothing になってるのはどういう訳だろう。ここは varchar (50) を反映していてほしかった。 標準SQL の INFORMATION_SCHEMA で、普通にメタ情報を得ることも、もちろんできる。

■ 日本語

ghci> run conn "UPDATE greeting SET text='こんにちは' WHERE id=1" []
ghci> quickQuery' conn "select * from greeting where id=1"[]
[[SqlInt32 1,SqlByteString "S\147kao"]]
ははは、文字化けした。mysql で見ても化けてる。面倒そうだから後で考えよっと。

--2012/08/12: 同じSQLを prepared statement でやったら問題なく「こんにちは」となる
--2012/08/12: ghci> run conn "UPDATE greeting SET text=? WHERE id=1" [toSql "こんにちは"] で上手く行く

気になるところが幾つかあったけど、最初の試行としてはこんなものだろう。

====

最後に豆知識メモ。

ghci のプロンプトを変えるには、":set prompt "ghci> "と入力すればいい。これを永続化するには、~/.ghc/ghci.conf に同じ事を書く。ただし、どういうわけか、ファイルのパーミッションで、グループに w が付いていると無視される。無視されないようにするには、"chmod g-w .ghc .ghc/ghci.conf "として、書き込み権限を取り除いておけばいい。

あと、いろいろググってると、HaskellDB と HSQL と HDBC とで、似て非なる事柄が一緒くたに引っかかってくるので、酒を飲みながら作業してたりすると、 HDBC.MySQL のソースのつもりで HaskellDB.HDBC.MySQL のソースを読んで小一時間頭をかきむしって苦しむ事になったりする。

2011年12月9日金曜日

Prolog で楽典クイズ

我々が日ごろ聞いてるふつうの音楽は、1オクターブの中に高さの異なる音が12個あって、それぞれの音は「音名」という名前を持っている。

実は、ほとんどの音はそれぞれ三つの音名を持っている。例えば、ミ♯、ファ、ソ♭♭の組み合わせや、レx、ミ、ファ♭の組み合わせは同じ音高であったりする。

ところが、ある音だけは二つの音名しか持っていない。この音が何かわかるだろうか。

ダブルシャープとかダブルフラットを使ってるから、小学校で習うかどうかは分からないけど、たぶん義務教育の範囲には入ってる気がするくらいの基礎的な楽典知識だけで構成された問いだけど、面白いことに、意外と音楽をやってる人でも即座には答えられなかったりする。

ピアノの鍵盤を思い浮かべて単なる図形として捉えたら簡単なんだけど、今日は、これを「寝る前ちょこっとプログラミング」のお題にしてみようと、帰りの電車で思いついた。Prolog でやってみる。

====

note(0, 'C').
note(2, 'D').
note(4, 'E').
note(5, 'F').
note(7, 'G').
note(9, 'A').
note(11, 'B').

notes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).

alteration('x', -2).
alteration('♯',  -1).
alteration('♮',   0).
alteration('♭',   1).
alteration('♭♭',  2).

noteWithThreeNames(Names)   :- findNoteNamesByCount(Names, 3).
noteWithOnlyTwoNames(Names) :- findNoteNamesByCount(Names, 2).

findNoteNamesByCount(Names, Count) :-
   notes(Notes), member(Note, Notes),
   collectNoteNames(Note, Names),
   length(Names, Count).

collectNoteNames(Note, Names) :-
  findall(Name, findName(Note, Name), Names).

findName(Note, Names) :- 
  alteration(Alteration, Diff), 
  getNote(Note + Diff, NaturalTone),
  concat_atom([NaturalTone, Alteration], Names).

getNote(N, X) :- N2 is N mod 12, note(N2, X).

素朴な実装だけど、以下のように正しい結果が得られる。
?- noteWithThreeNames(Names).
Names = ['B♯', 'C♮', 'D♭♭'] ;
Names = ['Bx', 'C♯', 'D♭'] ;
Names = ['Cx', 'D♮', 'E♭♭'] ;
Names = ['D♯', 'E♭', 'F♭♭'] ;
Names = ['Dx', 'E♮', 'F♭'] ;
Names = ['E♯', 'F♮', 'G♭♭'] ;
Names = ['Ex', 'F♯', 'G♭'] ;
Names = ['Fx', 'G♮', 'A♭♭'] ;
Names = ['Gx', 'A♮', 'B♭♭'] ;
Names = ['A♯', 'B♭', 'C♭♭'] ;
Names = ['Ax', 'B♮', 'C♭'].

?- noteWithOnlyTwoNames(Names).
Names = ['G♯', 'A♭'] ;
false.

====

音楽の話題でいえば、厳格対位法という、こんなのとは比較にならないくらいパズルっぽいやつがある。これは与えられた音の配列(定旋律)に対して、たくさんの「禁則」をくぐり抜けてごく限られた音の配列(対旋律)を見つけ出していくという、古典音楽作曲の伝統的なトレーニングなんだけど、視点を変えるとパズルの「川渡り問題」に似てなくもない。

たぶん、とっくにどこかのプログラマが挑戦してるだろうけど、もうちょいヒマができたら自分でもやってみたい気がする。

2011年12月6日火曜日

Jaskell このやろう!

名前だけはずいぶん前から知ってたけど、あまり興味が無くて放置していた Jaskell。

『プロダクティブ・プログラマ』で紹介されて高評価だったから、さぞかし有望株なのだろうと思いきや…

誰も使ってる気配がない…

まあダウンロードはできるから、ちょっと試してみようとしたけど、そもそも動いてくれない。

Exception in thread "main" java.lang.NoSuchMethodError: jfun.parsec.Parsers.plus(Ljfun/parsec/Parser;Ljfun/parsec/Parser;)Ljfun/parsec/Parser;
 at jfun.jaskell.JaskellParser.(JaskellParser.java:299)
 at jfun.jaskell.JaskellParser.instance(JaskellParser.java:1377)
 at jfun.jaskell.JaskellParser.parseExprOrLib(JaskellParser.java:1436)
 at jfun.jaskell.Jaskell.parseExprOrLib(Jaskell.java:2356)
 at jfun.jaskell.Jaskell.parseExprOrLib(Jaskell.java:2376)
 at jfun.jaskell.Jaskell.eval(Jaskell.java:2469)
 at jfun.jaskell.Jaskell.evalInputStream(Jaskell.java:2657)
 at jfun.jaskell.Jaskell.evalResource(Jaskell.java:2555)
 at jfun.jaskell.Jaskell.evalResource(Jaskell.java:2531)
 at jfun.jaskell.Jaskell.importPrelude(Jaskell.java:1990)
 at jfun.jaskell.shell.Shell.getShellRuntime(Shell.java:42)
 at jfun.jaskell.shell.Shell.main(Shell.java:35)
ちなみにこの例外は、java コマンドから Jaskell Shell を起動しようとしたときにも、Jaskell インスタンスを生成してそいつにスクリプトを読ませようとしたときにも、 どっちでも発生する。

第一、起動の仕方らしきものにたどり着くまでだいぶかかった。プロダクティブ・プログラマで紹介されている URL、「http://jaskell.codehaus.org/」 は、Jaskell でググっても一番上にくるやつだけど、動かし方がどこにも全然書いてない。

jaskell-1.0.jar を実行したら何か起こるだろうと思ったけど、うんともすんとも言わない。MANIFEST.MFにも、やっぱなんも書いてなかった。

しょうがないから、JavaDoc を読んでたら Jaskell クラスというのがあって、こいつが文字列やファイルを評価する eval () メソッドを持ってるから、インスタンス作って eval() したら、上の例外。

半泣きでググってたら、Jaskell Shell というのを見つけるが、トップページからリンクされてる訳でもなく、どこが本物のプロジェクトページだか、訳が分からない。で、動かしてみたら、また上の例外。

まあ、何個かある jar の組み合わせを変えてみるとか、再コンパイルしてみるとかで、解決しない事もないんだろうけど、もういい。心折れた。降りるわ。

それにしても Neal Ford さんは、なんでこんなの紹介したんだ…。
普通に、Haskell 勉強して行こうやって結論しか出てこない。

2011年12月4日日曜日

Groovy+HttpClient で Redmine の wiki 更新

Redmine の wiki を、HTTP のクライアントプログラムで更新する方法を考えてみた。言語は何でもいいが、ここでは Groovy を使ってみた。

====

例えば、以下のようなコードで、

class Test {
   def static content = """\
h1. 大見出し

なんとかこんとか
${ new Date().toString() }
"""
      public static void main(args) {
      new RedmineWikiRewriter()
         .login("user001", "password")
         .startEdit("project001", "Http_post_test")
         .saveEdit(content)
   }
}
下の画像のように、wiki ページが更新されるような クラス RedmineWikiRewriter を考えてみる。

  • login() では、与えられたユーザ名とパスワードでログインして、Redmine から返されるクッキーを保持する。
  • startEdit()は、指定のプロジェクトの指定の wiki ページを開く。これはページのバージョンを得るために必要
  • saveEdit()は、wiki コンテンツをマルチパートの HTTP POST で送信する
コードは以下のようなものになる

class RedmineWikiRewriter {
   def static UTF8 = Charset.forName("UTF-8")

   def HTTPBuilder http = new HTTPBuilder('http://localhost:3000/')

   def redmineSession
   def authenticityToken
   def contentVersion 
   
   def project
   def wikiEntry
   
   public RedmineWikiRewriter login(String username, String password) {
      http.get(path : '/login', query : [q:'Groovy'] ) { resp, reader -> 
         authenticityToken = reader.BODY.DIV.DIV
            .findAll { it.id="login-form" }.depthFirst()
            .grep { "authenticity_token"== it.@name.toString() }[0]
            .@value.toString()
      }
      http.request(POST) {
         uri.path = '/login'
         send URLENC, [
            username:username, password:password, 
            authenticity_token:authenticityToken]

         response.success = { resp ->
            redmineSession = resp.getAllHeaders()
               .find {"Set-Cookie"==it.name.toString()}
               .value.split(";")
               .find {it.toString().startsWith("_redmine_session")}
            }
      } 
      this
   }
   RedmineWikiRewriter startEdit(String project, String wikiEntry) {
      this.project = project
      this.wikiEntry = wikiEntry 

      http.request(GET) { resp ->
         uri.path = "/projects/"+project+"/wiki/" + wikiEntry + "/edit"
         headers.'Cookie' = redmineSession
         response.success = { res, reader ->
            contentVersion = reader
               .depthFirst()
               .grep{"content_version"==it.@id.toString() }[0]
               .@value.toString()
         }
      }
      this
   }
   void saveEdit(String content) {
      DefaultHttpClient httpclient = new DefaultHttpClient();
      def uri = "http://localhost:3000/projects/"+ project +"/wiki/" + wikiEntry

      HttpPost httppost = new HttpPost(uri)
      httppost.addHeader('Cookie', redmineSession)
      
      MultipartEntity mpe = new MultipartEntity(
         HttpMultipartMode.BROWSER_COMPATIBLE, 
         "----WebKitFormBoundaryfmexSJQ5daIXdA04", 
         UTF8);
      addPart(mpe, "commit",          "Save");
      addPart(mpe, "project_id",       project);
      addPart(mpe, "action",          "update");
      addPart(mpe, "_method",          "put");
      addPart(mpe, "authenticity_token", authenticityToken)
      addPart(mpe, "id",             wikiEntry)
      addPart(mpe, "content[text]",    content)
      addPart(mpe, "content[version]", contentVersion)
      addPart(mpe, "controller",       "wiki")
      
      httppost.setEntity(mpe);

      httpclient.execute(httppost);
   }
   static void addPart(MultipartEntity entity, String name, String value) {
      entity.addPart(name, new StringBody(value, UTF8));
   } 
}

■ 振り返り

  • saveEdit() でも HttpBuilder を使いたかったが、なぜか思うように動かない。仕方なく、DefaultHttpClient を使用
  • wiki エントリの新規作成でも動くかどうかは未確認
  • たかだかこれくらいのコードでも、やってみると予想外に難しい。Redmine の Rubyコードを読んだり、HTTP の Header を調べたりいろいろ手間がかかった。あと multipart の POST も意外と難しい
  • 仕組みはだいたい分かったので、他の言語でも試してみたい。もともとやりたかったのは、Excel 上の VBA からデータを整形して wiki に載せるというのを自動化すると言う事だった。

2011年12月3日土曜日

Fantom でサンタクロース問題

前回のせんだみつおゲームで Fantom の Actor の使い方が何となく分かったので、サンタクロース問題でもやってみようと思う。クリスマスも近いしな。まあ、本物のプログラマには、そんなの関係ないけどな。

====

SantaClausProblem.main()は、サンタ、トナカイ、小人を生成して起動する

using concurrent

class SantaClausProblem {
  Void main() {
    ActorPool pool := ActorPool()

    Santa santa  := Santa(pool)
    Deer[] deers := (1..9).map |Int n->Deer| { Deer(pool, n, santa) }
    Elf[] elves  := (1..10).map |Int n->Elf| { Elf(pool, n, santa) }

    deers.each |Deer deer| { deer.send("vacation") }
    elves.each |Elf elf| { elf.send("home") }

    while (!pool.isStopped) { Actor.sleep(1sec) }
  }
}

Player は登場人物共通のコード。ActorUnderSanta はトナカイと小人の共通コード。

Actor
 └ Player
     ├ Santa
     └ ActorUnderSanta
         ├ Deer
         └ Elf
const class Player: Actor {
  new make(ActorPool pool) : super(pool) {}

  Void sleepRandom(Range r) { Actor.sleep(1sec* Int.random(r)) }
}
const class ActorUnderSanta: Player {
  const Int number
  const Santa santa

  new make(ActorPool pool, Int number, Santa santa) : super(pool) {
    this.number = number
    this.santa = santa }

  Str name() { this.typeof().name + number  }
}

トナカイと小人はこんな感じ

const class Deer: ActorUnderSanta {
  new make(ActorPool pool, Int number, Santa santa)
    : super(pool, number, santa) {}

  override Obj? receive(Obj? msg) {
    if ("vacation" == msg) { sleepRandom(1..10); santa.send(this) }
    return msg
  }
}

const class Elf: ActorUnderSanta {
  new make(ActorPool pool, Int number, Santa santa)
    : super(pool, number, santa) {}

  override Obj? receive(Obj? msg) {
    if ("home" == msg) { sleepRandom(2..15); santa.send(this) }
    return msg
  }
}

サンタはこんな感じ

const class Santa: Player {
  new make(ActorPool pool) : super(pool) {}

  override Obj? receive(Obj? msg) {
    echo (((ActorUnderSanta)msg).name + " が来た")
    
    if (msg is Deer) addDeer((Deer)msg)
    else if (msg is Elf) addElf((Elf)msg)
    
    return msg
  }
  Void addDeer(Deer deer) {
    Deer[] deers := get("deers", (Deer[])[,])
    deers.add(deer)
    wakeUpIfConditionIsMet
  }
  Void addElf(Elf elf) {
    Elf[] elves := get("elves", (Elf[])[,])
    elves.add(elf)
    wakeUpIfConditionIsMet
  }
  Void wakeUpIfConditionIsMet() {
    if (9 == getDeers().size) deliverToys
    else if (3 <= getElves().size) meetInStudy
  }
  Void deliverToys() {
    echo("おもちゃを配る")
    sleepRandom(1..2)
    getDeers().each|Deer deer| { deer.send("vacation") }
    Actor.locals["deers"] = (Deer[])[,]
  }
  Void meetInStudy() {
    echo("小人と打ち合わせ")
    sleepRandom(1..2)
    Elf[] elves := getElves()
    elves.eachRange(0..2, |Elf elf| { elf.send("home") })
    Actor.locals["elves"] = elves.removeRange(0..2)
  }
  Deer[] getDeers() { (Deer[])Actor.locals["deers"] }

  Elf[] getElves() { (Elf[])Actor.locals["elves"] }

  Obj get(Str key, Obj defaultValue) {
    Obj? result := Actor.locals[key]
    if (null == result) {
      result = defaultValue
      Actor.locals[key] = result
    }
    return result
  }
}
こんな結果が得られる
$ fan santaClausProblem.fan
Deer6 が来た
Deer1 が来た
Elf2 が来た
Elf9 が来た
Deer9 が来た
Deer2 が来た
Deer8 が来た
Deer3 が来た
Elf3 が来た
小人と打ち合わせ
Elf5 が来た
Elf8 が来た
Deer4 が来た
Deer7 が来た
Elf1 が来た
小人と打ち合わせ
Deer5 が来た
おもちゃを配る
Elf2 が来た
Elf7 が来た
Elf9 が来た
小人と打ち合わせ
Deer9 が来た
Deer5 が来た
Elf4 が来た
Elf6 が来た
Elf3 が来た
小人と打ち合わせ