Java7でソートのロジックが変更されている件
コメントできないのでトラックバックで。
元の並びに依存したComparatorがダメらしいですね。
対応版のコードおいておきます。
とりあえずは、doColumnDescメソッドのオーバーライドで回避してもらえればと思います。
本体のほうも直しておきます。
TypeProviderでSQLのパラメータに型をつける
コンピュテーション式でADO.NETのトランザクションを表現するアイデアを使いつつ、パラメータありのSQLをTypeProviderを使って(比較的)型安全に実行する方法を考えてみました。
TypeProviderはF# 3.0で導入された、コンパイル時に型を生成する機能です。
こんなコードが動きます。
#r @"..\Tranq\bin\Debug\Tranq.dll" #r @"bin\Debug\Tranq.Provider.dll" open Tranq open System.Data.SqlClient let provider() = let config = "Data Source=.\SQLEXPRESS;Initial Catalog=SampleDB;Integrated Security=True" new SqlConnection(config) :> System.Data.Common.DbConnection (* TypeProviderを使ってSQLとパラメータの型をカプセル化した型を作成 *) type SelectByNameAndAge = Sql<" select * from person where name like @name and age >= @age"," name: string; age : int"> (* トランザクションを実行するコンピューテション式 *) let selectPersons n a = required { (* TypeProviderで作られた型を使ってSQLを実行 *) let! persons = Database.query <| SelectByNameAndAge(name = n, age = a) return persons } (* コンピュテーション式を実行 *) let result = selectPersons "j%" 20 |> runTx provider (* 実行結果を出力 *) match result with | Some persons -> printfn "success" persons |> Seq.iter (printfn "%A") | _ -> printfn "failed"
ポイントはもちろん、独自のTypeProvider(Sql)で新しい型(SelectByNameAndAge)を定義しているところ。
type SelectByNameAndAge = Sql<" select * from person where name like @name and age >= @age"," name: string; age : int">
TypeProviderにSQLとSQL中のパラメータ(@nameと@age)の型を教えています。TypeProviderは指定されたパラメータをコンストラクタで受け取る新しい型を生成します。
SQLを実行するときは、TypeProviderで生成された型のインスタンスを生成してDatabase.queryに渡します。コンストラクタの呼び出しには名前つき引数を使うのもポイントです。
Database.query <| SelectByNameAndAge(name = name, age = age)
この仕組みの何がうれしいかというと、パラメータの「名前や型が間違ってないこと」と「過不足」について、コンパイル時にチェックできるんですね!
このライブラリ、Tranq(仮称)という名前で作っていて、超単純なシナリオが何とか動くぐらいでのレベルでソースコードの公開もしていないのですが、興味もってくれる人っていますかねー?
コンピュテーション式とTypeProviderを使えて結構満足したので、これ以上作るかはまだ決めてなかったりします。
Lazyの合成
Lazyのアクティブパターンを使うとすっきり書けることに気づきました。
(* ('a -> 'b -> 'c) -> Lazy<'a> -> Lazy<'b> -> Lazy<'c> *) let lift2 f x y = lazy ( let (Lazy a) = x let (Lazy b) = y f a b) do let x = lazy (10 + 20) let y = lazy (1 + 2) let z = lift2 (+) x y printfn "%A" (z.Value) (* 33 *)
コンピュテーション式じゃないけど次のようなコンピュテーション式に見えなくもない。
let lift2 f x y = lazy { let! a = x let! b = y return f a b}
こういう感じのアクティブパターンの使い方は、ほかでも便利なことあるかも。
ピエールの綱渡り
すごいHaskellたのしく学ぼう! にMaybeモナドの説明で、次のようなものがあります。
ピエールがバランス棒を持って綱渡りしている最中、バランス棒の左右に鳥がとまる。
左右の鳥の数の差が3より大きくなったらピエールは落下して綱渡りが失敗する。
左右の鳥の数の差が3以下のままで最後までいけたら成功。
ということで、F#のコンピュテーション式で書いてみました。Maybeを表すだけだと物足りないので、以下のことをやってみました。
- コンピュテーション式のBuilerに失敗していないことを判定する関数を渡せるようにする
- let!ではなくdo!で書きつつ次の計算式に状態を渡せるようにする
利用部分を書くとこんな感じです。綱渡りを3回やっています。1回目は成功、2回目は左右の鳥の差が3を超えて失敗、3回目はバナナの皮に滑って失敗、という結果です。
let guard n = fun (left, right) -> abs(left - right) < n let tightrope = relay (guard 4) let routine = tightrope { do! landLeft 1 do! landRight 2 do! landLeft 3 } let routine2 = tightrope { do! landLeft 1 do! landRight 2 do! landLeft 5 } let routine3 = tightrope { do! landLeft 1 do! landRight 2 do! banana do! landLeft 3 } let print = function | Some(left, right) -> printfn "成功: %d, %d" left right | _ -> printfn "失敗" do exec routine (Some (0, 0)) |> print (* 成功: 4, 2 *) exec routine2 (Some (0, 0)) |> print (* 失敗 *) exec routine3 (Some (0, 0)) |> print (* 失敗 *)
シンプルに書けました!
コンピュテーション式には可能性を感じる!
Builderを書くのも読むのもしんどいけど。使う分には楽ですね。
コード全体はgistにあります。
コンピュテーション式でADO.NETのトランザクションを表現するアイデア
F#のコンピュテーション式がだんだんとわかってきたので思いついたアイデアをコードに落としてみました。
ADO.NETのトランザクションやコネクションの管理をすっきり書けるようにすることが目標。
ポイントは次のもの。
- トランザクション属性を宣言的に書けるようにする。required { ... } とか requiresNew { ... } みたいな。
- ADO.NETのDbConnectionとかDbTransactionを引数で渡す必要をなくす。かといってスレッドローカルも使わない
こんな感じです。
let insert = required { let! _ = Database.execute "insert person (id, name) values (1, 'hoge1')" let! _ = Database.execute "insert person (id, name) values (2, 'hoge2')" let! _ = Database.execute "insert person (id, name) values (3, 'hoge3')" return () } let delete = required { let! _ = Database.execute "delete from person" return () } let query = required { return! Database.query "select * from person" } let manipulate = requiresNew { do! insert let! result = query do! delete return result }
状態系のコンピュテーション式を導入するとコンピュテーション式の外から呼ぶのが手間なので、副作用のある関数とない関数を分離しやすくなるかもですねぇ。
F#でインタープリタ : 配列
配列が使えるようになりました。実装はF#のリストですけど。
次のスクリプトを解釈させると
a = [2, 3, 4] print(a[1]) a[1] = "three" print("a[1]: " + a[1]) b = [["one", 1], ["two", 2]] print(b[1][0] + ":" + b[1][1])
こんな出力になります。
3 "a[1]: three" "two:2"
多次元配列の扱いが難しかった。。。
以下のコードは値(value)を配列に代入するところです。
| Element(id, indexList, Array(elements)), value -> let rec loop elements indexList = match indexList with | x :: [] -> elements |> List.mapi (fun i element -> if i = x then value else element) |> Array | x :: xs -> elements |> List.mapi (fun i element -> if i = x then match element with | Array elements -> loop elements xs | _ -> failwith "invalid index" else element) |> Array | _ -> failwith "invalid index" let array = loop elements indexList let newEnv = Env.add id array env cont newEnv value
配列の要素を表す判別共用体(Element)に、配列の変数名(id)、インデックスのリスト(indexList)、配列を判別共用体(Array)を持たせています。
ネストした配列の中をトラバースして、代入先の要素を入れ替えています。