F# 2.0 Generic Inlining With Member Constraints
In this post, we are going to create a Converter
class who converts values to strings. The conversion rules are specified by passing the name of a culture to the class' constructor. The culture name can contain just the language (as ISO 639-1 alpha-2 code, e. g. "en") or the language and region (as ISO 3166 code, e. g. "US") combined with hyphen, e. g. "en-US". An empty culture name "" specifies the invariant culture, which falls back to "en".
The class has only one conversion method ToString
, which is generic. It takes a single parameter, which is the value to be converted. The parameter's type is inferred to have a member constraint, who restricts the type (at compile time) to have an instance member with the signature ToString: IFormatProvider -> string
. The parameter’s member is invoked with a member constraint invocation expression (see § 6.4.8. in The F# 2.0 Language Specification).
open System
open System.Globalization
type Converter(cultureName:string) =
/// The culture used by this converter.
member val Culture = CultureInfo.GetCultureInfo cultureName
/// Converts value to a string, based on the specified culture.
member inline self.ToString value =
(^T: (member ToString: IFormatProvider -> string)
value, self.Culture)
// Test
let germanConverter = Converter "de"
let nrString = germanConverter.ToString 1234.643 // "1234,643"
let dtString = germanConverter.ToString <|
DateTime(2003, 11, 25, 17, 38, 47) // "25.11.2003 17:38:47"
The converter's culture is now exposed via a public
property, based on F# 3.0 auto property syntax. (The Culture's value is evaluated only once, during class construction time.) Previously, the culture was a private
field, and the example could not compile in a regular source file, due to access rule violation. Strangely, it did compile in a scripting file; this is an example where the scripting compiler (wrongly) does not behave in the exact same way as the regular compiler.