Functional Java の HList を C# に移植して F# 拡張を作った
前々からFunctional JavaにHListが存在するのは知っていたのですが、なかなか手をだせていませんでした。
で、最近Lensライブラリ移植の息抜きにちょっと時間があったので移植してみました。
そもそもHListって?
heterogenous list。 日本語ではたまにヘテロリストとも呼ばれてるみたいですね。 "haskell HList"とか"HList shapeless"で検索すれば何かしら情報は手に入ると思います。
というわけで作った
下記リポジトリから取得できいます。 NuGetにも公開しているので簡単に試すこともできます。
pocketberserker/Data.HList · GitHub
移植自体はそんなに難しくなかった…嘘ですごめんなさい。
実は最初は Pure F# で実装していたのですが、型推論がだめな方向に作動してしまって型エラーをなんとかするのが面倒になった経緯があります。 以下残骸。
Pure F# で HList を実装しようとしたが…型推論に阻まれる
Apply.Cons
に型を書けば問題なく通るかもですが、それやるのであればいっそもうC#で実装したほうがいいんじゃない、ということでC#にスイッチしました。
使い勝手は?
HaskellやScala(shapeless)のそれに比べると使い勝手は悪いかもしれません…というかまず機能の差が。 それに彼らはこちらにはないHTやマクロ、型クラスがありますからね。
これは完全に型推論の力に依存して使いやすさが変わります。 サンプルコードを見ればその違いがよくわかるので、いかにリンクを並べておきます。
- Java https://github.com/functionaljava/functionaljava/blob/435cdac93bee544a93f5db9dabbaa0ec693e14e6/demo/src/main/java/fj/demo/HList_append.java#L13
- C# https://github.com/pocketberserker/Data.HList/blob/6db260b37d52764a9819fdc1c0918b6c47ee26ee/examples/HList.CSharpExamples/Program.cs#L10
- F# https://github.com/pocketberserker/Data.HList/blob/6db260b37d52764a9819fdc1c0918b6c47ee26ee/FSharp.Data.HList.Tests/HListTest.fs#L22
HAppendの例だけなのと、C#の実装がこれでよいのか自信がないのであれですが、C#...。
Javaは変数の型を書くのはだるいものの、型は比較的わかりやすいためそんなに考えなくてもなんとかなります。 が、C#はメソッドのほうが推論されないため、4つの型パラメータに何を当てはめれば望みの型になるのか考える必要があります。 F#もC#と同じ道をたどるかと思いきや…この程度のコードだと空気を読んで型を推論してしまいます*1。
まとめ
*1:推論に頼りすぎると前述みたいな型エラー事案に遭遇するのでご利用は計画的に
AppVeyorのSQL ServerにJDBC Driverで接続する
備忘録です。 簡単な経緯は以下の通り。
あと体力あれば URL これ使って scalikejdbc の テストできるか試してみたい
@pocketberserker やってみたんですけど URL テストの実行までいったけれど、(設定間違ってるのか)SQL Server繋げられてないです
2015-02-13 21:10:03 via Twitter Web Client to @pocketberserker
rpscala でそういう話題があったのに関連して、 AppVeyor で頑張れないかを xuwei さんのコードを引き継いで試してみました。
参考資料
- SQL Server 2012 ExpressにJava(JDBC)で外部から接続する - Symfoware
- configuration - Enable tcp\ip remote connections to sql server express already installed database with code or script(query) - Stack Overflow
だいたいこの辺りを情報を AppVeyor で実行できるようにしていきます。
設定やスクリプト
説明が面倒なので結論だけはります。 設定値やプロジェクト名もそのままになっているので、適宜読み替えてください。 また、今回の内容と直接関係のない部分は削っています。
あと、sbtはリポジトリに同梱しているパターンで試しているので、同梱したくない場合はsbtをダウンロードする必要もあります。
# appveyor.yml services: mssql2014 build_script: - netsh advfirewall firewall add rule name="jdbc driver for sql server udp" dir=in action=allow protocol=UDP localport=1434 - sc config sqlbrowser start= auto - net start sqlbrowser - ps: Set-ExecutionPolicy RemoteSigned - ps: .\Setting-SqlServer.ps1 - net stop "SQL Server (SQL2014)" - net start "SQL Server (SQL2014)" - netsh advfirewall firewall add rule name="jdbc driver for sql server tcp" dir=in action=allow protocol=TCP localport=1433 - sqlcmd -S ".\SQL2014" -U sa -P Password12! -Q "create database scalikejdbc;" - del scalikejdbc-core\src\test\resources\jdbc.properties - move scalikejdbc-core\src\test\resources\jdbc_sqlserver.properties scalikejdbc-core\src\test\resources\jdbc.properties - ps: wget http://download.microsoft.com/download/0/2/A/02AAE597-3865-456C-AE7F-613F99F850A8/sqljdbc_4.1.5605.100_enu.tar.gz -OutFile "sqljdbc.tar.gz" - 7z x -y -oC:\projects\scalikejdbc\ sqljdbc.tar.gz - 7z x -y -oC:\projects\scalikejdbc\ sqljdbc.tar - ps: mkdir .\scalikejdbc-core\lib - ps: cp .\sqljdbc_4.1\enu\sqljdbc41.jar .\scalikejdbc-core\lib\
// Setting-SqlServer.ps1 // バージョンに合わせて ComputerManagementの値を差し替える // SQL Server 2008 -> ComputerManagement10 // SQL Server 2012 -> ComputerManagement11 Get-WmiObject -Namespace root\Microsoft\SqlServer\ComputerManagement12 -Class ServerNetworkProtocol | Where-Object {$_.InstanceName -eq 'SQL2014' -and $_.ProtocolName -eq 'Tcp'} | ForEach-Object {$_.SetEnable()} $tcpProperties = Get-WmiObject -Namespace root\Microsoft\SqlServer\ComputerManagement12 -Class ServerNetworkProtocolProperty | Where-Object {$_.InstanceName -eq 'SQL2014' -and $_.ProtocolName -eq 'Tcp' -and $_.IPAddressName -eq 'IPAll'} foreach( $tcpProperty in $tcpProperties ){ $requestedValue = "" if($tcpProperty.PropertyName -eq "TcpPort"){ $requestedValue = "1433" } $tcpProperty.SetStringValue($requestedValue) }
SQL Server 2014 Espressで試していますが、たぶん SQL Server 2008あたりまでは普通に動きそうな気がします。
重要なところ?
SQLServer用のjarを同梱するのはライセンス的につらい感じがしたので、PowerShell 3.0から使える wget
エイリアスコマンドを使ってダウンロードし、AppVeyor にインストールされている 7Zip で解凍するのがミソです。
あと、各種設定をして再起動する前にデータベースを作成してもログイン権限がなくてテーブルにアクセスできないかも?
まとめ
今回はsbtで作業しましたが、gradleでもできるのではないでしょうか。
懸念事項は、AppVeyorの実行速度が遅いことと、mysqlやpotgresql前提で書かれたSQLがSQL Serverで通らないことがまれによくあることです。
CoArbitraryの実装比較
全国 QuickCheck 系ユーザの皆様こんにちは、もみあげです。
今日は
CoArbitraryとScalacheck - scalaとか・・・
に関連して、各言語の QuickCheck 実装の CoArbitrary
や Arbitrary (a -> b)
インスタンスについてみていきましょう。
CoArbitrary とは
QucikCheck のドキュメントによると、関数値の生成に使われる型クラスらしいです。
-- Haskell での定義 class CoArbitrary a where coarbitrary :: a -> Gen b -> Gen b
値と Gen
から新たな Gen
を生成できます。
第2引数の Gen
が必要なのは、最終的な値を生成するための Gen
を作るために、乱数生成器が必要だからだと思ってます。
CoArbitrary
のインスタンスを作るときには、 variant
という関数を用いることが多いです。
-- Haskell での定義 Integral n => n -> Gen a -> Gen a
この関数は Gen
の seed を変更するものです。
詳しく知りたい場合はソースコードを読みましょう。
Arbitrary (a -> b)
では、僕らの欲したインスタンスを見てみましょう。
-- Haskell での定義 instance (CoArbitrary a, Arbitrary b) => Arbitrary (a -> b) where arbitrary = promote (`coarbitrary` arbitrary)
promote
関数という関数は
http://hackage.haskell.org/package/QuickCheck-2.7.6/docs/Test-QuickCheck-Gen-Unsafe.html
Monad
に包まれた Gen
を Monad
な値を生成する Gen
にあげる関数です。
lift
と思ってもよさそうに見えますね。
promote
に適用する関数値の型は a -> Gen b
であり、 関数も Monad
なので条件を満たしています。
流れをまとめると
- 適用された値をから戻り値を生成するジェネレータの seed を変更する関数を作る
promote
で lifting させる- あたかも関数を生成したようにみせかけて property に渡す
- 関数適用したら
Gen
から戻り値がランダムに生成される
といったところでしょうか。
なお、一連の情報からこのインスタンスから得られる関数値の適用結果を使って何かテストするのは避けるべきということが伝わってくる気がします。
実装比較
てきとーに解説を済ませたところで、実装をみていきましょう。
Functional Java
https://github.com/functionaljava/functionaljava/blob/v4.2/core/src/main/java/fj/test/Gen.java#L637
promote
は Monad ではなく、1引数関数クラス F
が生成される Gen
を返します。
purescript-quickcheck
https://github.com/purescript/purescript-quickcheck/blob/v0.4.0/src/Test/QuickCheck.purs#L25-L26
promote
が repeatable
という名称なのと、 Monad
ではなく関数が対象です。
idris-quickcheck
Arbitrary
がほしいなら双対も一緒に定義しろ、という潔さです。
SwiftCheck
promote
は Monad
でも関数でもなく Rose
が対象です。
Persimmon.Dried
今日*1実装しました。
https://github.com/persimmon-projects/Persimmon.Dried/pull/2
たぶん動いています。
Gen.Apply
が 'T option
を返すので、こいつをなんとか 'T
にする苦肉の策として こんな感じにごまかしてます。
FsCheck
考え方はかわらないけど、実装が根本から異なるので気にしない方向で。
まとめ
CoArbitrary
の考え方は共通している- Idris のみ
Arbitrary
でcoarbitrary
の実装を要求する variant
関数はシグネチャは同じだが実装はばらばら(乱数生成器に依存する?)promote
はMonad
か関数かRose
が対象- 実装はそんなに難しくない?
だいたい似たような実装になっていますが、Gen
の構造が他と異なる Persimmon.Dried が若干強引な印象です。
実際、Persimmon.Dried の移植元である scalacheck でも以下の issue で同様の指摘がなされています。
https://github.com/rickynils/scalacheck/issues/136
scalacheck の動向に注視したいところですね。
それはそれとして、Functional Java やばい(褒め言葉)。
*1:ブログ投稿日 or pull request マージ日参照
各言語でtype classesを実装する際に使いそうな道具とか
現時点における GHC、Scala、F#、Java の type classes 実装に使う道具比較をメモしておきます。
言語比較したいわけではなく、論争を繰り広げたいわけでもないのであしからず。
あとてきとーに書いてる感は否めないので、間違っている言葉、表現、概念などあれば適宜突込みをお願いします。
高階型
F# や Java に関しては以下の記事を参照してください。
JavaでFree Monad - scalaとか・・・ F# で高階型のエミュレーション - pocketberserkerの爆走
ランクN多相的な何か
末尾再帰最適化
- GHC: よく知らない(そもそも lazy evaluation だし…)
- Scala: あり。 tailrec annotation をつけておくと末部再帰になっていない場合コンパイルエラー。
- F#: あり。次のバージョンあたりで Scala の tailrec annotation みたいな機能追加したいねとか言っていたような…
- Java: なし
型クラスのインスタンス作成
表現が思いつかなかった…とりあえず Maybe での例
-- GHC instance Monad Maybe where ...
// scalaz の Maybe での例 new Monad[Maybe] {
// F# type Option = Z { new Monad<Option.Z>
F# の場合、型パラメータの制約に hoge メソッドを持つ、とかできるので、高階型をエミュレーションしない場合はこの書き方にならない。 が、あれを説明するのは面倒なので FsControl というライブラリを検索してください。
Java は inner class に型クラスを継承させるのがやりやすい、だったかな?
型引数が2つある場合
- GHC: Monad (Free f) とかやれば書ける
- Scala: Monad[({type f[x] = Free[S, x]})#f]
- F#: 型を握りつぶすか、Option.Zにしておいてメソッドを呼び出したときに型パラメータを指定する
- Java: ワイルドカードを使う
Scalaは今後 type lambdas というシンタックスシュガーが入るかもしれない?
このあたりは F# が不利。 型をいい感じにできるのはもしかしたら type erasure 方式の利点かもしれない。
これと
これとかの比較だと、F# は型が残る上に変位指定ができない(できるならだれか教えてください)から、下手に obj も使えなくて悲しいことになっていたりします。 Java版はそのあたりすっぱり割り切っている。
型推論
これに関してはあまり書きたくない…。
どの言語にもあるけど、どこまで推論されるかは言語次第だよ、くらいでいいですかね? (指摘受けそうだなぁ…)
型クラスライブラリとか
- GHC: Hackage 調べましょう
- Scala: Scalaz が有名ですね
- FsControl, FSharp.Karma
- Java: Functional Java, highj, free-monad-java
おわりに
variance とか GADTs とか代数的データ型とかパターンマッチとか色々書いていない気がするけれど、気が向いたらで…。
あ、OCaml がないのは書いたことないからです…突っつかれたら勉強します。
F# で高階型のエミュレーション
この記事では、F# で 高階型 (higher kinded types) をエミュレーションすることについて記述します。
本当は 函数型なんたらの集い 2014 in Tokyo - connpass で話そうと思っていたのですが、運営を考えると発表できる気がしなくなった*1ので、記事にしてお茶を濁します。
注意事項
- F# で推奨される実装、ガイドラインから逸脱した実装になっています
- 型安全かどうかは考察しません(時間が足りない)
- この記事をみただけで"F# は残念な言語"という判断を下すのは誤りです
- 専門家ではないので、厳密なことは書けません。生暖かい目で見守ってください。
- Haskell, Scala(z), Java のコードも登場します
参考文献?
- highj - Haskell-style type classes in Java - Google Project Hosting
- ocamllabs/higher · GitHub
- xuwei-k/free-monad-java · GitHub
本記事に登場する FSharp 実装まとめ
本記事で実装した型に幾つか修正、追加を行ったライブラリが下記になります。
pocketberserker/FSharp.Karma · GitHub
問題点: この世界に奴はいない
世の中には高階型というものが存在します。高階型については、
(もりそば)Scalaによる高階型変数 - Higher-kind Generics - ( ꒪⌓꒪) ゆるよろ日記
とかを読んでみてください。
高階型が存在しないとどういう問題点があるのかという話については、
関数を扱えるだけでは、モナドを表現するには不十分過ぎる - scalaとか・・・
を読みましょう。
さて、 F# も高階型をサポートしていない言語の一つです。
抽象化や共通化のみを考える場合、F# には overload や"静的に解決された型パラメータ"を使えば、解決できる部分もあります。 これらの機能を使って実装されたライブラリとしては
があります。
しかし、型コンストラクタで高階型が要求されるような型クラスは、高階型がなければ実装が困難です。*2
解決案: ないなら…作るしかねぇ!
参考文献にあげた highj や higher、 free-monad-java では 高階型をエミュレーションするための型を用意しています。 この方法は、F# でも利用可能です。 本記事では free-monad-java のスタイルを採用して記述していきます。
// Scala の F[A] を表す型 type _1<'F, 'A> = interface end
_1
は 型パラメータを一つ持つ高階型です。
'F
は対象の型を、 'A
はその型がもつ型パラメータを表します。
今回は highj における μ
の代替として判別共用体を用います。
例: Identity
最も簡単な(そして標準に存在しない型の)例として、Identity型を定義してみましょう。
// 同名の判別共用体を用意 // highj の Id.μ 、 free-monad-java の Id.z 相当 type Id = Id // 実際の Id 型 type Id<'A> = { Value: 'A } with interface _1<Id, 'A>
まず、Id 型を表す高階型用の判別共用体 Id を用意します。
次に、実際に利用する Id 型に _1
を実装します。
こうすることで、その型が高階型であることを表現出来ます。
あとは、例えば Functor などを用意してあげれば、
type Functor<'F> = abstract member Map: ('A -> 'B) * _1<'F, 'A> -> _1<'F, 'B> module Functor = // 任意の Functor を対象にできる let map (fa: Functor<_>) f a = fa.Map(f, a)
ダウンキャストによって、それなりに抽象化できます。
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] module Id = // functor は F# の予約語なので使用できない let functor_ = { new Functor<Id> with // fa は _1<Id, 'A> なので、Id<'A> ダウンキャスト可能 member this.Map(f, fa) = (fa :?> Id<_>) |> map f :> _1<Id, _> }
例: Free
ここまでは、F# だと overload と 静的に解決された型パラメータを用いればよい話でした。
しかし、 Free Monad の実装には高階型が必要になるので、型パラメータでなんとかなるとは限りません。 Free Monad については検索したら色々と記事があるので探してください。
Haskell 実装
まずは、Free Monad の Haskell 実装から見てみましょう。 今回は ekmett 氏の free を参考にします。
f
が高階型になっており、 Free 型コンストラクタ内で Free 型を持っています。
Scala 実装
Scala では Scalaz のものが有名です。
S
が高階型となっています。
Haskell での Free 型コンストラクタに対応する Suspend 型コンストラクタをみると
Free を持つことがわかります。
_1 を用いてエミュレーション
Suspend を _1 を使って表現すると、次のようになります。
type private Suspend<'F, 'A> (a: _1<'F, Free<'F, 'A>>) = ...
あとはうまい具合に型を合わせてあげれば、Free Monadの完成です。 これに関しては本記事の内容ではないので、もっと詳しく知りたい方は
JavaでFreeモナドを表現するためのテクニックやexistential type(存在型)の話 - scalaとか・・・
を読んでください。 Java と F# での違いは
- F# は末尾再帰最適化される
- F# は type erasure ではないため、型パラメータの型が異なっていると InvalidCastException
- F# にてきとーに型推論させると、Gosubの型パラメータが obj に推論されて死ぬ(ので、結局明示しないとだめ)
あたりです。
問題点
- mixin がないので、既存の型に 1 を実装できない(Wrapper 型に対して 1 を実装するはめに)
- 明示的にキャストする必要があるので、ダウンキャストやアップキャストの嵐に
次回予告
もみあげ「やった、Free Monadができた...ん?」
GitHub「xuwei-k pushed to develop at xuwei-k/free-monad-java operational monad」
もみあげ「ナンデ!?ワイルドカードナンデ!?」
「Java開発者のための関数プログラミング」読んでみた
O'Reilly Japan - Java開発者のための関数プログラミング
最近こういう系統の本が増え始めた気がしますが、オライリーの電子書籍から発売されるとは思っていなかった。
そんなわけで、気になったので買って読んでみた。
ちなみに原著者のDean Wampler氏はバク本の著者の一人でもある(読み終わってから気がついた)。
- 作者: Dean Wampler,Alex Payne,株式会社オージス総研オブジェクトの広場編集部
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/01/20
- メディア: 大型本
- 購入: 3人 クリック: 320回
- この商品を含むブログ (38件) を見る
感想
訳者の方が書かれていることと大体同じことをおもった。
「Java開発者ための関数プログラミング」が出版されました - YAMAGUCHI::weblog
特に「6章 ここからどこへ向かうべきか」には参考文献の紹介が多く、これをきっかけにもっと学んでいってほしいというのが原著者の考えかな、とも思ったり。
本書には載ってない良書な日本語書籍としては
プログラミングの基礎 (Computer Science Library)
- 作者: 浅井健一
- 出版社/メーカー: サイエンス社
- 発売日: 2007/03
- メディア: 単行本
- 購入: 17人 クリック: 409回
- この商品を含むブログ (106件) を見る
オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門
- 作者: 株式会社テクノロジックアート,長瀬嘉秀,町田修一
- 出版社/メーカー: 技術評論社
- 発売日: 2010/11/13
- メディア: 単行本(ソフトカバー)
- 購入: 11人 クリック: 340回
- この商品を含むブログ (32件) を見る
この2冊が入りやすいかなと。
JavaCC,ANTLR,パーサコンビネータを触ってみた
某場所にて以下のような課題が出た。
- いわゆるΣ計算の構文を解析するParserと解析結果にあわせた処理を行うVisitorの作成
- 書式は sum i : NUMBER .. NUMBER of expression
- 変数はiで固定、iを1つ目のNUMBERから2つ目のNUMBERまで変化させながらexpressionを計算
- expressionは四則演算を行う。括弧を使用できること。
- jjtreeを用いて実装せよ
たとえば"sum i : 3 .. 5 of 1 + 2 * i"が27になる、みたいな形ですね。
でまぁ、jjtreeというかJavaCCだけだと
戦略というか方向性というか
- 時間短縮のため変数iはキーワードとしておく
- 変数が1つしかない仕様なので記号表は作らない
- Visitorのフィールドでiの当該値を記憶する
あとはその場の勢いで追加実装したりしています。
JavaCC,jjtreeで
コードは諸事情のため略。自分で書いたコードは170行くらい。
12月31日と1月1日で3時間ずつほど消費。割と問題が起こりそうにないところではまったり、typoしてジェネレートやり直しなどで時間をとられた気がする。
ANTLRで
年末から↓の本を読みはじめていたので、軽い気持ちで作ることに。
言語実装パターン ―コンパイラ技術によるテキスト処理から言語実装まで
- 作者: Terence Parr,中田育男,伊藤真浩
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/12/24
- メディア: 大型本
- 購入: 5人 クリック: 333回
- この商品を含むブログ (13件) を見る
コードは次のようになりました。
動かすのに必要なものは
- antlr-3.4.jar
Visitorではなく、tree grammarを使って木構文解析器をジェネレートする形にしました。行数は102行。
tree grammarの書き方がわからずに四苦八苦していたら1月2日が終わっていました…
JParsecで
そういえば、この課題が出た当初にこんなやり取りがあったことを思い出す。
@pocketberserker よし、JParsecを使いましょう!(マテ
2011-12-06 16:56:18 via Tween to @pocketberserker
思い出してしまったらやるしかないですよね。
動かすのに必要なものは
- jparsec-2.0.jar
- cglib-nodep-2.2.2.jar
JParsecで作ったやつだけ追加実装で乗算記号"*"を省略しても乗算が計算できるようにしてあります。320行。
ANTLRのときとは別の意味で書き方がわからず、チュートリアルやサンプルコード読んでいたら1月3日が通り過ぎていました…
おもったこと
言語は異なるものの、id:bleis-tiftさんが下記資料にかかれていること(83ページあたり以降)を実感しました。
- パーサジェネレータはホスト言語とはジェネレータ用の言語を覚える必要がある
- パーサジェネレータだと生成してコンパイルしなければならない
- パーサジェネレータで生成したコードは
- JParsecのコードを読むのはつらい(というか読める気がしない…)
- コード量が膨らむ
あと、パーサジェネレータは内部でホスト言語のコードも書かなければならなくなる場合も多いのでなんだかなぁと思ったりしますね。
JParsecを使ったコードは、今回実装したものが四則演算レベルなのでなんとか読めるレベルで収まっていますが、もっと複雑なことをしようとしたらわけがわからなくなると思われます。GroovyからJParsecを使えば読みやすさは多少改善できるかもしれませんが、パターンマッチやcase classがないのでデータ構造に関しては楽できないと思います。
あと、パーサコンビネータは小さいパーサを組み合わせて大きなパーサを作るので、部分部分のテストもしやすいです。
コードの解説は?
心に余裕ができたら書きます…