F# 6 brings a wealth of new features to the language, library, and tooling aimed at improving performance and making it simpler for programmers wishing to switch to it.
F# 6 adopts a new foundation for concurrency based on resumable code, which is a high-performance way to build asynchronous code or yielding state machines. Resumable code is a new low-level feature of the language that enables defining compositional re-entrant code. This feature is not meant to be used directly through its associated ResumableCode<'Data, 'T>
delegate type; rather, it provides the basis for the implementation of new computation expressions.
The first outcome of the introduction of resumable code in F# 6 is the new task {}
computation expression, which aims to provide a more performant way of creating an asynchronous task, whereas in F# 5 you would start an async task by executing:
let readFilesTask (path1, path2) =
async {
let! bytes1 = File.ReadAllBytesAsync(path1) |> Async.AwaitTask
let! bytes2 = File.ReadAllBytesAsync(path2) |> Async.AwaitTask
return Array.append bytes1 bytes2
} |> Async.StartAsTask
You can now use task
and omit the explicit AwaitTask
calls:
let readFilesTask (path1, path2) =
task {
let! bytes1 = File.ReadAllBytesAsync(path1)
let! bytes2 = File.ReadAllBytesAsync(path2)
return Array.append bytes1 bytes2
}
Besides the simplified syntax, task
has much better performance thanks to it relying on a completely different mechanism underneath and ensures improved interoperability with .NET tasks. While in most cases you can safely replace any use of async
with task
, you should nevertheless pay attention to a number of differences between the two, such as task
not implicitly propagating a cancellation token, not supporting asynchronous tail-calls, and so on.
A powerful feature of F# is the capability of defining named partitions to match input data, with the possibility of using those names in matching expressions. This is called Active patterns and F# 6 brings them further with support for struct representation. This basically enables the definition of an active pattern like (A|_)
to return a value option instead of an F# regular option value. To make use of these features, you have to use the Struct
attribute and replace Some
and None
with ValueSome
and ValueNone,
respectively:
[<return: Struct>]
let (|Int|_|) str =
match System.Int32.TryParse(str) with
| true, int -> ValueSome(int)
| _ -> ValueNone
Value options can be useful to speed up your code in many scenarios, although not always.
Another new feature aiming at making the language faster is the new InlineIfLambda
attribute that you can use with lambda arguments to indicate they should be inlined at call sites. Inlined lambdas can be particularly convenient, for example, within tight loops.
On the language syntax side, F# 6 does away with the OCaml legacy of using expr.[idx]
to index into vectors and adopts the more common expr[idx]
. While the old syntax remains valid, the new one is the preferred way of indexing and you can activate a warning (/warnon:3566) to help get rid of the old one.
F# 6 brings many more new features than can be covered here, including implicit integer and upcast conversions, improved debugging, faster compiler, and more. Do no miss the official announcement for the full details.