More About F# 3.0 Language Features

Version 3.0 of the F#  language (which ships with Visual Studio 2012) has a number of new language features.  I expect that most people who follow F# are aware of the two big new language features: Type Providers and LINQ queries, which work together to help F# developers have great experiences working with Big Data.  However there are a few other new minor language features worth calling out that people may not be aware of. 

In this blog post, I’ll describe four new minor features in the latest release of F#: CLIMutableAttribute, triple-quoted string literals, auto-properties, and unused variable warnings.  I’ll briefly describe how each feature works and what scenarios it helps improve.

Further information on using F# across various platforms can be found at fsharp.org.

The [<CLIMutable>] attribute

F# Records are often used as basic data structures for a set of named field values.  Though you can declare record fields to be mutable, people often use immutable records in F#, as there are a number of advantages to immutability.

type MyRecord = { Name : string; Age : int }
let r = { Name="Brian"; Age=36 }

However, there are a number of classes in .NET that only work with POCO types – Plain Old CLR Objects.  A POCO type is a .NET class that has a default constructor, and has getters and setters for all the properties.  An example of a class that only works with POCO types is the XmlSerializer.  F# immutable records are not POCO types; they do not have default constructors, or have setters for the properties.  As a result, if you try to use an F# immutable record with the XmlSerializer, it fails:

open System.Xml.Serialization 
let ser = new XmlSerializer(typeof<MyRecord>)
// throws System.InvalidOperationException: Program.MyRecord cannot be serialized because it does not have a parameterless constructor.

In F# 3.0, we’ve added CLIMutableAttribute.  If you attach this attribute to an F# record type,

[<CLIMutable>]
type MyRecord = { Name : string; Age : int }

then the F# compiler emits a default constructor and property setters into the generated IL for this type (though those features are not exposed to F# code).  As a result, the XmlSerializer is enabled to serialize/deserialize this data type:

open System.Xml.Serialization
let ser = new XmlSerializer(typeof<MyRecord>)
let ms = new System.IO.MemoryStream()
ser.Serialize(ms, r)
ms.Seek(0L, System.IO.SeekOrigin.Begin) |> ignore
let r2 = ser.Deserialize(ms)

This feature may also help you with other .NET scenarios, such as some WPF data-binding or Entity Framework scenarios.  If you enjoy the simple programming model of F# records, but have encountered some barriers interoperating with other .NET code that requires POCO objects, then CLIMutable may enable you to use these together.

(Speaking of uncommon attributes, in F# 3.0, the [<ReflectedDefinition>] attribute can now be placed on modules and type definitions, as a shorthand way to apply it to each individual member of the module/type.)

Triple-quoted strings

Like C#, F# 2.0 had two syntaxes for string literals: normal strings, and verbatim strings that are prefixed by the ‘@’ character.

let stringWithTabs = "AAA\tBBB\tCCC"// \t means tab
let path = @"C:\temp\tool.dat"// verbatim string, backslash not special

The verbatim syntax does not use backslash as an escape character, which makes it very useful for file paths.  However both of these string literal forms require you to escape quotation marks to embed quotes within the string literal:

let s1 = "escape \" quote"// use \" to have a quote character
let s2 = @"escape "" quote"// use "" to have a quote character

This makes it a bit of a pain to author string literals that have quote characters – XML with attributes being one common example.

In F# 3.0, we’ve introduced a new string literal format: triple-quoted strings.  In a triple-quoted string, everything between triple-quotes (“””…”””) is kept verbatim; there is no escaping at all.  As a result, if I want to have a bit of XAML as a string literal, it’s easy:

let xaml = """
<StackPanel xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Name="mainPanel">
<Border BorderThickness="15.0" BorderBrush="Black">
<StackPanel Name="stackPanel1">
<TextBlock Text="Super BreakAway!" FontSize="24" HorizontalAlignment="Center" />
<TextBlock Text="written in F#, by Brian McNamara - press 'p' to pause"
FontSize="12" HorizontalAlignment="Center" />
<Border BorderThickness="2.0" BorderBrush="Black">
<Canvas Name="canvas" Background="White" />
</Border>
</StackPanel>
</Border>
</StackPanel>"""

This feature can be useful in a number of scenarios, such as embedding XML blobs as data in a program (e.g. in a unit test as an expected return value from an XML web service), or for passing large strings as arguments to type providers.

(Note that since there is absolutely no escaping, it’s impossible to define a triple-quoted string literal that contains three quotes in a row, or that ends in a quote.  Instead, you can use e.g.

//let badContainsThreeQuotes = """blah blah """ blah blah"""
let containsThreeQuotes = """blah blah """ + "\"\"\"" + """blah blah"""
//let badEndsInQuote = """blah blah done""""
let endsInQuote = """blah blah done""" + "\""

where you use normal string literals to embed the quotes, and use ‘+’ to concatenate string literals.)

 

Auto-properties

For our sister language C#, C# 3.0 introduced auto-implemented properties, as a convenient shorthand for declaring a private backing field, getter, and setter for a property.  Many F# users have asked for the same thing.  In F# 2.0, you had to write

type MyClass() =
letmutable x = 0
member this.X with get() = x
and set(n) = x <- n

to author a basic class with a mutable data property.  In F# 3.0, you can just write

type MyClass() =
memberval X = 0 with get, set

for the same thing; “member val” is the way to introduce an auto-property, whose initial value is specified after the ‘=’.

(The implementation behind auto-properties can interact with other corner-case features or libraries, including F# recursive class initialization and Entity Framework serialization; you can always switch back to using normal properties for these scenarios.)

Warning about unused variables

An unused local variable in your code may be indicative of a bug.  The F# 3.0 compiler allows the command-line option “--warnon:1182”, which turns on warning number 1182 (off by default), which warns for unused variables.  Here’s a tiny contrived example:

let myfunc(foo,bar) =
if foo < 0 then
printfn "too small"

I’ve declared a function that takes a parameter ‘bar’ that is unused.  If I turn on this warning, then Visual Studio gives me a squiggle displaying the warning diagnostic:

Unused1
In a large project, this feature may help you find bugs.  On the other hand, when you’re in the midst of authoring new code, it may be a little distracting; if I was in the middle of typing the code for ‘myfunc’, the warning squiggle would be on the screen until I finally finished typing the last line

let myfunc(foo,bar) =
if foo < 0 then
printfn "too small"
else
printfn "sum is %d" (foo+bar)

where I finally do use ‘bar’.  Nevertheless, the warning has proven useful for us on large codebases; when we turned it on for the F# compiler code itself (roughly 100K LoC), we found a handful of bugs and a few cases of dead code.

Be aware that you may need to tweak some existing code to work with the warning.  There are two useful escape hatches you’ll want to know about if you choose to enable this warning.  First, there are cases where you do want to introduce a named variable that is unused.  The most common is with pattern-matching; here’s a short example:

type

type Tree =
| Tree of int * Tree * Tree
| Nil
let t = Tree(4, Tree(2, Tree(1,Nil,Nil), Tree(3,Nil,Nil)),
Tree(6, Tree(5,Nil,Nil), Tree(7,Nil,Nil)))
letrec height(t) =
match t with
| Nil –> 0
| Tree(data,left,right) -> max (height left) (height right)

Here the name ‘data’ on the last line helps document the meaning of this field of the discriminated union, but ‘data’ is itself unused.  As a result, it gets a warning squiggle.  The best solution here is to rename ‘data’ to ‘_data’:

    | Tree(_data,left,right)-> max (height left) (height right)

because names starting with underscore are ignored by the unused variable warning (to deal with this exact scenario).  This allows you to keep a meaningful name for documentation, but also explicitly call out (via naming convention) that it is not intended to be used.

The other case you may occasionally run into typically looks like this:

let log(timestamp,data) =
#if DEBUG
printfn "%A: %s" timestamp data
#else
()
#endif

Here the variables are used in Debug mode, but if you compile in Release mode, they will get flagged as unused.  For conditional code, the best solution is to use the ignore() function:

let log(timestamp,data) =
#if DEBUG
printfn "%A: %s" timestamp data
#else
ignore(timestamp,data)
#endif

Using ignore() marks the values as ‘used’ for the dataflow analysis, but compiles into a no-op.  Thus, either naming a variable starting with underscore or using ignore() are the two explicit ways to ‘locally silence’ the warning in a project that has “--warnon:1182” enabled.

Note that top-level (module-scoped) variables are always considered ‘used’:

let x = 42

will never get a warning about ‘x’ being unused if this code appears at the top-level (where it is exposed as public API out of the module).  The warning only applies to names bound within the local scope of a class, method, or function.

You can enable the warning for a project in Visual Studio by going to the ‘Build’ tab of the project properties, and adding “--warnon:1182” (without the quotes) to the “Other flags” field.  Be sure to do this for both Debug and Release configurations.

Summary

F# 3.0 has two major new language features (Type Providers and LINQ) that have been well-advertised.  This blog post describes a handful of minor new features that may be useful to know about.  I hope you’ve learned something useful!

Brian McNamara
Visual Studio F# Developer