A type provider is a component loaded by the compiler (or F# Interactive, or the editor) that supplies types, properties and methods based on an external source such as a JSON sample, a database schema or a web service. The generated types are provided types. A provider is a .NET assembly and runs at compile time only; the compiled program does not need it.
Using a provider
A provider is used like a generic type. Its arguments are static parameters: values the compiler can compute at compile time, written in angle brackets, as strings, numbers, booleans or named arguments. Literal values can be given directly or through [<Literal>] names; the const keyword passes a constant expression.
#r "nuget: FSharp.Data"
open FSharp.Data
type Weather = JsonProvider<"""{ "city": "Oslo", "temp": 3.5, "tags": ["a", "b"] }""">
let w = Weather.Parse("""{ "city": "Rome", "temp": 18.0, "tags": [] }""")
printfn "%s is %f degrees" w.City w.Temp // members come from the sample
[<Literal>]
let SamplePath = __SOURCE_DIRECTORY__ + "/data.csv"
type Rows = CsvProvider<SamplePath, HasHeaders = true, Separators = ";">
type Db = SqlDataProvider<const "Server=.;Database=Shop", UseOptionTypes = true>
- A provider needs the assembly reference (
#rin a script, a package reference in a project). The compiler asks for permission to run it the first time (see security below). - The type a provider produces can be used in annotations, in
newexpressions and for static members; editors show its members as you type. - Static arguments can be named (
HasHeaders = true), and the compiler evaluates them while compiling, so a path that depends on__SOURCE_DIRECTORY__must be a literal.
Erased and generative providers
- Erased providers produce types that exist only in the project they are generated in; they are not written to the assembly, cannot be used from other assemblies, and can have delayed members, which lets them offer a very large or endless information space, of which a program uses a small part.
- Generative providers produce real .NET types that are written to the assembly and can be used by other code, so the typed form of the data source has to be compiled in.
Widely used providers named in the documentation: FSharp.Data (JSON, XML, CSV and HTML), SQLProvider (relational databases with LINQ queries), FSharp.Data.SqlClient (compile-time checked T-SQL), the Azure Storage type provider (blobs, tables, queues) and FSharp.Data.GraphQL (types from a GraphQL server).
Writing a provider
A provider is an assembly marked [<assembly: TypeProviderAssembly>] that contains a class marked [<TypeProvider>] that implements ITypeProvider (from FSharp.Core.CompilerServices). Most authors derive from the helper class TypeProviderForNamespaces and use the ProvidedTypes source files to describe types. The compiler talks to it with type provider invocations: the provided namespaces, type definitions (as System.Type objects), methods, constructors, properties, events, literal fields and parameters (as the matching System.Reflection info objects), and the static parameters of a provided type. A provided type has a kind (class, interface, struct, enum, delegate), may inherit, carries attributes and an accessibility, and, if erased, supplies the code that replaces each member as a quotation (the elaborated code).
open ProviderImplementation.ProvidedTypes
open FSharp.Core.CompilerServices
[<TypeProvider>]
type HelloProvider(config: TypeProviderConfig) as this =
inherit TypeProviderForNamespaces(config)
let ns = "Samples.Hello"
let asm = System.Reflection.Assembly.GetExecutingAssembly()
let t = ProvidedTypeDefinition(asm, ns, "Hello", Some typeof<obj>, hideObjectMethods = true)
do
t.AddMember(ProvidedProperty("Message", typeof<string>, isStatic = true, getterCode = fun _ -> <@@ "hello" @@>))
this.AddNamespace(ns, [ t ])
[<assembly: TypeProviderAssembly>]
do ()
Security
A type provider is code that runs inside the compiler and the editor, so the tools ask before loading one for the first time (Visual Studio shows a dialog; the choice can be changed in the settings), and a project that references a provider you did not trust fails to compile with an error that names it. A #r "nuget: …" reference loads the provider’s code in the same way.
Not described
Known, not yet covered
- Reference: the full provided-types chapter of the specification (name mangling of static parameters, restrictions on provided members, how an erased type’s code is elaborated, generative and erased differences in detail), the whole provider-writing tutorial, the provider interfaces (
ITypeProvider,ITypeProvider2,IProvidedNamespace), troubleshooting, and the F# 4.0 changes to static method arguments for providers are only named. TheProvidedTypeDefinitionsignatures in the example were not checked against the helper library. - Reference: the sample in the first section was written for this page: the provider names and their parameters (
JsonProvider,CsvProvider,SqlDataProviderand the options shown) are from memory of those libraries and were not checked against their documentation.
See also
- Directives –
#r "nuget: …"in scripts - Quotations – the code of an erased provider’s members