ようこそ。睡眠不足なプログラマのチラ裏です。

TypeProviders に関するちょっとした小ネタ集

F# Advent Calendar 2016 の 22日目の記事です。

TypeProviderについては以前、 型プロバイダー(TypeProvider)のちょっとしたアレコレというのを書きました。 書いたのはそーとー前ですが、今でも割と役に立つかもしれない以下の話題について扱っているので、気になるものがあればどうぞ。

- 型プロバイダーに渡すことができる静的引数んの種類
- 型プロバイダーの実行部分は部分的な制限がある
- 他のDLLに依存する型プロバイダーを作る
- 他のNuGetパッケージに依存した型プロバイダーを作ってNuGetで配布するときのやり方
- 型プロバイダーが参照するファイルの更新チェックを実装する
- 消去型と生成型



  さて。この記事は、TypeProviderに関する役立つものから役立たないものまで雑多な小ネタをいくつか適当に書いていきます。何かひとつでも引っかかるものがあって、持ち帰ってもらえれたら幸いかなと思います。

TypeProviderを作る際の下準備

古くは、F# Type Provider Templateとゆーありがたいテンプレートが用意されていました!が、もう過去のもの。今は何も考えずに、まず FSharp.TypeProviders.StarterPackPaketなり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に仕上げることができるかもしれません。

ちなみに想像に難しくはないと思いますが、消去型のTypeProviderenumを作ろうとしても、読んで字のなんとやら型情報が消えてしまいます!ので、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に渡す静的パラメータをハードコーディングしたくないパターンのやつ

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を作っていると、ArrayListの要素に対して直にアクセスするシーンが頻出します。ラムダ式の引数から静的パラメータを取り出す場合、直にインデックス指定でアクセスして値を取り出すのが割と正攻法な気がしていますが、まあある程度は割り切ってしまって、雑パターンマッチで済ませてパターンマッチの記述をさぼるのもアリかなあと思います。#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])


あるいは状況に応じて、RoslynFSharp.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 をコンストラクタで呼び出すには、ProvidedConstructorBaseConstructorCallでよしなに処理してあげればよいです。

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

で取得した baseMathodInvokeCode内の任意のタイミングで呼び出せばたぶんできます(雑すぎ)

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を用いて、privatefieldに値を設定しておて、それを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

これ、実はPaketNuGetを介して最新版を取得しても、まったく降ってこないんですよね。気づきにくい(!)  

軽い気持ちでコードを読もうとしても黒魔術すぎてわたしのような雑魚にはぶっちゃけわけわかめ(難しい)なんですが、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作っていきまっしょい(圧倒的情報不足のため、どんな情報でも共有お待ちしています)