TypeProviders に関するちょっとした小ネタ集
F# Advent Calendar 2016 の 22日目の記事です。
TypeProvider
については以前、 型プロバイダー(TypeProvider)のちょっとしたアレコレというのを書きました。
書いたのはそーとー前ですが、今でも割と役に立つかもしれない以下の話題について扱っているので、気になるものがあればどうぞ。
- 型プロバイダーに渡すことができる静的引数んの種類 - 型プロバイダーの実行部分は部分的な制限がある - 他のDLLに依存する型プロバイダーを作る - 他のNuGetパッケージに依存した型プロバイダーを作ってNuGetで配布するときのやり方 - 型プロバイダーが参照するファイルの更新チェックを実装する - 消去型と生成型
さて。この記事は、TypeProvider
に関する役立つものから役立たないものまで雑多な小ネタをいくつか適当に書いていきます。何かひとつでも引っかかるものがあって、持ち帰ってもらえれたら幸いかなと思います。
TypeProviderを作る際の下準備
古くは、F# Type Provider Templateとゆーありがたいテンプレートが用意されていました!が、もう過去のもの。今は何も考えずに、まず FSharp.TypeProviders.StarterPack
を PaketなりNuGet
なり、お好きな方法でインストールしましょう。こちらが現在メジャーなテンプレとなります。最新バージョンは現時点では1.1.3.88
です。これさえあればすぐにTypeProvider
の作成に取り組むことができます!(ありがてぇ)まぁ、少しかゆいところに手が届いてない感じも否めないですが、StarterPack
という名前から察するにあえてやり過ぎないように意識しているのかもしれません。
なので、StarterPack
だけでは満たされないF#er諸兄は、お好みでHelper関数などを別途作ります。
module Shared = type private T = interface end let thisAssembly = typeof<T>.Assembly
TypeProvider
では、自身のAssemblyを取得する必要があるので適当に生やすとか。お作法的には、TypeProviderConfig.RuntimeAssembly
から取得する方がよいのかなあ?まあたぶんお好みで大丈夫かと。
let assembly = Assembly.LoadFrom( config.RuntimeAssembly)
TypeProvider
を作るためのシンプルなヘルパーをいくつか準備しておくのもいいでしょう。
type internal ProvidedTypes = static member DefineProvidedType(namespaceName, className, ?baseType, ?assembly, ?isErased) = let assembly = defaultArg assembly Shared.ThisAssembly let t = ProvidedTypeDefinition(assembly, namespaceName, className, baseType) isErased |> Option.iter (fun v -> t.IsErased <- v) t static member ConvertToGenerated(ty : ProvidedTypeDefinition) = if ty.IsErased then failwith "消去型ではこの操作はきたいされません" let asm = new ProvidedAssembly(IO.Path.GetTempFileName() + ".dll") asm.AddTypes([ty]) static member CreateSimpleErasedType(ns) = ProvidedTypes.DefineProvidedType(ns, "Erased", baseType = typeof<obj>) static member CreateSimpleErasedType(ns, typeName) = ProvidedTypes.DefineProvidedType(ns, typeName, baseType = typeof<obj>) static member CreateSimpleGeneratedType(ns) = let ty = ProvidedTypes.DefineProvidedType(ns, "Generated", baseType = typeof<obj>, isErased = false) ProvidedTypes.ConvertToGenerated ty ty static member CreateSimpleGeneratedType(ns, typeName) = let ty = ProvidedTypes.DefineProvidedType(ns, typeName, baseType = typeof<obj>, isErased = false) ProvidedTypes.ConvertToGenerated ty ty
ちなみにこちらは某サンプルにあるやつとほぼ同じやーつで。面白みは特にないです。
TypeProviderのデバッグ方法
TypeProvider
のデバッグ方法については、いろいろな人が何度か取り上げている気がしますが。あらためて。プロジェクトファイルfsproj
に以下のような定義を追加し(VisualStudio上から設定可)、デバッグ時に別のVisualSudioが立ち上がるように設定してあげます。
<StartAction>Program</StartAction> <StartProgram>$(DevEnvDir)devenv.exe</StartProgram> <StartWorkingDirectory>$(SolutionDir)</StartWorkingDirectory> <StartArguments>DebugSample.sln</StartArguments>
TypeProvider
と同一のソリューション内でデバッグ用に作成したTypeProvider
を参照しているプロジェクトの.fs
ファイルを開くと、その時点で Visual Studio氏が空気を読まずにDLLを掴みっぱなしにしてしまい、ビルドができなくなってしまいます。ファイルを開かないように気を付ければソリューション自体を分けなくともTypeProvider
のデバッグ実行は可能ですが、デバッグ用プロジェクトのソリューションは分けておいた方が、なにかと捗るかなと思います。デバッグしながら作っていてもよくわからないTypeProvider
の世界へようこそ(´・_・`)
enumの作り方
enum
を生成する雑サンプルです。
module ProvidedEnums = let Namespace = "ProvidedEnums" [<TypeProvider>] type TypeProvider() as this = inherit TypeProviderForNamespaces() let ty = ProvidedTypes.CreateSimpleGeneratedType(Namespace) do ty.SetBaseType typeof<Enum> let field = ProvidedLiteralField("Apple", ty, 1) do ty.AddMember field let field = ProvidedLiteralField("Pen", ty, 2) do ty.AddMember field do this.AddNamespace(Namespace, [ty]) let field = ProvidedLiteralField("Pineapple", ty, 2) do ty.AddMember field do this.AddNamespace(Namespace, [ty])
これを使うときは、
type Pikotaro = ProvidedEnums.Generated
このように書くとenum
を生成することができます。
このシンプルな例ではField
名はベタ書きの固定値を用いて生成されますが、任意の静的パラメータを渡すなりして任意の文字列ないし特定の外部ファイルの内容をもとに不特定多数のenum
を生成できるようなものを作ることもできます。工夫をすることにより、それなりに使い道のあるTypeProvider
に仕上げることができるかもしれません。
ちなみに想像に難しくはないと思いますが、消去型のTypeProvider
でenum
を作ろうとしても、読んで字のなんとやら型情報が消えてしまいます!ので、enum
を生成することができません。enum
を作りたい場合は生成型で生成する必要があります。消去型と生成型の違いここにアリ!って感じですね。消去型と生成型の違いをシンプル且つわかりやすく説明するのにはよいサンプルでした。はい。
カスタム属性の付け方。FlagsAttribute を付与しよう
FlagsAttribute
を付与したenum
を生成する方法から、カスタム属性を付与しつつ型を生成する方法についてみてみましょう。
たとえば以下のように、CustomAttributeData
のインスタンスを作るヘルパーを用意しておくと便利です。やっぱりオブジェクト式は便利ですね。はい。
module Attributes = //カスタム属性追加するやーつのヘルパー type CustomAttributeDataExtentions = static member Create(ctorInfo, ?args, ?namedArgs) = { new CustomAttributeData() with member __.Constructor = ctorInfo member __.ConstructorArguments = defaultArg args [||] :> IList<_> member __.NamedArguments = defaultArg namedArgs [||] :> IList<_> }
FlagsAttribute
のようなコンストラクタで引数を要求しないAttribute向けには、たとえばこんな関数を定義しておきます(雑)。
let createAttributeData<'TAttribute>() = CustomAttributeDataExtentions.Create(typeof<'TAttribute>.GetConstructor([||]), [| |])
適当なパラメータからFlagsAttribute
を付加したenum
を作る雑サンプルです。
module ProvidedFlagsEnums = let Namespace = "ProvidedFlagsEnums" [<TypeProvider>] type TypeProvider() as this = inherit TypeProviderForNamespaces() let root = ProvidedTypes.CreateSimpleGeneratedType(Namespace) do let p = ProvidedStaticParameter("names", typeof<string>) root.DefineStaticParameters ( parameters = [p], instantiationFunction = ( fun typeName names -> let ty = ProvidedTypes.CreateSimpleGeneratedType(Namespace, typeName) ty.SetBaseType typeof<Enum> let attrData = Attributes.createAttributeData<FlagsAttribute>() ty.AddCustomAttribute(attrData) let names = names |> Array.head |> string |> (fun x -> x.Split(',')) |> Array.mapi (fun i x -> x,i - 1) for name,i in names do let field = ProvidedLiteralField(name, typeof<int>, Math.Pow(2.,float i) |> int) ty.AddMember field ty ) ) do this.AddNamespace(Namespace, [root])
使い方はたとえばこうです。
type 属性 = ProvidedFlagsEnums.Generated<"なし,火遁,風遁,水遁,雷遁,土遁,木遁,溶遁">
サンプルなので雑に作りましたが、まじめに作ればそれなりに便利なものが作れそうな気はします。このように、生成する型に対してカスタム属性を付加することもできます。TypeProvider
によって生成された型であるということを属性によってマークして明示する用途に使うこともできますし。いろいろと応用の幅はありそうですね。
引数のある属性であればこんな感じのを複数パターン用意したり
let createAttributeData2<'TAttribute> arg1 = CustomAttributeDataExtentions.Create(typeof<'TAttribute>.GetConstructor([|arg1.GetType()|]), [| CustomAttributeTypedArgument(typeof<'TAttribute>, arg1) |])
安全性を無視できるのであれば、雑にこういうの作ったりするのもまぁアリかもですね。
let createAttributeData3<'TAttribute> args = let types = args |> Array.map (fun arg -> arg.GetType()) let values = args |> Array.map (fun arg -> CustomAttributeTypedArgument(typeof<'TAttribute>, arg)) CustomAttributeDataExtentions.Create(typeof<'TAttribute>.GetConstructor(types), values)
型安全絶対死守するマンを突き通して、ガチに頑張るのであればTryGetConstructor
的なやつを作ったりしてまじめにやる感じかなあ?そのあたりはお好みでどうぞ。
TypeProviderに渡す静的パラメータをハードコーディングしたくないパターンのやつ
StringReader type provider https://t.co/vMbcSlvgbb たし蟹なるほどねッ!(´・_・`)
— ぜくる (@zecl) 2016年11月22日
TypeProviderあるあるすぎて、なるほどたし蟹!と思ったしだいです。
静的パラメータをコードに直書きしたくないときは、TypeProvider
から静的パラメータを作れればええやん!のパターンのやつ。以下のような感じのをつくっとけばサクッと実現できますね。
module FileReader = open System.IO [<TypeProvider>] type public FileReaderProvider(config : TypeProviderConfig) as this = inherit TypeProviderForNamespaces() let nameSpace = this.GetType().Namespace let assembly = Assembly.LoadFrom( config.RuntimeAssembly) let providerType = ProvidedTypes.CreateSimpleErasedType(nameSpace, "FileReader") do providerType.DefineStaticParameters( parameters = [ ProvidedStaticParameter("Path", typeof<string>) ], instantiationFunction = fun typeName [| :? string as path |] -> let t = ProvidedTypes.CreateSimpleErasedType(nameSpace, typeName) let fullPath = if Path.IsPathRooted(path) then path else Path.Combine(config.ResolutionFolder, path) let content = File.ReadAllText(fullPath) t.AddMember <| ProvidedLiteralField("Content", typeof<string>, content) t ) this.AddNamespace( nameSpace, [ providerType ])
雑サンプルではありますが、こーゆーの用意しておくと便利かもーですね。
#nowarn "25" してもええんじゃよ
お気づきの方もいるかもしれませんが、上記のFileReaderProvider
では、パターンマッチが不完全な記述があるため、警告がでてしまっています(!)
該当のコードを切り出すと、ちょうどここです。配列に対するパターンマッチが不完全ですね。あらまあ。
instantiationFunction = fun typeName [| :? string as path |] ->
メソッドやプロパティの実行コードを実装するためのInvokeCode
の型は Quotations.Expr list -> Quotations.Expr
たったりもしますし、TypeProvider
を作っていると、Array
やList
の要素に対して直にアクセスするシーンが頻出します。ラムダ式の引数から静的パラメータを取り出す場合、直にインデックス指定でアクセスして値を取り出すのが割と正攻法な気がしていますが、まあある程度は割り切ってしまって、雑パターンマッチで済ませてパターンマッチの記述をさぼるのもアリかなあと思います。#nowarn "25"
で不完全なパターンマッチの警告を無視してしまうのも方法としてはまあアリかなと。そう割り切れるか割り切れないかはまあ。好みの問題。お好きなように。
ただ、F#では 1度#nowarn
しちゃうと、その行以下のファイル全体に対して影響が及んでしまう(イケていない)ので。ファイル内で再び警告を有効化できるように対応してもらいたいですね(はよ!)。
↓このあたりの話ですね。
Allow F# compiler directives like #nowarn to span less than an entire file.
#nowarn
しないなら こんな感じのアクティブパターンでがんばります?になるのかな。だるそう(´・_・`)
let (|Empty|NonEmpty|) l = match l with [] -> Empty | x::xs -> NonEmpty(x, xs, l)
ちなみに、F#er 諸兄の中で不完全なパターンマッチを絶対コロスマンの人ってどのくらいいるのでしょう。つまり、以下のような感じにコンパイルオプションでこの警告をコンパイルエラーに設定してる人のことです。
--warnaserror:25
まあこの手の警告がでたら、ほとんどの場合はすぐに直すのでコンパイラオプションでガチガチに指定するほどでもない説はかなりあります(´・_・`)
TypeProviderで生成される型のインスタンスへのアクセス
let instance = (%%(Quotations.Expr.Coerce ( args.[0], typeof<HogeBase>)) : HogeBase)
Quotations.Expr list
で最初に渡ってくるヤツがソレなので。そいつを
Quotations.Expr.Coerce
を用いて強制的に適切な型にキャストしてあげればよいです。
この方法で生成しようとしている型のインスタンスに対してコネコネ操作することができます。やったね。
有効な識別子かどうかをチェックしたりしてもよいかもね
静的なパラメーターによって動的に変化するメタデータを使用してC#
ライクな型を生成するケースなどでは、例えば Microsoft.CSharp.CSharpCodeProvider
を使用して、IsValidIdentifier
(有効な識別子かどうか)など厳密にチェックするような実装を検討してもいいかもしれません。
module GeneratedTypesWithStaticParams = let Namespace = "GeneratedTypesWithStaticParams" let csCodeProvider = new Microsoft.CSharp.CSharpCodeProvider() [<TypeProvider>] type TypeProvider() as this = inherit TypeProviderForNamespaces() let top = ProvidedTypes.CreateSimpleGeneratedType(Namespace) do let p = ProvidedStaticParameter("name", typeof<string>) top.DefineStaticParameters (parameters = [p], instantiationFunction = (fun typeName [|:? string as name|] -> let ty = ProvidedTypes.DefineProvidedType(Namespace, typeName, typeof<obj>, isErased = false) ProvidedTypes.ConvertToGenerated ty if not (csCodeProvider.IsValidIdentifier name) then failwithf "'%s' is not an identifier" name ty.DefineMethod("Method_" + name, [], typeof<string>, isStatic = true, invokeCode = fun [] -> <@@ name @@>) |> ignore ty)) do this.AddNamespace(Namespace, [top])
あるいは状況に応じて、Roslyn
やFSharp.Compiler.Service
を使ってほげもげー!ってガチに頑張るパターンもありけり(´・_・`)
static parameterをすべて省略したときのやり方のやーつ
静的パラメータありのTypeProvider
で、すべての静的パラメータを省略できることをサポートするためには、渡されるすべてのProvidedStaticParameter
について、省略された場合の既定値を設定しておいてあげればよいです。
[<TypeProvider>] type TypeProvider() as this = inherit TypeProviderForNamespaces() let top = ProvidedTypes.CreateSimpleGeneratedType(Namespace) do let p = ProvidedStaticParameter("name", typeof<string>, "省略されたよ") //既定値を指定するよ top.DefineStaticParameters (parameters = [p], instantiationFunction = (fun typeName [|:? string as name|] -> let ty = ProvidedTypes.DefineProvidedType(Namespace, typeName, typeof<obj>, isErased = false) ProvidedTypes.ConvertToGenerated ty if not (csCodeProvider.IsValidIdentifier name) then failwithf "'%s' is not an identifier" name ty.DefineMethod("Method_" + name, [], typeof<string>, isStatic = true, invokeCode = fun [] -> <@@ name @@>) |> ignore ty)) do this.AddNamespace(Namespace, [top])
type GTWSP = GeneratedTypesWithStaticParams2.Generated<"ABC"> type GTWSP2 = GeneratedTypesWithStaticParams2.Generated //すべての静的パラメータを省略できている let a = GTWSP.Method_ABC() let b = GTWSP2.Method_省略されたよ()
という感じで、静的パラメータをすべて省略可能なTypeProvider
を作ることもできますのでご活用ください。あまりお役立ち情報ではないうえ、知ってた速報!かもしれませんが。個人的にはなかなか気づけなかったんですよねコレ(´・_・`)
base..ctorを呼び出す方法
生成する型のbaseType の base..ctor をコンストラクタで呼び出すには、ProvidedConstructor
のBaseConstructorCall
でよしなに処理してあげればよいです。
module Constructors1 = let Namespace = "Constructors1" [<TypeProvider>] type TypeProvider() as this = inherit TypeProviderForNamespaces() let resizeArrayTy = typeof<ResizeArray<int>> let ty = ProvidedTypes.DefineProvidedType(Namespace, "Generated", baseType = resizeArrayTy, isErased = false) do ProvidedTypes.ConvertToGenerated ty let ctor = ProvidedConstructor([ProvidedParameter("x", typeof<string>)]) ctor.BaseConstructorCall <- //ここんとろね fun [this; _] -> let ci = resizeArrayTy.GetConstructor([| typeof<int>|]) ci, [ this; <@@ 100 @@>] ctor.InvokeCode <- fun [this; arg] -> let addMethod = ty.GetMethod "Add" let add = E.Call(E.Coerce(this, resizeArrayTy), addMethod, [ <@@ int (%%arg : string) @@>]) <@@ (%%add : unit) (%%E.Coerce(this, resizeArrayTy) : ResizeArray<int>).Add(int (%%arg : string) + 100) @@> ty.AddMember ctor this.AddNamespace(Namespace, [ty])
メソッドのオーバーライドで、baseのメソッドを呼び出すときの書き方
let baseMathod = methodInfo.GetBaseDefinition() :?> ProvidedMethod
で取得した baseMathod
をInvokeCode
内の任意のタイミングで呼び出せばたぶんできます(雑すぎ)
abstract メソッド/プロパティをオーバーライドするときの書き方
TODO:あとで書くかも(そーゆー場合だいたい書かない)
任意のインターフェイスを実装するときの書き方
TODO:あとで書くかも(そーゆー場合だいたい書かない)
staticコンストラクタとstatic fieldの書き方
staticコンストラクタとstatic fieldをつくるサンプル
module StaticConstructorAndStaticFields = let Namespace = "StaticConstructorAndStaticFields" [<TypeProvider>] type TypeProvider() as this = inherit TypeProviderForNamespaces() let top = ProvidedTypes.DefineProvidedType(Namespace, "Generated", baseType = typeof<System.Windows.Controls.Button>, isErased = false) do ProvidedTypes.ConvertToGenerated top let propertyName = "StateProperty" let f = ProvidedField(propertyName, typeof<System.Windows.DependencyProperty>) do f.SetFieldAttributes (FieldAttributes.Public ||| FieldAttributes.InitOnly ||| FieldAttributes.Static) top.AddMember f do let typeInit = ProvidedConstructor([], IsTypeInitializer = true) typeInit.InvokeCode <- fun [] -> Quotations.Expr.FieldSet ( f, <@@ System.Windows.DependencyProperty.Register(propertyName, typeof<bool>, top, Windows.PropertyMetadata(false)) @@> ) top.AddMember typeInit do this.AddNamespace(Namespace, [top])
とゆー感じで書けるます。
この例とは直接関係はありませんが、生成対象のclass
に get only なプロパティーを生やす場合の実装方法として、返却する値はリフレクションを使ってプロパティに突っこんでおけばええやん!説が、私の中では割と正義だったのですが。Quotations.Expr.FieldSet
を用いて、private
なfield
に値を設定しておて、それをget onlyなプロパティで返してあげるように実装してあげる方がキレイなのかな。と思い直している今日この頃です。でもまあ、力強くリフレクションで済ませてしまう方が実装楽ではあります(雑にそう書いてもバチはあたらないでしょう!)。
Quotations.Exprを活用しよう
そんなこんなで、Quotations.Expr
に生えている各種 static member を把握してうまく活用することで、TypeProvider
でできることの幅がぐんと広がるかと思います。
↓を参照してください。
Quotations.Expr Class (F#)
少しだけ例を紹介しようかなとも思いましたが、長くなるので雑にリンクだけ貼っておきます(読めば察せると思いますので!)。
TypeProvider の静的パラメータに任意の型を渡せるようになる時代がくーるー!?
F# RFC FS-1023 - Allow type providers to generate types from types ってゆー話があり。次期バージョンの F# でこれがサポートされると...、もうやりたい放題!!ですね。夢が広がりんぐ。はやくきてくれ~(´・_・`)
ひとつの TypeProvider から異なる複数の型(生成型)を生成できるようにする方法
消去型のTypeProviderにおいてはまったく意識する必要はないのですが、生成型のTypeProviderでは意識すべき内容です。
type Fuga = HogeTypeProvider<"Fuga"> type Piyo = HogeTypeProvider<"Piyo">
上記のように1つのTypeProvider
を用いて複数の異なる型を生成する。こんな当たり前(そう)なことが、生成型のTypePrpvider
においては、そうできるように実装を考慮をしていないと実現できません。考慮せずに実装した場合は、たとえばおおよそ以下のような感じのエラーが出てしまい、ほげーーーっと剥げてしまいます。
重大度レベル コード 説明 プロジェクト ファイル 行 抑制状態 エラー バイナリ 'D:\Code\F#\TypeProviders\TypeProviderSample\ConsoleApplication1\obj\Debug\ConsoleApplication1.exe' の書き込み中に問題が発生しました: Error in pass3 for type Program, error: Error in GetMethodRefAsMethodDefIdx for mref = (".ctor", "Hoge"), error: 種類 'Microsoft.FSharp.Compiler.AbstractIL.ILBinaryWriter+MethodDefNotFound' の例外がスローされました。 ConsoleApplication1 FSC 1
このようなエラーを回避するには、TypeProvider
の各ルートタイプに対して個別の一時アセンブリを作成してあげる必要があります。この考慮が割と忘れがちになります(TypeProvider実装考慮漏れあるある)。生成型のTypeProvider
を作るときのお決まりのパターンというか、ある種嗜みのようなものかなと思いますので、覚えておいて損はないかと思います。
やり方については以下を参照してもらえればよいかと思います。
Creating more than one root type with generative type provider - stackoverflow
https://gist.github.com/dsevastianov/46d1a8495c4af46a9875
おれたちのVisualSudioさんが、期待ている config ファイルを参照してくれない件について
TypeProvider
が参照するDLLからConfigurationManager
を使用してapp.config
から情報を取得しようとした場合、われわれが期待するapp.config
を参照してくれないことがある!とゆ-残念無念な問題があります。VisualStudioでホストしてTypeProvider
を動作させた場合、devenv.exe
からみた configファイルを対象として動作してしまうのが原因です(どーしよーもない)。
devenv.exe
(VisualSudio)からみたconfig
は、われわれが期待するソレではなく、以下のようないわゆるVisualStudioの在り処にあるconfigファイルを対象に動作します(これはひどい罠)。
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe.config
TypeProvider
から参照されるDLLからConfigurationManager
を用いて、適切にわれわれが期待するapp.config
を読み込みたいケースでは、なんらかの方法で対象のPathをConfigurationManager
に直接指定せざるを得ないです。まあ、この問題にぶちあたるケースの方がよっぽどレアケース説!がありすぎて。ほぼ誰の役にも立たない情報かもしれません。
TypeProviderをテストするためには
FSharp.TypeProviders.StarterPack
の中の人でもある Mavnn(@mavnn)さんがまとめてくださっているので、くわしくはそちらをご参照ください!(THE丸投げ)。
Testing ProvidedType.fs by Example
Cross-Targeting Type Providers
素でFSharp.TypeProviders.StarterPack
を使っているとなかなか気付きにくいことですが、FSharp.TypeProviders.StarterPackのGitHubリポジトリの方を見に行くと、Cross-Targeting Type Providers
とかなんとか何やら興味深い記述が。
リポジトリにある以下の3つのファイルを追加すれば利用できるとのこと。
- AssemblyReader.fs - AssemblyReaderReflection.fs - ProvidedTypesContext.fs
これ、実はPaket
やNuGet
を介して最新版を取得しても、まったく降ってこないんですよね。気づきにくい(!)
軽い気持ちでコードを読もうとしても黒魔術すぎてわたしのような雑魚にはぶっちゃけわけわかめ(難しい)なんですが、AssemblyReader.fs
は軽量.NETアセンブリリーダー、AssemblyReaderReflection.fs
はディスク上のアセンブリに対するリフレクションオブジェクトの実装。ProvidedTypesContext.fs
は、mscorlib
またはSystem.Runtime
のDLLが見つからないような環境下において、軽量.NETアセンブリリーダーを使用してよしなにCross-Targeting対応してくれるやーつみたいな感じなのかな。たぶん。
これの使い方自体は簡単で。ProvidedTypesContext
でコンテキストをつくって
let ctxt = ProvidedTypesContext.Create(config)
そのコンテキスト経由で各種Providedほげもげを生成してTypeProvider
を構成していけばよいみたいです。ほーぅ。
let myType = ctxt.ProvidedTypeDefinition(asm, ns, "MyType", typeof<obj>)
実装構成によっては割と面倒くさいことになり兼ねない感じがプンプンします(が、そんなときはReaderモナドとかでうまいこと乗り切ってください(察し))
以下あたりも読んでおきましょうかね。
Developing Cross-Platform and Multi-Targeting Type Providers
ということで、とりとめもなくTypeProvider
に関する小ネタをいくつか書いてみました。まだまだネタは尽きないような気もしましたが、私の気力がちょうど尽き(and 時間切れ)たところで終わります。あとで書くかものやつは、書かない可能性が高そうですが大目に見てください(誰かが書いてくれてもいい)。
ところで、TypeProvider
は、弊社次期タイトル「黒騎士と白の魔王」でもガチで使用されています。リリースされましたら、そのあたりにも注目してみてください(まったく表に出てきませんので注目のしようがありませんが!)。どのように使われているのかについてお話する機会が持てるかどうかはわかりませんが。その時は面白い話もできるかもしれません。つらつらダラダラと書きましたが、何かひとつでも参考になれば幸いです。
TypeProvider
作っていきまっしょい(圧倒的情報不足のため、どんな情報でも共有お待ちしています)