Friday, October 7, 2011

Authoring Type Providers with the TypeProviderDSL from FSharpx

Several days ago, I submitted my AppSettings Type Provider to the FSharpx project. FSharpx is an open source library that adds a number of useful "functional constructs on top of the core F# library". I strongly encourage checking it out.

One of the many things in FSharpx that I find useful is the TypeProviderDSL. This is a DSL that sits on top of the ProvidedTypes module provided in the F# 3.0 Sample Pack. With this DSL and a few other helpers in FSharpx, the code from the AppSettings Type Provider that I showed in a previous post becomes easier to read. Plus, a few lines of code are shaved off in the process.

Let's look at a few of the features of the TypeProviderDSL that are used by the AppSettings Type Provider.

1. The erasedType function provides a simple way to create a ProvidedTypeDefinition.
2. The staticParameter function encapsulates the code needed to define a static parameter.
3. The literalField function makes it slightly easier to create a ProvidedLiteralField.
4. The addXmlDoc function provides a common way to add XML documentation.
5. Lastly, the |+> operator makes it very easy to add members.

Here's the end result:

module SampleTypeProviderWithFSharxDSL
open FSharpx.TypeProviders.DSL
open Microsoft.FSharp.Core.CompilerServices
open Samples.FSharpPreviewRelease2011.ProvidedTypes
open System
open System.Configuration
open System.IO
open System.Reflection
let rootNamespace = "SampleTypeProviderWithFSharxDSL"
let thisAssembly = Assembly.GetExecutingAssembly()
let tryParseWith func = func >> function
| true, value -> Some value
| false, _ -> None
let (|Bool|_|) = tryParseWith Boolean.TryParse
let (|Int|_|) = tryParseWith Int32.TryParse
let (|Double|_|) = tryParseWith Double.TryParse
let addTypedAppSettings (cfg:TypeProviderConfig) (configFileName:string) (tyDef:ProvidedTypeDefinition) =
try
let filePath = Path.Combine(cfg.ResolutionFolder, configFileName)
let fileMap = ExeConfigurationFileMap(ExeConfigFilename=filePath)
let appSettings = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None).AppSettings.Settings
Seq.iter (fun (key) ->
tyDef
|+> match (appSettings.Item key).Value with
| Int fieldValue -> literalField key fieldValue
| Bool fieldValue -> literalField key fieldValue
| Double fieldValue -> literalField key fieldValue
| fieldValue -> literalField key fieldValue
|> addXmlDoc (sprintf "Returns the value from the appSetting with key %s" key)
|> ignore) appSettings.AllKeys
tyDef
with
| exn -> tyDef
let typedAppSettings (cfg:TypeProviderConfig) =
erasedType<obj> thisAssembly rootNamespace "AppSettings"
|> staticParameter "configFileName" (fun typeName configFileName ->
erasedType<obj> thisAssembly rootNamespace typeName
|> addTypedAppSettings cfg configFileName )
[<TypeProvider>]
type public SampleAppSettingProvider(cfg:TypeProviderConfig) as this =
inherit TypeProviderForNamespaces()
do this.AddNamespace(rootNamespace, [typedAppSettings cfg])
[<TypeProviderAssembly>]
do ()


You can get FSharpx.TypeProviders from the NuGet Gallery at http://www.nuget.org/List/Packages/FSharpx.TypeProviders and find the source on GitHub at https://github.com/fsharp/fsharpx/tree/TypeProviders.