読者です 読者をやめる 読者になる 読者になる

TypeProviderについて、勝手に補足

昨日行われたF# Meetup in TokyoのPart 1@kos59125さんがTypeProviderに関する素晴らしい発表をしてくれました。

TypeProviderを作りたい場合に、最初の入り口として素晴らしい発表資料だと思います。 何より、日本語で読めるのがすばらしいですね!

この資料について勝手に補足します。

ProvidedTypes.fsについて

ProvidedTypes.fsというのは、TypeProviderを作るうえであると便利なものを提供してくれる素敵なファイルです。 正直、これがない状態でTypeProviderを作るのは至難の業であり、事実上必須のファイルです。

しかし、このファイルは資料にもある通り、F# 3.0 Sample Packの中にあるものをTypeProviderを提供する各ライブラリがコピーして使っているという状況です。 さらに、各々がこのファイルをいじくり、独自の進化を遂げているような状態になっています。 これは、F# 3.0 Sample Packのリポジトリが全然更新されないので仕方ないと言えば仕方ないです。

この状況を打破する可能性となるプロジェクトとして、FSharp.TypeProviders.StarterPackというものがあります。 このプロジェクトではNuGetパッケージを提供しており、NuGet参照としてこれを追加するだけでProvidedTypes.fsが自分のプロジェクトに追加されます。 まだ試してはいませんが、NuGetパッケージの更新をすれば、最新版に置き換えてくれたりするのでしょう。

消去型の使い道

20枚目に載っている「消去型」ですが、発表では「ほとんど使わないのでunitでもいい」という風に説明されていました。 こいつの使い道ですが、例えばExcelHouganshiTypeProviderでは

ProvidedTypeDefinition(asm, ns, typeName, Some typeof<ExcelFile>, HideObjectMethods = true)

引用: Houganshi.fs

のように、ExcelFileという型を指定しています。 こうすると何が嬉しいかと言うと、

type MyHouganshi = Houganshi<"Def.fs">

let h = MyHouganshi("sample.xls", NPOI.NpoiBook.Load)
(* hを使った操作 *)
h.Save()

と書けます。 最後の行のSaveメソッド呼出しは、ExcelFileが持っているメソッドです。 全てをProvidedMethodで定義するのではなく、生成する必要のないものは消去型に指定した型に定義すると便利です。

他にも、既存の型を元にしたTypeProviderを作る場合、既存の型を受け取る関数にTypeProviderによって提供された型のインスタンスも渡せると便利ですよね。 そういう場合には、既存の型を消去型として指定することになります。

生成型について

TypeProviderには消去型と生成型という2つの種類があります。 発表されていたのは、消去型についてでした。 自分も最近までは消去型のTypeProviderしか作っていなかったのですが、 最近生成型の有用さに気付いたので書いておきます*1

消去型の欠点

多くのケースでは、消去型で問題ありません。 しかし、消去型は「型が提供される」といってもILレベルではその型は消えているので、 「提供された型」自体やその型情報が欲しい場合は消去型では対応できません。

そこで使えるのが生成型のTypeProviderです。

生成型のTypeProviderの作り方

参考になるのは、F#の標準ライブラリとして提供されているEdmxFileや、SqlDataConnectionの元となる、 DataProvidersです。 このTypeProviderは、一つのTypeProviderで複数の似たTypeProviderを提供するような構成になっており、このテクニックは消去型でも参考になると思います*2

生成型のTypeProviderで提供される型は、実際にどこかのアセンブリに存在する必要があります。 DataProvidersでは、最終的にcscを呼出すことで実際にdllを作り、 生成したdllを読み込んでいます

他にも、ProvidedTypeDefinitionIsErasedプロパティをfalseに設定する必要もあります。

生成型のTypeProviderの活用例は、アイディアがあるので近いうちに見せれたらな、と思います。

*1:ちなみにVisual Studio 2012がVisual Studio 11 Developer Previewだったころには、情報がなさ過ぎて生成型の方を使っていました。そのときとはGenerateという属性が型を提供される側で必要だったのですが、現在はこの属性は消えているようです

*2:ただし、その場合ProvidedTypes.fsのTypeProviderForNamespacesを継承せずに、ITypeProviderやIProvidedNamespaceを自分で実装する必要がある