Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# custom type provider: "Container type already set" error

Recently I attended a tutorial by Keith Battochi on type providers, in which he introduced a variant of the MiniCsv type provider in the MSDN tutorial. Unfortunately, my laptop wasn't available, so I had to write down the code by hand as well as I could. I believe I've recreated the type provider, but I'm getting

error FS3033: The type provider 'CsvFileTypeProvider+CsvFileTypeProvider' reported an error: container type for 'CsvFileProvider.Row' was already set to 'CsvFileProvider.CsvFile,Filename="events.csv"

When I look at the code, I can't see how I'm adding the Row type to the container twice (or to some other container). Removing selected lines of the code doesn't help.

Here's how I'm calling the code in fsi:

#r "CsvFileTypeProvider.dll"
open CsvFileProvider
let eventInfos = new CsvFile<"events.csv">() ;;

And here's the code itself:

module CsvFileTypeProvider
open Samples.FSharp.ProvidedTypes
open Microsoft.FSharp.Core.CompilerServices

let getType str =
    if System.DateTime.TryParse(str, ref Unchecked.defaultof<_>) then
        typeof<System.DateTime>, (fun (str:Quotations.Expr) -> <@@ System.DateTime.Parse(%%str) @@>)
    elif System.Int32.TryParse(str, ref Unchecked.defaultof<_>) then
        typeof<System.Int32>, (fun (str:Quotations.Expr) -> <@@ System.Int32.Parse(%%str) @@>)
    elif System.Double.TryParse(str, ref Unchecked.defaultof<_>) then
        typeof<System.Double>, (fun (str:Quotations.Expr) -> <@@ System.Double.Parse(%%str) @@>)
    else
        typeof<string>, (fun (str:Quotations.Expr) -> <@@ %%str @@>)

[<TypeProvider>]
type CsvFileTypeProvider() =
    inherit TypeProviderForNamespaces()

    let asm = typeof<CsvFileTypeProvider>.Assembly
    let ns = "CsvFileProvider"

    let csvFileProviderType = ProvidedTypeDefinition(asm, ns, "CsvFile", None)
    let parameters = [ProvidedStaticParameter("Filename", typeof<string>)]

    do csvFileProviderType.DefineStaticParameters(parameters, fun tyName [| :? string as filename |] ->
        let rowType = ProvidedTypeDefinition(asm, ns, "Row", Some(typeof<string[]>))

        let lines = System.IO.File.ReadLines(filename) |> Seq.map (fun line -> line.Split(','))
        let columnNames = lines |> Seq.nth 0
        let resultTypes = lines |> Seq.nth 1 |> Array.map getType

        for idx in 0 .. (columnNames.Length - 1) do
            let col = columnNames.[idx]
            let ty, converter = resultTypes.[idx]
            let prop = ProvidedProperty(col, ty)
            prop.GetterCode <- fun [row] -> converter <@@  (%%row:string[]).[idx] @@>
            rowType.AddMember(prop)

        let wholeFileType = ProvidedTypeDefinition(asm, ns, tyName, Some(typedefof<seq<_>>.MakeGenericType(rowType)))
        wholeFileType.AddMember(rowType)

        let ctor = ProvidedConstructor(parameters = []) // the *type* is parameterized but the *constructor* gets no args
        ctor.InvokeCode <- //given the inputs, what will we get as the outputs? Now we want to read the *data*, skip the header
            fun [] -> <@@ System.IO.File.ReadLines(filename) |> Seq.skip 1 |> Seq.map (fun line -> line.Split(','))  @@>
        wholeFileType.AddMember(ctor)
        wholeFileType
        )

    do base.AddNamespace(ns, [csvFileProviderType])

[<TypeProviderAssembly>]
do()

Thanks for any help!

like image 814
Paul Blair Avatar asked Jun 23 '12 14:06

Paul Blair


1 Answers

you need to use another constructor when defining 'Row' type. Existing ProvidedTypeDefinition type exposes two constructors:

  • (assembly, namespace, typename, base type) - defines top level type whose container is namespace.
  • (typename, basetype) - defines nested type that should be added to some another type.

Now Row type is defined using first constructor so it is treated as top level type. Exception is raised when this type is later added to wholeFileType as nested.

like image 51
desco Avatar answered Oct 23 '22 05:10

desco