F# Compiler Services


Interactive Service: Embedding F# Interactive

This tutorial demonstrates how to embed F# interactive in your application. F# interactive is an interactive scripting environment that compiles F# code into highly efficient IL code and executes it on the fly. The F# interactive service allows you to embed F# evaluation in your application.

NOTE: There is a number of options for embedding F# Interactive. The easiest one is to use the fsi.exe process and communicate with it using standard input and standard output. In this tutorial, we look at calling F# Interactive directly through .NET API. However, if you have no control over the input, it is a good idea to run F# interactive in a separate process. One reason is that there is no way to handle StackOverflowException and so a poorly written script can terminate the host process. Remember that while calling F# Interactive through .NET API, --shadowcopyreferences option will be ignored. For detailed discussion, please take a look at this thread. NOTE: If FsiEvaluationSession.Create fails with an error saying that FSharp.Core.dll cannot be found, add the FSharp.Core.sigdata and FSharp.Core.optdata files. More info here.

However, the F# interactive service is still useful, because you might want to wrap it in your own executable that is then executed (and communicates with the rest of your application), or if you only need to execute limited subset of F# code (e.g. generated by your own DSL).

Starting the F# interactive

First, we need to reference the libraries that contain F# interactive service:

1: 
2: 
3: 
#r "FSharp.Compiler.Service.dll"
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Interactive.Shell

To communicate with F# interactive, we need to create streams that represent input and output. We will use those later to read the output printed as a result of evaluating some F# code that prints:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
open System
open System.IO
open System.Text

// Intialize output and input streams
let sbOut = new StringBuilder()
let sbErr = new StringBuilder()
let inStream = new StringReader("")
let outStream = new StringWriter(sbOut)
let errStream = new StringWriter(sbErr)

// Build command line arguments & start FSI session
let argv = [| "C:\\fsi.exe" |]
let allArgs = Array.append argv [|"--noninteractive"|]

let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
let fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream)  

Evaluating and executing code

The F# interactive service exposes several methods that can be used for evaluation. The first is EvalExpression which evaluates an expression and returns its result. The result contains the returned value (as obj) and the statically inferred type of the value:

1: 
2: 
3: 
4: 
5: 
/// Evaluate expression & return the result
let evalExpression text =
  match fsiSession.EvalExpression(text) with
  | Some value -> printfn "%A" value.ReflectionValue
  | None -> printfn "Got no result!"

This takes a string as an argument and evaluates (i.e. executes) it as F# code.

1: 
evalExpression "42+1" // prints '43'

This can be used in a strongly typed way as follows:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
/// Evaluate expression & return the result, strongly typed
let evalExpressionTyped<'T> (text) = 
    match fsiSession.EvalExpression(text) with
    | Some value -> value.ReflectionValue |> unbox<'T>
    | None -> failwith "Got no result!"

evalExpressionTyped<int> "42+1"  // gives '43'

The EvalInteraction method can be used to evaluate side-effectful operations such as printing, declarations, or other interactions that are not valid F# expressions, but can be entered in the F# Interactive console. Such commands include #time "on" (and other directives), open System all declarations and other top-level statements. The code does not require ;; at the end. Just enter the code that you want to execute:

1: 
fsiSession.EvalInteraction "printfn \"bye\""

The EvalScript method allows to evaluate a complete .fsx script.

1: 
2: 
File.WriteAllText("sample.fsx", "let twenty = 10 + 10")
fsiSession.EvalScript "sample.fsx"

Catching errors

EvalExpression, EvalInteraction and EvalScript are awkward if the code has type checking warnings or errors, or if evaluation fails with an exception. In these cases you can use EvalExpressionNonThrowing, EvalInteractionNonThrowing and EvalScriptNonThrowing. These return a tuple of a result and an array of FSharpErrorInfo values. These represent the errors and warnings. The result part is a Choice<_,_> between an actual result and an exception.

The result part of EvalExpression and EvalExpressionNonThrowing is an optional FSharpValue. If that value is not present then it just indicates that the expression didn't have a tangible result that could be represented as a .NET object. This siutation shouldn't actually occur for any normal input expressions, and only for primitives used in libraries.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
File.WriteAllText("sample.fsx", "let twenty = 'a' + 10.0")
let result, warnings = fsiSession.EvalScriptNonThrowing "sample.fsx"

// show the result
match result with 
| Choice1Of2 () -> printfn "checked and executed ok"
| Choice2Of2 exn -> printfn "execution exception: %s" exn.Message

Gives:

1: 
execution exception: Operation could not be completed due to earlier error
1: 
2: 
3: 
// show the errors and warnings
for w in warnings do 
   printfn "Warning %s at %d,%d" w.Message w.StartLineAlternate w.StartColumn 

Gives:

1: 
2: 
Warning The type 'float' does not match the type 'char' at 1,19
Warning The type 'float' does not match the type 'char' at 1,17

For expressions:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let evalExpressionTyped2<'T> text =
   let res, warnings = fsiSession.EvalExpressionNonThrowing(text)
   for w in warnings do 
       printfn "Warning %s at %d,%d" w.Message w.StartLineAlternate w.StartColumn 
   match res with 
   | Choice1Of2 (Some value) -> value.ReflectionValue |> unbox<'T>
   | Choice1Of2 None -> failwith "null or no result"
   | Choice2Of2 (exn:exn) -> failwith (sprintf "exception %s" exn.Message)

evalExpressionTyped2<int> "42+1"  // gives '43'

Executing in parallel

By default the code passed to EvalExpression is executed immediately. To execute in parallel, submit a computation that starts a task:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
open System.Threading.Tasks

let sampleLongRunningExpr = 
    """
async { 
    // The code of what you want to run
    do System.Threading.Thread.Sleep 5000
    return 10 
}
  |> Async.StartAsTask"""

let task1 = evalExpressionTyped<Task<int>>(sampleLongRunningExpr)  
let task2 = evalExpressionTyped<Task<int>>(sampleLongRunningExpr)  

Both computations have now started. You can now fetch the results:

1: 
2: 
task1.Result // gives the result after completion (up to 5 seconds)
task2.Result // gives the result after completion (up to 5 seconds)

Type checking in the evaluation context

Let's assume you have a situation where you would like to typecheck code in the context of the F# Interactive scripting session. For example, you first evaluation a declaration:

1: 
fsiSession.EvalInteraction "let xxx = 1 + 1"

Now you want to typecheck the partially complete code xxx + xx

1: 
let parseResults, checkResults, checkProjectResults = fsiSession.ParseAndCheckInteraction("xxx + xx")

The parseResults and checkResults have types ParseFileResults and CheckFileResults explained in Editor. You can, for example, look at the type errors in the code:

1: 
checkResults.Errors.Length // 1

The code is checked with respect to the logical type context available in the F# interactive session based on the declarations executed so far.

You can also request declaration list information, tooltip text and symbol resolution:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
open Microsoft.FSharp.Compiler

// get a tooltip
checkResults.GetToolTipTextAlternate(1, 2, "xxx + xx", ["xxx"], FSharpTokenTag.IDENT) 

checkResults.GetSymbolUseAtLocation(1, 2, "xxx + xx", ["xxx"]) // symbol xxx
  

The 'fsi' object

If you want your scripting code to be able to access the 'fsi' object, you should pass in an implementation of this object explicitly. Normally the one fromm FSharp.Compiler.Interactive.Settings.dll is used.

1: 
let fsiConfig2 = FsiEvaluationSession.GetDefaultConfiguration(fsi)

Collectible code generation

Evaluating code in using FsiEvaluationSession generates a .NET dynamic assembly and uses other resources. You can make generated code collectible by passing collectible=true. However code will only be collected if there are no outstanding object references involving types, for example FsiValue objects returned by EvalExpression, and you must have disposed the FsiEvaluationSession. See also Restrictions on Collectible Assemblies.

The example below shows the creation of 200 evaluation sessions. Note that collectible=true and use session = ... are both used.

If collectible code is working correctly, overall resource usage will not increase linearly as the evaluation progresses.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let collectionTest() = 

    for i in 1 .. 200 do
        let defaultArgs = [|"fsi.exe";"--noninteractive";"--nologo";"--gui-"|]
        use inStream = new StringReader("")
        use outStream = new StringWriter()
        use errStream = new StringWriter()

        let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
        use session = FsiEvaluationSession.Create(fsiConfig, defaultArgs, inStream, outStream, errStream, collectible=true)
        
        session.EvalInteraction (sprintf "type D = { v : int }")
        let v = session.EvalExpression (sprintf "{ v = 42 * %d }" i)
        printfn "iteration %d, result = %A" i v.Value.ReflectionValue

// collectionTest()  <-- run the test like this
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Compiler
namespace Microsoft.FSharp.Compiler.SourceCodeServices
namespace Microsoft.FSharp.Compiler.Interactive
module Shell

from Microsoft.FSharp.Compiler.Interactive
namespace System
namespace System.IO
namespace System.Text
val sbOut : StringBuilder

Full name: Interactive.sbOut
Multiple items
type StringBuilder =
  new : unit -> StringBuilder + 5 overloads
  member Append : value:string -> StringBuilder + 18 overloads
  member AppendFormat : format:string * arg0:obj -> StringBuilder + 4 overloads
  member AppendLine : unit -> StringBuilder + 1 overload
  member Capacity : int with get, set
  member Chars : int -> char with get, set
  member Clear : unit -> StringBuilder
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EnsureCapacity : capacity:int -> int
  member Equals : sb:StringBuilder -> bool
  ...

Full name: System.Text.StringBuilder

--------------------
StringBuilder() : unit
StringBuilder(capacity: int) : unit
StringBuilder(value: string) : unit
StringBuilder(value: string, capacity: int) : unit
StringBuilder(capacity: int, maxCapacity: int) : unit
StringBuilder(value: string, startIndex: int, length: int, capacity: int) : unit
val sbErr : StringBuilder

Full name: Interactive.sbErr
val inStream : StringReader

Full name: Interactive.inStream
Multiple items
type StringReader =
  inherit TextReader
  new : s:string -> StringReader
  member Close : unit -> unit
  member Peek : unit -> int
  member Read : unit -> int + 1 overload
  member ReadLine : unit -> string
  member ReadToEnd : unit -> string

Full name: System.IO.StringReader

--------------------
StringReader(s: string) : unit
val outStream : StringWriter

Full name: Interactive.outStream
Multiple items
type StringWriter =
  inherit TextWriter
  new : unit -> StringWriter + 3 overloads
  member Close : unit -> unit
  member Encoding : Encoding
  member GetStringBuilder : unit -> StringBuilder
  member ToString : unit -> string
  member Write : value:char -> unit + 2 overloads

Full name: System.IO.StringWriter

--------------------
StringWriter() : unit
StringWriter(formatProvider: IFormatProvider) : unit
StringWriter(sb: StringBuilder) : unit
StringWriter(sb: StringBuilder, formatProvider: IFormatProvider) : unit
val errStream : StringWriter

Full name: Interactive.errStream
val argv : string []

Full name: Interactive.argv
val allArgs : string []

Full name: Interactive.allArgs
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val append : array1:'T [] -> array2:'T [] -> 'T []

Full name: Microsoft.FSharp.Collections.Array.append
val fsiConfig : FsiEvaluationSessionHostConfig

Full name: Interactive.fsiConfig
type FsiEvaluationSession =
  interface IDisposable
  member EvalExpression : code:string -> FsiValue option
  member EvalExpressionNonThrowing : code:string -> Choice<FsiValue option,exn> * FSharpErrorInfo []
  member EvalInteraction : code:string -> unit
  member EvalInteractionNonThrowing : code:string -> Choice<unit,exn> * FSharpErrorInfo []
  member EvalScript : filePath:string -> unit
  member EvalScriptNonThrowing : filePath:string -> Choice<unit,exn> * FSharpErrorInfo []
  member GetCompletions : longIdent:string -> seq<string>
  member Interrupt : unit -> unit
  member ParseAndCheckInteraction : code:string -> FSharpParseFileResults * FSharpCheckFileResults * FSharpCheckProjectResults
  ...

Full name: Microsoft.FSharp.Compiler.Interactive.Shell.FsiEvaluationSession
static member FsiEvaluationSession.GetDefaultConfiguration : unit -> FsiEvaluationSessionHostConfig
static member FsiEvaluationSession.GetDefaultConfiguration : fsiObj:obj -> FsiEvaluationSessionHostConfig
static member FsiEvaluationSession.GetDefaultConfiguration : fsiObj:obj * useFsiAuxLib:bool -> FsiEvaluationSessionHostConfig
val fsiSession : FsiEvaluationSession

Full name: Interactive.fsiSession
static member FsiEvaluationSession.Create : fsiConfig:FsiEvaluationSessionHostConfig * argv:string [] * inReader:TextReader * outWriter:TextWriter * errorWriter:TextWriter * ?collectible:bool * ?msbuildEnabled:bool -> FsiEvaluationSession
val evalExpression : text:string -> unit

Full name: Interactive.evalExpression


 Evaluate expression & return the result
val text : string
member FsiEvaluationSession.EvalExpression : code:string -> FsiValue option
union case Option.Some: Value: 'T -> Option<'T>
val value : FsiValue
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
property FsiValue.ReflectionValue: obj
union case Option.None: Option<'T>
val evalExpressionTyped : text:string -> 'T

Full name: Interactive.evalExpressionTyped


 Evaluate expression & return the result, strongly typed
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
member FsiEvaluationSession.EvalInteraction : code:string -> unit
type File =
  static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendText : path:string -> StreamWriter
  static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
  static member Create : path:string -> FileStream + 3 overloads
  static member CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  static member Encrypt : path:string -> unit
  static member Exists : path:string -> bool
  ...

Full name: System.IO.File
File.WriteAllText(path: string, contents: string) : unit
File.WriteAllText(path: string, contents: string, encoding: Encoding) : unit
member FsiEvaluationSession.EvalScript : filePath:string -> unit
val result : Choice<unit,exn>

Full name: Interactive.result
val warnings : Compiler.FSharpErrorInfo []

Full name: Interactive.warnings
member FsiEvaluationSession.EvalScriptNonThrowing : filePath:string -> Choice<unit,exn> * Compiler.FSharpErrorInfo []
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
Multiple items
val exn : exn

--------------------
type exn = Exception

Full name: Microsoft.FSharp.Core.exn
property Exception.Message: string
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val w : Compiler.FSharpErrorInfo
property Compiler.FSharpErrorInfo.Message: string
property Compiler.FSharpErrorInfo.StartLineAlternate: int
property Compiler.FSharpErrorInfo.StartColumn: int
val evalExpressionTyped2 : text:string -> 'T

Full name: Interactive.evalExpressionTyped2
val res : Choice<FsiValue option,exn>
val warnings : Compiler.FSharpErrorInfo []
member FsiEvaluationSession.EvalExpressionNonThrowing : code:string -> Choice<FsiValue option,exn> * Compiler.FSharpErrorInfo []
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
namespace System.Threading
namespace System.Threading.Tasks
val sampleLongRunningExpr : string

Full name: Interactive.sampleLongRunningExpr
val task1 : Task<int>

Full name: Interactive.task1
Multiple items
type Task =
  new : action:Action -> Task + 7 overloads
  member AsyncState : obj
  member ContinueWith : continuationAction:Action<Task> -> Task + 9 overloads
  member CreationOptions : TaskCreationOptions
  member Dispose : unit -> unit
  member Exception : AggregateException
  member Id : int
  member IsCanceled : bool
  member IsCompleted : bool
  member IsFaulted : bool
  ...

Full name: System.Threading.Tasks.Task

--------------------
type Task<'TResult> =
  inherit Task
  new : function:Func<'TResult> -> Task<'TResult> + 7 overloads
  member ContinueWith : continuationAction:Action<Task<'TResult>> -> Task + 9 overloads
  member Result : 'TResult with get, set
  static member Factory : TaskFactory<'TResult>

Full name: System.Threading.Tasks.Task<_>

--------------------
Task(action: Action) : unit
Task(action: Action, cancellationToken: Threading.CancellationToken) : unit
Task(action: Action, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj) : unit
Task(action: Action, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken) : unit
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit

--------------------
Task(function: Func<'TResult>) : unit
Task(function: Func<'TResult>, cancellationToken: Threading.CancellationToken) : unit
Task(function: Func<'TResult>, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj) : unit
Task(function: Func<'TResult>, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken) : unit
Task(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
val task2 : Task<int>

Full name: Interactive.task2
property Task.Result: int
val parseResults : FSharpParseFileResults

Full name: Interactive.parseResults
val checkResults : FSharpCheckFileResults

Full name: Interactive.checkResults
val checkProjectResults : FSharpCheckProjectResults

Full name: Interactive.checkProjectResults
member FsiEvaluationSession.ParseAndCheckInteraction : code:string -> FSharpParseFileResults * FSharpCheckFileResults * FSharpCheckProjectResults
property FSharpCheckFileResults.Errors: Compiler.FSharpErrorInfo []
property Array.Length: int
member FSharpCheckFileResults.GetToolTipTextAlternate : line:int * colAtEndOfNames:int * lineText:string * names:string list * tokenTag:int -> Async<FSharpToolTipText>
module FSharpTokenTag

from Microsoft.FSharp.Compiler.SourceCodeServices
val IDENT : int

Full name: Microsoft.FSharp.Compiler.SourceCodeServices.FSharpTokenTag.IDENT
member FSharpCheckFileResults.GetSymbolUseAtLocation : line:int * colAtEndOfNames:int * lineText:string * names:string list -> Async<FSharpSymbolUse option>
val fsiConfig2 : FsiEvaluationSessionHostConfig

Full name: Interactive.fsiConfig2
val fsi : Interactive.InteractiveSession

Full name: Microsoft.FSharp.Compiler.Interactive.Settings.fsi
val collectionTest : unit -> unit

Full name: Interactive.collectionTest
val i : int32
val defaultArgs : string []
val inStream : StringReader
val outStream : StringWriter
val errStream : StringWriter
val fsiConfig : FsiEvaluationSessionHostConfig
val session : FsiEvaluationSession
val v : FsiValue option
property Option.Value: FsiValue
Fork me on GitHub