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 a limited subset of F# code (e.g. generated by your own DSL).
First, we need to reference the libraries that contain the F# interactive service:
#r "FSharp.Compiler.Service.dll"
open FSharp.Compiler.Interactive.Shell
open FSharp.Compiler.Tokenization
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:
open System
open System.IO
open System.Text
// Initialize 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)
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:
/// 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.
evalExpression "42+1" // prints '43'
This can be used in a strongly typed way as follows:
/// 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:
fsiSession.EvalInteraction "printfn \"bye\""
The EvalScript
method allows to evaluate a complete .fsx script.
File.WriteAllText("sample.fsx", "let twenty = 10 + 10")
fsiSession.EvalScript "sample.fsx"
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 FSharpDiagnostic
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 situation shouldn't actually
occur for any normal input expressions, and only for primitives used in libraries.
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:
execution exception: Operation could not be completed due to earlier error
// show the errors and warnings
for w in warnings do
printfn "Warning %s at %d,%d" w.Message w.StartLine w.StartColumn
Gives:
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:
let evalExpressionTyped2<'T> text =
let res, warnings =
fsiSession.EvalExpressionNonThrowing(text)
for w in warnings do
printfn "Warning %s at %d,%d" w.Message w.StartLine 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'
By default the code passed to EvalExpression
is executed immediately. To execute in parallel, submit a computation that starts a task:
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:
task1.Result // gives the result after completion (up to 5 seconds)
task2.Result // gives the result after completion (up to 5 seconds)
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
evaluate a declaration:
fsiSession.EvalInteraction "let xxx = 1 + 1"
Now you want to typecheck the partially complete code xxx + xx
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:
checkResults.Diagnostics.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:
// get a tooltip
checkResults.GetToolTip(1, 2, "xxx + xx", [ "xxx" ], FSharpTokenTag.IDENT)
checkResults.GetSymbolUseAtLocation(1, 2, "xxx + xx", [ "xxx" ]) // symbol xxx
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 from FSharp.Compiler.Interactive.Settings.dll is used.
let fsiConfig2 =
FsiEvaluationSession.GetDefaultConfiguration(fsiSession)
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.
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
Multiple items
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp.Compiler
namespace FSharp.Compiler.Interactive
module Shell
from FSharp.Compiler.Interactive
namespace FSharp.Compiler.Tokenization
namespace System
namespace System.IO
namespace System.Text
val sbOut: StringBuilder
Multiple items
type StringBuilder =
interface ISerializable
new: unit -> unit + 5 overloads
member Append: value: bool -> StringBuilder + 25 overloads
member AppendFormat: provider: IFormatProvider * format: string * arg0: obj -> StringBuilder + 7 overloads
member AppendJoin: separator: char * [<ParamArray>] values: obj array -> StringBuilder + 5 overloads
member AppendLine: unit -> StringBuilder + 3 overloads
member Clear: unit -> StringBuilder
member CopyTo: sourceIndex: int * destination: char array * destinationIndex: int * count: int -> unit + 1 overload
member EnsureCapacity: capacity: int -> int
member Equals: span: ReadOnlySpan<char> -> bool + 1 overload
...
<summary>Represents a mutable string of characters. This class cannot be inherited.</summary>
--------------------
StringBuilder() : StringBuilder
StringBuilder(capacity: int) : StringBuilder
StringBuilder(value: string) : StringBuilder
StringBuilder(capacity: int, maxCapacity: int) : StringBuilder
StringBuilder(value: string, capacity: int) : StringBuilder
StringBuilder(value: string, startIndex: int, length: int, capacity: int) : StringBuilder
val sbErr: StringBuilder
val inStream: StringReader
Multiple items
type StringReader =
inherit TextReader
new: s: string -> unit
member Close: unit -> unit
member Peek: unit -> int
member Read: unit -> int + 2 overloads
member ReadAsync: buffer: char array * index: int * count: int -> Task<int> + 1 overload
member ReadBlock: buffer: Span<char> -> int
member ReadBlockAsync: buffer: char array * index: int * count: int -> Task<int> + 1 overload
member ReadLine: unit -> string
member ReadLineAsync: unit -> Task<string> + 1 overload
...
<summary>Implements a <see cref="T:System.IO.TextReader" /> that reads from a string.</summary>
--------------------
StringReader(s: string) : StringReader
val outStream: StringWriter
Multiple items
type StringWriter =
inherit TextWriter
new: unit -> unit + 3 overloads
member Close: unit -> unit
member FlushAsync: unit -> Task
member GetStringBuilder: unit -> StringBuilder
member ToString: unit -> string
member Write: value: char -> unit + 4 overloads
member WriteAsync: value: char -> Task + 4 overloads
member WriteLine: buffer: ReadOnlySpan<char> -> unit + 1 overload
member WriteLineAsync: value: char -> Task + 4 overloads
...
<summary>Implements a <see cref="T:System.IO.TextWriter" /> for writing information to a string. The information is stored in an underlying <see cref="T:System.Text.StringBuilder" />.</summary>
--------------------
StringWriter() : StringWriter
StringWriter(formatProvider: IFormatProvider) : StringWriter
StringWriter(sb: StringBuilder) : StringWriter
StringWriter(sb: StringBuilder, formatProvider: IFormatProvider) : StringWriter
val errStream: StringWriter
val argv: string array
val allArgs: string array
type Array =
interface ICollection
interface IEnumerable
interface IList
interface IStructuralComparable
interface IStructuralEquatable
interface ICloneable
member Clone: unit -> obj
member CopyTo: array: Array * index: int -> unit + 1 overload
member GetEnumerator: unit -> IEnumerator
member GetLength: dimension: int -> int
...
<summary>Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the base class for all arrays in the common language runtime.</summary>
val append: array1: 'T array -> array2: 'T array -> 'T array
val fsiConfig: FsiEvaluationSessionHostConfig
type FsiEvaluationSession =
interface IDisposable
member AddBoundValue: name: string * value: obj -> unit
member EvalExpression: code: string -> FsiValue option + 1 overload
member EvalExpressionNonThrowing: code: string -> Choice<FsiValue option,exn> * FSharpDiagnostic array + 1 overload
member EvalInteraction: code: string * ?cancellationToken: CancellationToken -> unit + 1 overload
member EvalInteractionNonThrowing: code: string * ?cancellationToken: CancellationToken -> Choice<FsiValue option,exn> * FSharpDiagnostic array + 1 overload
member EvalScript: filePath: string -> unit
member EvalScriptNonThrowing: filePath: string -> Choice<unit,exn> * FSharpDiagnostic array
member FormatValue: reflectionValue: obj * reflectionType: Type -> string
member GetBoundValues: unit -> FsiBoundValue list
...
<summary>
Represents an F# Interactive evaluation session.
</summary>
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
static member FsiEvaluationSession.Create: fsiConfig: FsiEvaluationSessionHostConfig * argv: string array * inReader: TextReader * outWriter: TextWriter * errorWriter: TextWriter * ?collectible: bool * ?legacyReferenceResolver: FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver -> FsiEvaluationSession
val evalExpression: text: string -> unit
Evaluate expression & return the result
val text: string
member FsiEvaluationSession.EvalExpression: code: string -> FsiValue option
member FsiEvaluationSession.EvalExpression: code: string * scriptFileName: string -> FsiValue option
union case Option.Some: Value: 'T -> Option<'T>
val value: FsiValue
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
property FsiValue.ReflectionValue: obj with get
<summary>
The value, as an object
</summary>
union case Option.None: Option<'T>
val evalExpressionTyped: text: string -> 'T
Evaluate expression & return the result, strongly typed
'T
val unbox: value: obj -> 'T
val failwith: message: string -> 'T
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> =
int
member FsiEvaluationSession.EvalInteraction: code: string * ?cancellationToken: Threading.CancellationToken -> unit
member FsiEvaluationSession.EvalInteraction: code: string * scriptFileName: string * ?cancellationToken: Threading.CancellationToken -> unit
type File =
static member AppendAllLines: path: string * contents: IEnumerable<string> -> unit + 1 overload
static member AppendAllLinesAsync: path: string * contents: IEnumerable<string> * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 1 overload
static member AppendAllText: path: string * contents: string -> unit + 1 overload
static member AppendAllTextAsync: path: string * contents: string * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 1 overload
static member AppendText: path: string -> StreamWriter
static member Copy: sourceFileName: string * destFileName: string -> unit + 1 overload
static member Create: path: string -> FileStream + 2 overloads
static member CreateSymbolicLink: path: string * pathToTarget: string -> FileSystemInfo
static member CreateText: path: string -> StreamWriter
static member Decrypt: path: string -> unit
...
<summary>Provides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of <see cref="T:System.IO.FileStream" /> objects.</summary>
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>
val warnings: FSharp.Compiler.Diagnostics.FSharpDiagnostic array
member FsiEvaluationSession.EvalScriptNonThrowing: filePath: string -> Choice<unit,exn> * FSharp.Compiler.Diagnostics.FSharpDiagnostic array
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
Multiple items
val exn: exn
--------------------
type exn = Exception
property Exception.Message: string with get
val w: FSharp.Compiler.Diagnostics.FSharpDiagnostic
property FSharp.Compiler.Diagnostics.FSharpDiagnostic.Message: string with get
<summary>
Gets the message for the diagnostic
</summary>
property FSharp.Compiler.Diagnostics.FSharpDiagnostic.StartLine: int with get
<summary>
Gets the start line for the diagnostic
</summary>
property FSharp.Compiler.Diagnostics.FSharpDiagnostic.StartColumn: int with get
<summary>
Gets the start column for the diagnostic
</summary>
val evalExpressionTyped2: text: string -> 'T
val res: Choice<FsiValue option,exn>
member FsiEvaluationSession.EvalExpressionNonThrowing: code: string -> Choice<FsiValue option,exn> * FSharp.Compiler.Diagnostics.FSharpDiagnostic array
member FsiEvaluationSession.EvalExpressionNonThrowing: code: string * scriptFileName: string -> Choice<FsiValue option,exn> * FSharp.Compiler.Diagnostics.FSharpDiagnostic array
val sprintf: format: Printf.StringFormat<'T> -> 'T
namespace System.Threading
namespace System.Threading.Tasks
val sampleLongRunningExpr: string
val task1: Task<int>
Multiple items
type Task =
interface IAsyncResult
interface IDisposable
new: action: Action -> unit + 7 overloads
member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable
member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads
member Dispose: unit -> unit
member GetAwaiter: unit -> TaskAwaiter
member RunSynchronously: unit -> unit + 1 overload
member Start: unit -> unit + 1 overload
member Wait: unit -> unit + 5 overloads
...
<summary>Represents an asynchronous operation.</summary>
--------------------
type Task<'TResult> =
inherit Task
new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads
member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult>
member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads
member GetAwaiter: unit -> TaskAwaiter<'TResult>
member WaitAsync: cancellationToken: CancellationToken -> Task<'TResult> + 2 overloads
member Result: 'TResult
static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>
--------------------
Task(action: Action) : Task
Task(action: Action, cancellationToken: Threading.CancellationToken) : Task
Task(action: Action, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj) : Task
Task(action: Action, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken) : Task
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task
--------------------
Task(``function`` : Func<'TResult>) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: Threading.CancellationToken) : Task<'TResult>
Task(``function`` : Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
val task2: Task<int>
property Task.Result: int with get
<summary>Gets the result value of this <see cref="T:System.Threading.Tasks.Task`1" />.</summary>
<exception cref="T:System.AggregateException">The task was canceled. The <see cref="P:System.AggregateException.InnerExceptions" /> collection contains a <see cref="T:System.Threading.Tasks.TaskCanceledException" /> object.
-or-
An exception was thrown during the execution of the task. The <see cref="P:System.AggregateException.InnerExceptions" /> collection contains information about the exception or exceptions.</exception>
<returns>The result value of this <see cref="T:System.Threading.Tasks.Task`1" />, which is of the same type as the task's type parameter.</returns>
val parseResults: FSharp.Compiler.CodeAnalysis.FSharpParseFileResults
val checkResults: FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults
val checkProjectResults: FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults
member FsiEvaluationSession.ParseAndCheckInteraction: code: string -> FSharp.Compiler.CodeAnalysis.FSharpParseFileResults * FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults * FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults
property FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults.Diagnostics: FSharp.Compiler.Diagnostics.FSharpDiagnostic array with get
<summary>
The errors returned by parsing a source file.
</summary>
property Array.Length: int with get
<summary>Gets the total number of elements in all the dimensions of the <see cref="T:System.Array" />.</summary>
<exception cref="T:System.OverflowException">The array is multidimensional and contains more than <see cref="F:System.Int32.MaxValue">Int32.MaxValue</see> elements.</exception>
<returns>The total number of elements in all the dimensions of the <see cref="T:System.Array" />; zero if there are no elements in the array.</returns>
member FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults.GetToolTip: line: int * colAtEndOfNames: int * lineText: string * names: string list * tokenTag: int * ?width: int -> FSharp.Compiler.EditorServices.ToolTipText
module FSharpTokenTag
from FSharp.Compiler.Tokenization
<summary>
Some of the values in the field FSharpTokenInfo.Tag
</summary>
val IDENT: int
<summary>
Indicates the token is an identifier (synonym for FSharpTokenTag.Identifier)
</summary>
member FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults.GetSymbolUseAtLocation: line: int * colAtEndOfNames: int * lineText: string * names: string list -> FSharp.Compiler.CodeAnalysis.FSharpSymbolUse option
val fsiConfig2: FsiEvaluationSessionHostConfig
val collectionTest: unit -> unit
val i: int32
val defaultArgs: string array
val session: FsiEvaluationSession
val v: FsiValue option
property Option.Value: FsiValue with get