F# 2.0 Generic Inlining With Member Constraints

posted in: Language Features | 0

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"

Update (Oct 15, 2012)

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.