C# developers will be familiar with the notion of Extension Methods - a feature introduced in C#3 that allows you to apply static (and typically stateless) methods to an existing type that you may or may not "own". It's really just syntactic sugar, but it can aid with writing Domain Specific Languages (DSLs), such as LINQ's set of extensions on top of
IEnumerable - and it can lead to slightly more idiomatic-looking C# code.
F# developers tend to avoid members on types in general and instead prefer separation of data and behaviours into separate entities - records or unions for data, and modules with functions for behaviours:
let ctx : HttpContext = ... let config = ctx.GetService<IConfiguration>() let connectionString = config // data |> ConfigHelpers.getConnectionString "TodoDb" // behaviour
Here, we have a module called
ConfigHelpers which has a function on it,
getConnectionString whose signature is something like
dbName:string -> config:IConfiguration -> connectionString:string i.e. given a config and database name, return back a connection string.
Interestingly, you can almost consider C#-style extension methods to be similar to standard F# functions insofar as you can "pipeline" any data into an F# function (as long as the types match up, of course) - the biggest difference is that the pipelined type is the final argument to an F# function (which is partially applied), whereas in C#, extension methods use the first argument as the "extension" point (indicated by the
However, extension members are sometimes useful in F#, especially since you can create extension properties as well as methods in F#! Consider the previous example, which we can simplify as follows:
This would be made possible with two extension members:
Configuration, which would be an extension property on the
TodoDbConnectionString, which would be an extension property on
The code for this is not complex:
type HttpContext with /// Gets the current IConfiguration from the service locator. member this.Configuration = this.GetService<IConfiguration>() type IConfiguration with member this.TodoDbConnectionString = match this.GetConnectionString "TodoDb" with | null -> failwith "Missing connection string" // simply panic if no connection string is found | v -> v
Note that use of
this is not mandated for the instance name - you could also use
config as desired.
Extension members can sometimes be useful, especially for accessing properties that are frequently required - in such cases, the alternative of using pipelines everywhere can quickly become messy when working with nested members.