What's new with code quotations in F# 5?
One the improvements in F# 5 is to the code quotations feature. Code quotations now carry more information about certain type constraints of the functions inside them. To describe this more fully, I'll need to explain what code quotations and statically resolved type parameters are.
What are code quotations?
A code quotation is some expression wrapped in
<@ ... @>. When you quote code like this it isn't run, but it is converted into an abstract syntax tree, which can then be analysed and modified at runtime.
Here are two simple examples along with their values shown by FSI:
<@ 1 + 1 @> // Quotations.Expr<int> = Call (None, op_Addition, [Value (1), Value (1)]) <@ [| 5 |] @> // Quotations.Expr<int > = NewArray (Int32, Value (5))
Quoted expressions can even be evaluated at runtime:
open FSharp.Linq.RuntimeHelpers <@ 1 + 1 @> |> LeafExpressionConverter.EvaluateQuotation // 2 <@ 1 :: [ 2 ] @> |> LeafExpressionConverter.EvaluateQuotation // [ 1; 2 ]
One possible use case for this is for test assertions, as in the excellent Unquote library.
What are SRTPs?
Statically Resolved Type Parameters, or SRTPs, allow you to define functions or methods that accept an input of any type as long as the type has a property or method with a certain name. This is a kind of static "duck typing". Let's look at a simple example.
let inline length x = (^a : (member Length : _) x)
length function takes any value as long as it has a property
.Length. This means it can be used on both arrays and lists. Here are two examples of its usage along with outputs.
length [ 3 ] // 1 length [| "a"; "b" |] // 2
Using Quotations and SRTPs together
Here's some code that puts our SRTP function inside quotations and evaluates them:
<@ length [| "a"; "b" |] @> |> LeafExpressionConverter.EvaluateQuotation // exception before F# 5
Before F# 5, code quotations could not hold information about SRTPs. The quotation in the above code can be created but upon running the evaluation function, it fails with the exception "Dynamic invocation of get_Length is not supported".
But from F# 5 onwards, we can evaluate expressions like this using an SRTP function on different input types:
<@ length [ 3 ] @> |> LeafExpressionConverter.EvaluateQuotation // 1 <@ length [| "a"; "b" |] @> |> LeafExpressionConverter.EvaluateQuotation // 2
This is a small but interesting improvement to a pair of powerful features.