Announcing F# 4.6 Preview

Phillip

January 24th, 2019

F# 4.6 is now fully released. See the announcement blog post for more.

We’re excited to announce that Visual Studio 2019 will ship a new version of F# when it releases: F# 4.6!

F# 4.6 is a smaller update to the F# language, making it a “true” point-release. As with previous versions of F#, F# 4.6 was developed entirely via an open RFC (requests for comments) process. The F# community has offered very detailed feedback in discussions for this version of the language. You can view all RFCs that correspond with this release here:

This post will detail the feature set and how to get started.

Get started

First, install either:

Next, update your FSharp.Core dependency to FSharp.Core 4.6 (or higher). If you’re using Visual Studio, you can do this with the NuGet Package Management UI. If you are not using Visual Studio, or prefer hand-editing project files, add this to the project file:

Once you have installed the necessary bits, you can use F# 4.6 with Visual Studio, Visual Studio for Mac, or Visual Studio Code with Ionide.

Anonymous Records

Aside from various bug fixes, the only language change in F# 4.6 is the introduction of Anonymous Record types.

Basic usage

From an F#-only perspective, Anonymous Records are F# record types that don’t have explict names and can be declared in an ad-hoc fasion. Although they are unlikely to fundamentally change how you write F# code, they do fill many smaller gaps F# programmers have encountered over time, and can be used for succinct data manipuation that was not previously possible.

They’re quite easy to use. For example, here how you can interact with a function that produces an anonymous record:

However, they can be used for more than just basic data containers. The following expands the previous sample to use a more type-safe printing function:

If you attempt to call `printCircleStats` with an anonymous record that had the same underlying data types but different labels, it will fail to compile:

This is exactly how F# record types work, except everything has been declared ad-hoc rather than up-front. This has benefits and drawbacks depending on your particular situation, so we recommend using anonymous records judiciously rather than replacing all your up-front F# record declarations.

Struct anonymous records

Anonymous records can also be structs by using the struct keyword:

You can call a function that takes a struct anonymous record can be done explicitly like this:

Or you can use “structness inference” to elide the `struct` at the call site:

This will treat the instance of the anonymous record you created as if it were a struct.

Note that the reverse is not true:

It is not currently possible to define IsByRefLike or IsReadOnly struct anonymous record types. There is a language suggestion that proposes this enhancement, but due to oddities in syntax it is still under discussion.

Taking things further

Anonymous records can be used in a broader set of more advanced contexts.

Anonymous records are serializable

You can serialize and deserialize anonymous records:

This outputs what you might expect:

Here’s a sample library that is also called in another project:

This may make things easier for scenarios like lightweight data going over a network in a system made up of microservices.

Anonymous records can be combined with other type definitions

You may have a tree-like data model in your domain, such as the following example:

It is typical to see cases modeled as tuples with named union fields, but as data gets more complicated, you may extract each case with records:

This recursive definition can now be shortened with anonymous records if it suits your codebase:

As with previous examples, this technique should be applied judiciously and when applicable to your scenario.

Anonymous records ease the use of LINQ in F#

F# programmers typically prefer using the List, Array, and Sequence combinators when working with data, but it can sometimes be helpful to use LINQ. This has traditionally been a bit painful, since LINQ makes use of C# anonymous types.

With anonymous records, you can use LINQ methods just as you would with C# and anonymous types:

This prints:

Anonymous records ease working with Entity Framework and other ORMs

F# programmers using F# query expressions to interact with a database should see some minor quality of life improvements with anonymous records.

For example, you may be used to using tuples to group data with a `select` clause:

But this results in columns with names like Item1 and Item2 that are not ideal. Prior to anonymous records, you would need to declare a record type and use that. Now you don’t need to do that:

No need to specify the record type up front! This makes query expressions much more aligned with the actual SQL that they model.

Anonymous records also let you avoid having to create AnonymousObject types in more advanced queries just to create an ad-hoc grouping of data for the purposes of the query.

Anonymous records ease the use of custom routing in ASP.NET Core

You may be using ASP.NET Core with F# already, but may have run into an awkwardness when defining custom routes. As with previous examples, this could still be done by defining a record type up front, but this has often been seen as unnecessary by F# developers. Now you can do it inline:

It’s still not ideal due to the fact that F# is strict about return types (unlike C#, where you need not explicitly ignore things that return a value). However, this does let you remove previously-defined record definitions that served no purpose other than to allow you to send data into the ASP.NET middleware pipeline.

Copy and update expressions with anonymous records

As with Record types, you can use copy-and-update syntax with anonymous records:

However, copy-and-update expressions do not restrict the resulting anonymous record to be the same type:

The original expression can also be a record type:

You can also copy data to and from reference and struct anonymous records:

The use of copy-and-update expressions gives anonymous records a high degree of flexibility when working with data in F#.

Equality and pattern matching

Anonymous records are structurally equatable and comparable:

However, the types being compared must have the same “shape”:

Although you can equate and compare anonymous records, you cannot pattern match over them. This is for two reasons:

A pattern must account for every field of an anonymous record, unlike record types. This is because anonymous records do not support structural subtyping – they are nominal types.

There is no ability to have additional patterns in a pattern match expression, as each distinct pattern would imply a different anonymous record type.

The requirement to account for every field in an anonymous record would make a pattern more verbose than the use of “dot” notation.

Instead, “dot”-syntax is used to extract values from an anonymous record. This will always be at most as verbose as if pattern matching were used, and in practice is likely to be less verbose due to not always extracting every value from an anonymous record. Here’s how to work with a previous example where anonymous records are a part of a discriminated union:

There is currently an open suggestion to allow pattern matching on anonymous records in the limited contexts that they could actually be enabled. If you have a proposed use case, please use that issue to discuss it!

FSharp.Core additions

It wouldn’t be another F# release without additions to the F# Core Library!

ValueOption expansion

The ValueOption type introduces in F# 4.5 now has a few more goodies attached to the type:

The DebuggerDisplay attribute to help with debugging

IsNone, IsSome, None, Some, op_Implicit, and ToString members

This gives it “parity” with the Option type.

Additionally, there is now a ValueOption module containing the same functions the the `Option` module has:

This should alleviate any concerns that `ValueOption` is the weird sibling of `Option` that doesn’t get the same set of functionality.

tryExactlyOne for List, Array, and Seq

This fine function was contributed by Grzegorz Dziadkiewicz. Here’s how it works:

Wrapping up

Although the total list of features in F# 4.6 isn’t huge, they still go quite deep! We encourage you to try out F# 4.6 and leave us feedback so that we can fine-tune things before the full release. As always, thank you to the F# community for their contributions – both in code and design discussion – that help us continue to advance the F# language.

Cheers, and happy hacking!