Skip to content
Stephen Swensen edited this page Dec 30, 2023 · 16 revisions

User Guide

Installation

Unquote targets netstandard2.0 and FSharp.Core 8.0.100.

To use Unquote, install the latest version via NuGet

You must also obtain and install your unit testing framework of choice separately (NUnit, xUnit.net, and Fuchu/Expecto have been given special support, but any exception-based unit testing framework including MSTest is supported as well).

To use Unquote within FSI, locate and add a reference to Unquote.dll using the #r directive (.NET 4.5 build).

Breaking Changes from Unquote 3.x to Unquote 4.x

  • Support for .NET 4.0 and PCL builds dropped
  • The long obsolete '?' suffixed have been removed

Breaking Changes from Unquote 2.x to Unquote 3.x

Support for Silverlight 4 has been dropped.

The '?' suffixed operators have all been replaced with equivalent '!' suffixed operators in Unquote 3.0.0 in order to avoid conflicts with Nullable operators introduced by F# 3.0. You may open the package Swensen.Unquote.Assertions.Obsolete to continue using the now obsolete '?' suffixed operators.

The signature of Swensen.Unquote.Operators.unquote has been changed from Quotations.Expr -> unit to Quotations.Expr -> UnquotedExpression.

Features

The Swensen.Unquote namespace contains three AutoOpen modules: Assertions, Operators, and Extensions. Therefore you may choose to bring all of Unquote's features into top-level scope simply by opening Swensen.Unquote or you may choose to open individual modules or even alias individual modules for a finer level of control.

Assertions

The Swensen.Unquote.Assertions module contains all functions used for performing unit testing assertions. These include test, raises, and a series of '!' suffixed binary infix operators. All of these operators can be used within a unit test enabled project or FSI.

Unquote chooses its output source as follows

  • if loaded in FSI then print to the console (unless Fuchu is loaded)
  • else if xUnit or NUnit loaded in currently executing assembly, then use appropriate test failed methods
  • else throw a Swensen.Unquote.AssertionFailedException with a message

The following is a reference of the functions available and some examples:

  • val inline test : Quotations.Expr<bool> -> unit
> test <@ (1+2)/3 = 1 @>;;
val it : unit = ()
> test <@ (1+2)/3 = 2 @>;;

Test failed:
	
(1 + 2) / 3 = 2
3 / 3 = 2
1 = 2
false

val it : unit = ()
  • val inline ( =! ) : 'a -> 'a -> unit when 'a : equality
> [1;2;3;4] =! [4;3;2;1];;

Test failed:

[1; 2; 3; 4] = [4; 3; 2; 1]
false

val it : unit = ()
  • val inline ( <! ) : 'a -> 'a -> unit when 'a : comparison
  • val inline ( >! ) : 'a -> 'a -> unit when 'a : comparison
  • val inline ( <=! ) : 'a -> 'a -> unit when 'a : comparison
  • val inline ( >=! ) : 'a -> 'a -> unit when 'a : comparison
  • val inline ( <>! ) : 'a -> 'a -> unit when 'a : equality
  • val inline raises<'a when 'a :> exn> : Quotations.Expr -> unit
> raises<exn> <@ (null:string).Length @>;;
val it : unit = ()
> raises<NullReferenceException> <@ (null:string).Length @>;;
val it : unit = ()
> raises<System.ArgumentException> <@ (null:string).Length @>;;

Test failed:

Expected exception of type 'ArgumentException', but 'NullReferenceException' was raised instead

null.Length
System.NullReferenceException: Object reference not set to an instance of an object.
   at Swensen.Unquote.Evaluation.evalInstance@275(FSharpList`1 env, FSharpOption`1 expr)
   at Swensen.Unquote.Evaluation.eval@133(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.reduce(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.loop@132(FSharpList`1 env, FSharpExpr expr, FSharpList`1 acc)

val it : unit = ()
> raises<exn> <@ 3 @>;;

Test failed:

Expected exception of type 'Exception', but no exception was raised

3

val it : unit = ()
  • val inline raisesWith : Expr -> (#exn -> Expr<bool>) -> unit
> raisesWith<System.NullReferenceException> <@ (null:string).Length @> (fun e -> <@ e.ToString() = null @>);;

Test failed:

The expected exception was raised, but the exception assertion failed:

Exception Assertion:

System.NullReferenceException: Object reference not set to an instance of an object.
   at Swensen.Unquote.Evaluation.evalInstance@275(FSharpList`1 env, FSharpOption`1 expr)
   at Swensen.Unquote.Evaluation.eval@133(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.reduce(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.loop@132(FSharpList`1 env, FSharpExpr expr, FSharpList`1 acc).ToString() = null
"System.NullReferenceException: Object reference not set to an instance of an object.
   at Swensen.Unquote.Evaluation.evalInstance@275(FSharpList`1 env, FSharpOption`1 expr)
   at Swensen.Unquote.Evaluation.eval@133(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.reduce(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.loop@132(FSharpList`1 env, FSharpExpr expr, FSharpList`1 acc)" = null
false

Test Expression:

null.Length
System.NullReferenceException: Object reference not set to an instance of an object.
   at Swensen.Unquote.Evaluation.evalInstance@275(FSharpList`1 env, FSharpOption`1 expr)
   at Swensen.Unquote.Evaluation.eval@133(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Evaluation.eval(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.reduce(FSharpList`1 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.loop@132(FSharpList`1 env, FSharpExpr expr, FSharpList`1 acc)

val it : unit = ()

Operators

The Swensen.Unquote.Operators module contains additional functions for decompiling, evaluating, and reducing quotation expressions. The following is a reference of the functions available and some examples:

  • val inline decompile : Quotations.Expr -> string
> decompile <@ (1+2)/3 @>;;
val it : string = "(1 + 2) / 3"
  • val inline eval : Quotations.Expr<'a> -> 'a
> eval <@ "Hello World".Length + 20 @>;;
val it : int = 31
  • val inline evalRaw : Quotations.Expr -> 'a
> evalRaw<int> <@@ "Hello World".Length + 20 @@>;;
val it : int = 31
  • val inline reduce : Quotations.Expr -> Quotations.Expr
> <@ (1+2)/3 @> |> reduce |> decompile;;
val it : string = "3 / 3"
  • val inline reduceFully : Quotations.Expr -> Quotations.Expr list
> <@ (1+2)/3 @> |> reduceFully |> List.map decompile;;
val it : string list = ["(1 + 2) / 3"; "3 / 3"; "1"]
  • val inline isReduced : Quotations.Expr -> bool
> <@ (1+2)/3 @> |> isReduced;;
val it : bool = false
> <@ 1 @> |> isReduced;;
val it : bool = true
  • val inline unquote : Quotations.Expr -> UnquotedExpression
> unquote <@ (1+2)/3 @>;;
val it : UnquotedExpression = 

(1 + 2) / 3
3 / 3
1

val it : unit = ()

The following are functions which accept a variable environment for processing synthetic expressions with unbound variables:

  • val inline evalWith : Map<Var,obj> -> Quotations.Expr<'a> -> 'a
  • val inline evalRawWith : Map<Var,obj> -> Expr -> 'a
  • val inline reduceWith : Map<Var,obj> -> Quotations.Expr<'a> -> Quotations.Expr
  • val inline reduceFullyWith : Map<Var,obj> -> Quotations.Expr<'a> -> Quotations.Expr list

Extensions

The Swensen.Unquote.Extensions module duplicates functions in the Swensen.Unquote.Operators as instance type extensions on Quotations.Expr and Quotations.Expr<'a>. But it also includes an instance property extension on System.Type, FSharpName, which returns the F#-style signature of a type:

> typeof<int>.FSharpName;;
val it : string = "int"
> typeof<int[]>.FSharpName;;
val it : string = "int[]"
> typeof<int[,,,]>.FSharpName;;
val it : string = "int[,,,]"
> typeof<System.Collections.Generic.Dictionary<string, float>>.FSharpName;;
val it : string = "Dictionary<string, float>"
> typeof<unit -> int -> string>.FSharpName;;
val it : string = "unit -> int -> string"
> typeof<unit -> (float -> int) -> string>.FSharpName;;
val it : string = "unit -> (float -> int) -> string"
> typeof<int * float * string>.FSharpName;;
val it : string = "int * float * string"
> typeof<int * (bool * float) * string>.FSharpName;;
val it : string = "int * (bool * float) * string"
> typeof<int -> (list<(int * (int -> string))[]> * string[,,])>.FSharpName;;
val it : string = "int -> list<(int * (int -> string))[]> * string[,,]"
> typedefof<int -> int>.FSharpName;;
val it : string = "'T -> 'TResult"