F# Documentation
F# Documentation
F# documentation
What is F#
Get started
Install F#
F# in Visual Studio
F# in Visual Studio Code
F# with the .NET Core CLI
F# in Visual Studio for Mac
Tour of F#
Tutorials
Introduction to Functional Programming
First-class functions
Asynchronous and Concurrent Programming
Asynchronous Programming
Type Providers
Create a Type Provider
Type provider Security
Troubleshooting Type Providers
What's new in F#
F# 5.0
F# 4.7
F# 4.6
F# 4.5
F# language reference
Keyword Reference
Symbol and Operator Reference
Arithmetic Operators
Boolean Operators
Bitwise Operators
Nullable Operators
Functions
let Bindings
do Bindings
Lambda Expressions: the fun keyword
Recursive Functions: the rec keyword
Entry Point
External Functions
Inline Functions
Values
Null Values
Literals
F# Types
Type Inference
Basic Types
Unit Type
Strings
Interpolated strings
Tuples
F# Collection Types
Lists
Arrays
Sequences
Slices
Options
Value Options
Results
Generics
Automatic Generalization
Constraints
Statically Resolved Type Parameters
Records
Anonymous Records
Copy and Update Record Expressions
Discriminated Unions
Enumerations
Type Abbreviations
Classes
Structures
Nullable value types
Inheritance
Interfaces
Abstract Classes
Members
let Bindings in Classes
do Bindings in Classes
Properties
Indexed Properties
Methods
Constructors
Events
Explicit Fields: The `val` Keyword
Type Extensions
Parameters and Arguments
Operator Overloading
Flexible Types
Delegates
Object Expressions
Casting and Conversions
Access Control
Conditional Expressions: if...then...else
Match Expressions
Pattern Matching
Active Patterns
Loops: for...to Expression
Loops: for...in Expression
Loops: while...do Expression
Assertions
Exception Handling
Exception Types
The try...with Expression
The try...finally Expression
The raise Function
The failwith Function
The invalidArg Function
Attributes
Resource Management: the use Keyword
Namespaces
Modules
Import Declarations: The open Keyword
Signature Files
Units of Measure
Plain Text Formatting
XML Documentation
Lazy Expressions
Computation Expressions
Asynchronous Workflows
Query Expressions
Code Quotations
Fixed keyword
Byrefs
Reference Cells
nameof
Compiler Directives
Compiler Options
F# Interactive Options
Source Line, File, and Path Identifiers
Caller Information
Verbose Syntax
Compiler errors and warnings
F# Tools
F# Interactive
F# style guide
F# code formatting guidelines
F# coding conventions
F# component design guidelines
Use F# on Azure
Get started with Azure Blob Storage using F#
Get started with Azure File Storage using F#
Get started with Azure Queue Storage using F#
Get started with Azure Table Storage using F#
Package Management for F# Azure Dependencies
What is F#
3/6/2021 • 2 minutes to read • Edit Online
F# is a functional programming language that makes it easy to write correct and maintainable code.
F# programming primarily involves defining types and functions that are type-inferred and generalized
automatically. This allows your focus to remain on the problem domain and manipulating its data, rather than
the details of programming.
[<EntryPoint>]
let main args =
// Defines a list of names
let names = [ "Don"; "Julia"; "Xi" ]
type FailedWithdrawal =
{ Amount: decimal
Balance: decimal
IsOverdraft: bool }
F# records and discriminated unions are non-null, immutable, and comparable by default, making them very
easy to use.
// Returns a WithdrawalResult
let withdrawMoney amount = // Implementation elided
F# functions are also first-class, meaning they can be passed as parameters and returned from other functions.
module Set =
let isEmpty (set: Set<'T>) = set.IsEmpty
Rather than writing code that is object-oriented, in F#, you will often write code that treats objects as another
data type for functions to manipulate. Features such as generic interfaces, object expressions, and judicious use
of members are common in larger F# programs.
Next steps
To learn more about a larger set of F# features, check out the F# Tour.
Get Started with F#
11/2/2020 • 2 minutes to read • Edit Online
Windows Get started with Visual Get started with Visual Get started with the .NET
Studio Studio Code Core CLI
macOS Get started with VS for Mac Get started with Visual Get started with the .NET
Studio Code Core CLI
Linux N/A Get started with Visual Get started with the .NET
Studio Code Core CLI
In general, there is no specific that is better than the rest. We recommend trying all ways to use F# on your
machine to see what you like the best!
F# and the Visual F# tooling are supported in the Visual Studio integrated development environment (IDE).
To begin, ensure that you have Visual Studio installed with F# support.
module HelloSquare
let square x = x * x
[<EntryPoint>]
let main argv =
printfn "%d squared is: %d!" 12 (square 12)
0 // Return an integer exit code
The previous code sample defines a function called square that takes an input named x and multiplies it by
itself. Because F# uses Type inference, the type of x doesn't need to be specified. The F# compiler understands
the types where multiplication is valid and assigns a type to x based on how square is called. If you hover
over square , you should see the following:
This is what is known as the function's type signature. It can be read like this: "Square is a function that takes an
integer named x and produces an integer". The compiler gave square the int type for now; this is because
multiplication is not generic across all types but rather a closed set of types. The F# compiler will adjust the type
signature if you call square with a different input type, such as a float .
Another function, main , is defined, which is decorated with the EntryPoint attribute. This attribute tells the F#
compiler that program execution should start there. It follows the same convention as other C-style
programming languages, where command-line arguments can be passed to this function, and an integer code is
returned (typically 0 ).
It is in the entry point function, main , that you call the square function with an argument of 12 . The F#
compiler then assigns the type of square to be int -> int (that is, a function that takes an int and produces
an int ). The call to printfn is a formatted printing function that uses a format string and prints the result (and
a new line). The format string, similar to C-style programming languages, has parameters ( %d ) that correspond
to the arguments that are passed to it, in this case, 12 and (square 12) .
Congratulations! You've created your first F# project in Visual Studio, written an F# function that calculates and
prints a value, and run the project to see the results.
Next steps
If you haven't already, check out the Tour of F#, which covers some of the core features of the F# language. It
provides an overview of some of the capabilities of F# and ample code samples that you can copy into Visual
Studio and run.
Tour of F#
See also
F# language reference
Type inference
Symbol and operator reference
Get Started with F# in Visual Studio Code
3/6/2021 • 7 minutes to read • Edit Online
You can write F# in Visual Studio Code with the Ionide plugin to get a great cross-platform, lightweight
Integrated Development Environment (IDE) experience with IntelliSense and code refactorings. Visit Ionide.io to
learn more about the plugin.
To begin, ensure that you have F# and the Ionide plugin correctly installed.
Once it completes, change directory to the project and open Visual Studio Code:
cd FirstIonideProject
code .
After the project loads on Visual Studio Code, you should see the F# Solution Explorer pane on the left-hand side
of your window open. This means Ionide has successfully loaded the project you just created. You can write code
in the editor before this point in time, but once this happens, everything has finished loading.
Configure F# interactive
First, ensure that .NET Core scripting is your default scripting environment:
1. Open the Visual Studio Code settings (Code > Preferences > Settings ).
2. Search for the term F# Script .
3. Click the checkbox that says FSharp: use SDK scripts .
This is currently necessary due to some legacy behaviors in .NET Framework-based scripting that don't work
with .NET Core scripting, and Ionide is currently striving for that backwards compatibility. In the future, .NET
Core scripting will become the default.
Write your first script
Once you've configured Visual Studio Code to use .NET Core scripting, navigate to the Explorer view in Visual
Studio Code and create a new file. Name it MyFirstScript.fsx.
Now add the following code to it:
let toPigLatin (word: string) =
let isVowel (c: char) =
match c with
| 'a' | 'e' | 'i' |'o' |'u'
| 'A' | 'E' | 'I' | 'O' | 'U' -> true
|_ -> false
This function converts a word to a form of Pig Latin. The next step is to evaluate it using F# Interactive (FSI).
Highlight the entire function (it should be 11 lines long). Once it's highlighted, hold the Alt key and hit Enter .
You'll notice a terminal window pop up on the bottom of the screen, and it should look similar to this:
toPigLatin "banana";;
Now, let's try with a vowel as the first letter. Enter the following:
toPigLatin "apple";;
The function appears to be working as expected. Congratulations, you just wrote your first F# function in Visual
Studio Code and evaluated it with FSI!
NOTE
As you may have noticed, the lines in FSI are terminated with ;; . This is because FSI allows you to enter multiple lines.
The ;; at the end lets FSI know when the code is finished.
This states that toPigLatin is a function that takes in a string as input (called word ), and returns another
string . This is known as the type signature of the function, a fundamental piece of F# that's key to
understanding F# code. You'll also notice this if you hover over the function in Visual Studio Code.
In the body of the function, you'll notice two distinct parts:
1. An inner function, called isVowel , that determines if a given character ( c ) is a vowel by checking if it
matches one of the provided patterns via Pattern Matching:
2. An if..then..else expression that checks if the first character is a vowel, and constructs a return value
out of the input characters based on if the first character was a vowel or not:
module PigLatin =
let toPigLatin (word: string) =
let isVowel (c: char) =
match c with
| 'a' | 'e' | 'i' |'o' |'u'
| 'A' | 'E' | 'I' | 'O' | 'U' -> true
|_ -> false
This module should be above the main function and below the open System declaration. Order of declarations
matters in F#, so you'll need to define the function before you call it in a file.
Now, in the main function, call your Pig Latin generator function on the arguments:
[<EntryPoint>]
let main argv =
for name in argv do
let newName = PigLatin.toPigLatin name
printfn "%s in Pig Latin is: %s" name newName
Now you can run your console app from the command line:
And you'll see that it outputs the same result as your script file, but this time as a running program!
Troubleshooting Ionide
Here are a few ways you can troubleshoot certain problems that you might run into:
1. To get the code editing features of Ionide, your F# files need to be saved to disk and inside of a folder that is
open in the Visual Studio Code workspace.
2. If you've made changes to your system or installed Ionide prerequisites with Visual Studio Code open, restart
Visual Studio Code.
3. If you have invalid characters in your project directories, Ionide might not work. Rename your project
directories if this is the case.
4. If none of the Ionide commands are working, check your Visual Studio Code Key Bindings to see if you're
overriding them by accident.
5. If Ionide is broken on your machine and none of the above has fixed your problem, try removing the
ionide-fsharp directory on your machine and reinstall the plugin suite.
6. If a project failed to load (the F# Solution Explorer will show this), right-click on that project and click See
details to get more diagnostic info.
Ionide is an open source project built and maintained by members of the F# community. Please report issues
and feel free to contribute at the ionide-vscode-fsharp GitHub repository.
You can also ask for further help from the Ionide developers and F# community in the Ionide Gitter channel.
Next steps
To learn more about F# and the features of the language, check out Tour of F#.
Get started with F# with the .NET Core CLI
3/6/2021 • 2 minutes to read • Edit Online
This article covers how you can get started with F# on any operating system (Windows, macOS, or Linux) with
the .NET Core CLI. It goes through building a multi-project solution with a class library that is called by a console
application.
Prerequisites
To begin, you must install the latest .NET Core SDK.
This article assumes that you know how to use a command line and have a preferred text editor. If you don't
already use it, Visual Studio Code is a great option as a text editor for F#.
The following directory structure is produced after running the previous command:
FSNetCore
├── FSNetCore.sln
The following directory structure is produced after running the previous command:
└── FSNetCore
├── FSNetCore.sln
└── src
└── Library
├── Library.fs
└── Library.fsproj
open Newtonsoft.Json
Add the Library project to the FSNetCore solution using the dotnet sln add command:
Run dotnet build to build the project. Unresolved dependencies will be restored when building.
Write a console application that consumes the class library
Use the dotnet new command, create a console application in the src folder named App.
The following directory structure is produced after running the previous command:
└── FSNetCore
├── FSNetCore.sln
└── src
├── App
│ ├── App.fsproj
│ ├── Program.fs
└── Library
├── Library.fs
└── Library.fsproj
Replace the contents of the Program.fs file with the following code:
open System
open Library
[<EntryPoint>]
let main argv =
printfn "Nice command-line arguments! Here's what JSON.NET has to say about them:"
Add the App project to the FSNetCore solution using the dotnet sln add command:
dotnet sln add src/App/App.fsproj
Restore the NuGet dependencies, dotnet restore and run dotnet build to build the project.
Change directory to the src/App console project and run the project passing Hello World as arguments:
cd src/App
dotnet run Hello World
Nice command-line arguments! Here's what JSON.NET has to say about them:
Next steps
Next, check out the Tour of F# to learn more about different F# features.
Get started with F# in Visual Studio for Mac
3/6/2021 • 5 minutes to read • Edit Online
F# and the Visual F# tooling are supported in the Visual Studio for Mac IDE. Ensure that you have Visual Studio
for Mac installed.
module HelloSquare
let square x = x * x
[<EntryPoint>]
let main argv =
printfn "%d squared is: %d!" 12 (square 12)
0 // Return an integer exit code
In the previous code sample, a function square has been defined which takes an input named x and multiplies
it by itself. Because F# uses Type Inference, the type of x doesn't need to be specified. The F# compiler
understands the types where multiplication is valid, and will assign a type to x based on how square is called.
If you hover over square , you should see the following:
This is what is known as the function's type signature. It can be read like this: "Square is a function which takes
an integer named x and produces an integer". Note that the compiler gave square the int type for now - this
is because multiplication is not generic across all types, but rather is generic across a closed set of types. The F#
compiler picked int at this point, but it will adjust the type signature if you call square with a different input
type, such as a float .
Another function, main , is defined, which is decorated with the EntryPoint attribute to tell the F# compiler that
program execution should start there. It follows the same convention as other C-style programming languages,
where command-line arguments can be passed to this function, and an integer code is returned (typically 0 ).
It is in this function that we call the square function with an argument of 12 . The F# compiler then assigns the
type of square to be int -> int (that is, a function which takes an int and produces an int ). The call to
printfn is a formatted printing function which uses a format string, similar to C-style programming languages,
parameters which correspond to those specified in the format string, and then prints the result and a new line.
12 squared is 144!
Congratulations! You've created your first F# project in Visual Studio for Mac, written an F# function printed the
results of calling that function, and run the project to see some results.
Using F# Interactive
One of the best features of the Visual F# tooling in Visual Studio for Mac is the F# Interactive Window. It allows
you to send code over to a process where you can call that code and see the result interactively.
To begin using it, highlight the square function defined in your code. Next, click on Edit from the top level
menu. Next select Send selection to F# Interactive . This executes the code in the F# Interactive Window.
Alternatively, you can right click on the selection and choose Send selection to F# Interactive . You should see
the F# Interactive Window appear with the following in it:
>
>
This shows the same function signature for the square function, which you saw earlier when you hovered over
the function. Because square is now defined in the F# Interactive process, you can call it with different values:
This executes the function, binds the result to a new name it , and displays the type and value of it . Note that
you must terminate each line with ;; . This is how F# Interactive knows when your function call is finished. You
can also define new functions in F# Interactive:
You can also use the pipe-forward operator to pipeline the value into the two functions:
Next steps
If you haven't already, check out the Tour of F#, which covers some of the core features of the F# language. It will
give you an overview of some of the capabilities of F#, and provide ample code samples that you can copy into
Visual Studio for Mac and run. There are also some great external resources you can use, showcased in the F#
Guide.
See also
F# guide
Tour of F#
F# language reference
Type inference
Symbol and operator reference
Tour of F#
5/18/2021 • 31 minutes to read • Edit Online
The best way to learn about F# is to read and write F# code. This article will act as a tour through some of the
key features of the F# language and give you some code snippets that you can execute on your machine. To
learn about setting up a development environment, check out Getting Started.
There are two primary concepts in F#: functions and types. This tour emphasizes features of the language that
fall into these two concepts.
/// You use 'let' to define a function. This one accepts an integer argument and returns an integer.
/// Parentheses are optional for function arguments, except for when you use an explicit type
annotation.
let sampleFunction1 x = x*x + 3
/// Apply the function, naming the function return result using 'let'.
/// The variable type is inferred from the function return type.
let result1 = sampleFunction1 4573
// This line uses '%d' to print the result as an integer. This is type-safe.
// If 'result1' were not of type 'int', then the line would fail to compile.
printfn $"The result of squaring the integer 4573 and adding 3 is %d{result1}"
/// When needed, annotate the type of a parameter name using '(argument:type)'. Parentheses are
required.
let sampleFunction2 (x:int) = 2*x*x - x/5 + 3
// This line uses '%f' to print the result as a float. As with '%d' above, this is type-safe.
printfn $"The result of applying the 3rd sample function to (6.5 + 4.5) is %f{result3}"
let bindings are also how you bind a value to a name, similar to a variable in other languages. let bindings
are immutable by default, which means that once a value or function is bound to a name, it cannot be changed
in-place. This is in contrast to variables in other languages, which are mutable , meaning their values can be
changed at any point in time. If you require a mutable binding, you can use let mutable ... syntax.
module Immutability =
/// A mutable binding. This is required to be able to mutate the value of 'otherNumber'.
let mutable otherNumber = 2
module IntegersAndNumbers =
/// This computed a new number by some arithmetic. Numeric types are converted using
/// functions 'int', 'double' and so on.
let sampleInteger2 = (sampleInteger/4 + 5 - 7) * 4 + int sampleDouble
/// This is a list of all tuples containing all the numbers from 0 to 99 and their squares.
let sampleTableOfSquares = [ for i in 0 .. 99 -> (i, i*i) ]
// The next line prints a list that includes tuples, using an interpolated string.
printfn $"The table of squares from 0 to 99 is:\n{sampleTableOfSquares}"
Here's what Boolean values and performing basic conditional logic looks like:
module Booleans =
/// Substrings use the indexer notation. This line extracts the first 7 characters as a substring.
/// Note that like many languages, Strings are zero-indexed in F#.
let substring = helloWorld.[0..6]
printfn $"{substring}"
Tuples
Tuples are a big deal in F#. They are a grouping of unnamed but ordered values that can be treated as values
themselves. Think of them as values which are aggregated from other values. They have many uses, such as
conveniently returning multiple values from a function, or grouping values for some ad-hoc convenience.
module Tuples =
You can also create struct tuples. These also interoperate fully with C#7/Visual Basic 15 tuples, which are also
struct tuples:
/// Tuples are normally objects, but they can also be represented as structs.
///
/// These interoperate completely with structs in C# and Visual Basic.NET; however,
/// struct tuples are not implicitly convertible with object tuples (often called reference tuples).
///
/// The second line below will fail to compile because of this. Uncomment it to see what happens.
let sampleStructTuple = struct (1, 2)
//let thisWillNotCompile: (int*int) = struct (1, 2)
printfn $"Struct Tuple: {sampleStructTuple}\nReference tuple made from the Struct Tuple: {(sampleStructTuple
|> convertFromStructTuple)}"
It's important to note that because struct tuples are value types, they cannot be implicitly converted to
reference tuples, or vice versa. You must explicitly convert between a reference and struct tuple.
module PipelinesAndComposition =
/// You can shorten 'squareOddValuesAndAddOnePipeline' by moving the second `List.map` call
/// into the first, using a Lambda Function.
///
/// Note that pipelines are also being used inside the lambda function. F# pipe operators
/// can be used for single values as well. This makes them very powerful for processing data.
let squareOddValuesAndAddOneShorterPipeline values =
values
|> List.filter isOdd
|> List.map(fun x -> x |> square |> addOne)
/// Lastly, you can eliminate the need to explicitly take 'values' in as a parameter by using '>>'
/// to compose the two core operations: filtering out even numbers, then squaring and adding one.
/// Likewise, the 'fun x -> ...' bit of the lambda expression is also not needed, because 'x' is simply
/// being defined in that scope so that it can be passed to a functional pipeline. Thus, '>>' can be
used
/// there as well.
///
/// The result of 'squareOddValuesAndAddOneComposition' is itself another function which takes a
/// list of integers as its input. If you execute 'squareOddValuesAndAddOneComposition' with a list
/// of integers, you'll notice that it produces the same results as previous functions.
///
/// This is using what is known as function composition. This is possible because functions in F#
/// use Partial Application and the input and output types of each data processing operation match
/// the signatures of the functions we're using.
let squareOddValuesAndAddOneComposition =
List.filter isOdd >> List.map (square >> addOne)
The previous sample made use of many features of F#, including list processing functions, first-class functions,
and partial application. Although these are advanced concepts, it should be clear how easily functions can be
used to process data when building pipelines.
/// This is a list with 3 elements. ';' is used to separate elements on the same line.
let list2 = [ 1; 2; 3 ]
/// You can also separate elements by placing them on their own lines.
let list3 = [
1
2
3
]
/// Computations can include conditionals. This is a list containing the tuples
/// which are the coordinates of the black squares on a chess board.
let blackSquares =
[ for i in 0 .. 7 do
for j in 0 .. 7 do
if (i+j) % 2 = 1 then
yield (i, j) ]
/// Lists can be transformed using 'List.map' and other functional programming combinators.
/// This definition produces a new list by squaring the numbers in numberList, using the pipeline
/// operator to pass an argument to List.map.
let squares =
numberList
|> List.map (fun x -> x*x)
/// There are many other list combinations. The following computes the sum of the squares of the
/// numbers divisible by 3.
let sumOfSquares =
numberList
|> List.filter (fun x -> x % 3 = 0)
|> List.sumBy (fun x -> x * x)
printfn $"The sum of the squares of numbers up to 1000 that are divisible by 3 is: %d{sumOfSquares}"
Arrays are fixed-size, mutable collections of elements of the same type. They support fast random access of
elements, and are faster than F# lists because they are just contiguous blocks of memory.
module Arrays =
/// This is The empty array. Note that the syntax is similar to that of Lists, but uses `[| ... |]`
instead.
let array1 = [| |]
/// Arrays are specified using the same range of constructs as lists.
let array2 = [| "hello"; "world"; "and"; "hello"; "world"; "again" |]
/// This is an array containing only the words "hello" and "world".
let array4 =
[| for word in array2 do
if word.Contains("l") then
yield word |]
/// This is an array initialized by index and containing the even numbers from 0 to 2000.
let evenNumbers = Array.init 1001 (fun n -> n * 2)
/// You can loop over arrays and lists using 'for' loops.
for word in array4 do
printfn $"word: {word}"
// You can modify the contents of an array element by using the left arrow assignment operator.
//
// To learn more about this operator, see: https://docs.microsoft.com/dotnet/fsharp/language-
reference/values/index#mutable-variables
array2.[1] <- "WORLD!"
/// You can transform arrays using 'Array.map' and other functional programming operations.
/// The following calculates the sum of the lengths of the words that start with 'h'.
///
/// Note that in this case, similar to Lists, array2 is not mutated by Array.filter.
let sumOfLengthsOfWords =
array2
|> Array.filter (fun x -> x.StartsWith "h")
|> Array.sumBy (fun x -> x.Length)
printfn $"The sum of the lengths of the words in Array 2 is: %d{sumOfLengthsOfWords}"
Sequences are a logical series of elements, all of the same type. These are a more general type than Lists and
Arrays, capable of being your "view" into any logical series of elements. They also stand out because they can be
lazy , which means that elements can be computed only when they are needed.
module Sequences =
/// This example shows the first 100 elements of the random walk.
let first100ValuesOfRandomWalk =
randomWalk 5.0
|> Seq.truncate 100
|> Seq.toList
Recursive Functions
Processing collections or sequences of elements is typically done with recursion in F#. Although F# has support
for loops and imperative programming, recursion is preferred because it is easier to guarantee correctness.
NOTE
The following example makes use of the pattern matching via the match expression. This fundamental construct is
covered later in this article.
module RecursiveFunctions =
/// This example shows a recursive function that computes the factorial of an
/// integer. It uses 'let rec' to define a recursive function.
let rec factorial n =
if n = 0 then 1 else n * factorial (n-1)
printfn $"The Greatest Common Factor of 300 and 620 is %d{greatestCommonFactor 300 620}"
/// This example computes the sum of a list of integers using recursion.
///
/// '::' is used to split a list into the head and tail of the list,
/// the head being the first element and the tail being the rest of the list.
let rec sumList xs =
match xs with
| [] -> 0
| y::ys -> y + sumList ys
/// This makes 'sumList' tail recursive, using a helper function with a result accumulator.
let rec private sumListTailRecHelper accumulator xs =
match xs with
| [] -> accumulator
| y::ys -> sumListTailRecHelper (accumulator+y) ys
/// This invokes the tail recursive helper function, providing '0' as a seed accumulator.
/// An approach like this is common in F#.
let sumListTailRecursive xs = sumListTailRecHelper 0 xs
F# also has full support for Tail Call Optimization, which is a way to optimize recursive calls so that they are just
as fast as a loop construct.
/// You can also do this on the same line with ';' separators.
let contactOnSameLine = { Name = "Alf"; Phone = "(206) 555-0157"; Verified = false }
/// This example shows how to use "copy-and-update" on record values. It creates
/// a new record value that is a copy of contact1, but has different values for
/// the 'Phone' and 'Verified' fields.
///
/// To learn more, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/copy-and-update-
record-expressions
let contact2 =
{ contact1 with
Phone = "(206) 555-0112"
Verified = true }
/// This example shows how to write a function that processes a record value.
/// It converts a 'ContactCard' object to a string.
let showContactCard (c: ContactCard) =
c.Name + " Phone: " + c.Phone + (if not c.Verified then " (unverified)" else "")
let contactAlternate =
{ Name = "Alf"
Phone = "(206) 555-0157"
Verified = false
Address = "111 Alf Street" }
You can also represent Records as structs. This is done with the [<Struct>] attribute:
[<Struct>]
type ContactCardStruct =
{ Name : string
Phone : string
Verified : bool }
Discriminated Unions (DUs) are values that could be a number of named forms or cases. Data stored in the type
can be one of several distinct values.
module DiscriminatedUnions =
/// A Discriminated Union can also be used to represent the rank of a playing card.
type Rank =
/// Represents the rank of cards 2 .. 10
| Value of int
| Ace
| King
| Queen
| Jack
/// This computes a list representing all the cards in the deck.
let fullDeck =
[ for suit in [ Hearts; Diamonds; Clubs; Spades] do
for rank in Rank.GetAllRanks() do
yield { Suit=suit; Rank=rank } ]
You can also use DUs as Single-Case Discriminated Unions, to help with domain modeling over primitive types.
Often, strings and other primitive types are used to represent something, and are thus given a particular
meaning. However, using only the primitive representation of the data can result in mistakenly assigning an
incorrect value! Representing each type of information as a distinct single-case union can enforce correctness in
this scenario.
// Single-case DUs are often used for domain modeling. This can buy you extra type safety
// over primitive types such as strings and ints.
//
// Single-case DUs cannot be implicitly converted to or from the type they wrap.
// For example, a function which takes in an Address cannot accept a string as that input,
// or vice versa.
type Address = Address of string
type Name = Name of string
type SSN = SSN of int
/// When you need the value, you can unwrap the underlying value with a simple function.
let unwrapAddress (Address a) = a
let unwrapName (Name n) = n
let unwrapSSN (SSN s) = s
As the above sample demonstrates, to get the underlying value in a single-case Discriminated Union, you must
explicitly unwrap it.
Additionally, DUs also support recursive definitions, allowing you to easily represent trees and inherently
recursive data. For example, here's how you can represent a Binary Search Tree with exists and insert
functions.
Because DUs allow you to represent the recursive structure of the tree in the data type, operating on this
recursive structure is straightforward and guarantees correctness. It is also supported in pattern matching, as
shown below.
Additionally, you can represent DUs as struct s with the [<Struct>] attribute:
[<Struct>]
type Shape =
| Circle of radius: float
| Square of side: float
| Triangle of height: float * width: float
However, there are two key things to keep in mind when doing so:
1. A struct DU cannot be recursively defined.
2. A struct DU must have unique names for each of its cases.
Failure to follow the above will result in a compilation error.
Pattern Matching
Pattern Matching is the F# language feature that enables correctness for operating on F# types. In the above
samples, you probably noticed quite a bit of match x with ... syntax. This construct allows the compiler, which
can understand the "shape" of data types, to force you to account for all possible cases when using a data type
through what is known as Exhaustive Pattern Matching. This is incredibly powerful for correctness, and can be
cleverly used to "lift" what would normally be a run-time concern into a compile-time concern.
module PatternMatching =
Something you may have noticed is the use of the _ pattern. This is known as the Wildcard Pattern, which is a
way of saying "I don't care what something is". Although convenient, you can accidentally bypass Exhaustive
Pattern Matching and no longer benefit from compile-time enforcements if you aren't careful in using _ . It is
best used when you don't care about certain pieces of a decomposed type when pattern matching, or the final
clause when you have enumerated all meaningful cases in a pattern matching expression.
In the following example, the _ case is used when a parse operation fails.
/// Find all managers/executives named "Dave" who do not have any reports.
/// This uses the 'function' shorthand to as a lambda expression.
let findDaveWithOpenPosition(emps : List<Employee>) =
emps
|> List.filter(function
| Manager({First = "Dave"}, []) -> true // [] matches an empty list.
| Executive({First = "Dave"}, [], _) -> true
| _ -> false) // '_' is a wildcard pattern that matches anything.
// This handles the "or else" case.
/// You can also use the shorthand function construct for pattern matching,
/// which is useful when you're writing functions which make use of Partial Application.
let private parseHelper (f: string -> bool * 'T) = f >> function
| (true, item) -> Some item
| (false, _) -> None
// Define some more functions which parse with the helper function.
let parseInt = parseHelper Int32.TryParse
let parseDouble = parseHelper Double.TryParse
let parseTimeSpan = parseHelper TimeSpan.TryParse
Active Patterns are another powerful construct to use with pattern matching. They allow you to partition input
data into custom forms, decomposing them at the pattern match call site. They can also be parameterized, thus
allowing to define the partition as a function. Expanding the previous example to support Active Patterns looks
something like this:
/// Pattern Matching via 'function' keyword and Active Patterns often looks like this.
let printParseResult = function
| Int x -> printfn $"%d{x}"
| Double x -> printfn $"%f{x}"
| Date d -> printfn $"%O{d}"
| TimeSpan t -> printfn $"%O{t}"
| _ -> printfn "Nothing was parse-able!"
Optional Types
One special case of Discriminated Union types is the Option Type, which is so useful that it's a part of the F# core
library.
The Option Type is a type that represents one of two cases: a value, or nothing at all. It is used in any scenario
where a value may or may not result from a particular operation. This then forces you to account for both cases,
making it a compile-time concern rather than a runtime concern. These are often used in APIs where null is
used to represent "nothing" instead, thus eliminating the need to worry about NullReferenceException in many
circumstances.
module OptionValues =
/// First, define a zip code defined via Single-case Discriminated Union.
type ZipCode = ZipCode of string
/// Next, define an interface type that represents an object to compute the shipping zone for the
customer's zip code,
/// given implementations for the 'getState' and 'getShippingZone' abstract methods.
type IShippingCalculator =
abstract GetState : ZipCode -> string option
abstract GetShippingZone : string -> int
/// Next, calculate a shipping zone for a customer using a calculator instance.
/// This uses combinators in the Option module to allow a functional pipeline for
/// transforming data with Optionals.
let CustomerShippingZone (calculator: IShippingCalculator, customer: Customer) =
customer.ZipCode
|> Option.bind calculator.GetState
|> Option.map calculator.GetShippingZone
Units of Measure
One unique feature of F#'s type system is the ability to provide context for numeric literals through Units of
Measure.
Units of Measure allow you to associate a numeric type to a unit, such as Meters, and have functions perform
work on units rather than numeric literals. This enables the compiler to verify that the types of numeric literals
passed in make sense under a certain context, thus eliminating runtime errors associated with that kind of work.
module UnitsOfMeasure =
// Values using Units of Measure can be used just like the primitive numeric type for things like
printing.
printfn $"After a %f{sampleValue1} race I would walk %f{sampleValue2} miles which would be
%f{sampleValue3} meters"
The F# Core library defines many SI unit types and unit conversions. To learn more, check out the
FSharp.Data.UnitSystems.SI.UnitSymbols Namespace.
module DefiningClasses =
/// This internal field stores the length of the vector, computed when the
/// object is constructed
let length = sqrt (dx*dx + dy*dy)
member this.DY = dy
/// Get a new scaled vector object, without modifying the original object.
let vector2 = vector1.Scale(10.0)
module DefiningGenericClasses =
/// An 'int' instance of the state tracker class. Note that the type parameter is inferred.
let tracker = StateTracker 10
// Add a state
tracker.UpdateState 17
To implement an Interface, you can use either interface ... with syntax or an Object Expression.
module ImplementingInterfaces =
Next Steps
Now that you've seen some of the primary features of the language, you should be ready to write your first F#
programs! Check out Getting Started to learn how to set up your development environment and write some
code.
The next steps for learning more can be whatever you like, but we recommend Introduction to Functional
Programming in F# to get comfortable with core Functional Programming concepts. These will be essential in
building robust programs in F#.
Also, check out the F# Language Reference to see a comprehensive collection of conceptual content on F#.
Introduction to Functional Programming in F#
3/6/2021 • 8 minutes to read • Edit Online
Functional programming is a style of programming that emphasizes the use of functions and immutable data.
Typed functional programming is when functional programming is combined with static types, such as with F#.
In general, the following concepts are emphasized in functional programming:
Functions as the primary constructs you use
Expressions instead of statements
Immutable values over variables
Declarative programming over imperative programming
Throughout this series, you'll explore concepts and patterns in functional programming using F#. Along the way,
you'll learn some F# too.
Terminology
Functional programming, like other programming paradigms, comes with a vocabulary that you will eventually
need to learn. Here are some common terms you'll see all of the time:
Function - A function is a construct that will produce an output when given an input. More formally, it maps
an item from one set to another set. This formalism is lifted into the concrete in many ways, especially when
using functions that operate on collections of data. It is the most basic (and important) concept in functional
programming.
Expression - An expression is a construct in code that produces a value. In F#, this value must be bound or
explicitly ignored. An expression can be trivially replaced by a function call.
Purity - Purity is a property of a function such that its return value is always the same for the same
arguments, and that its evaluation has no side effects. A pure function depends entirely on its arguments.
Referential Transparency - Referential Transparency is a property of expressions such that they can be
replaced with their output without affecting a program's behavior.
Immutability - Immutability means that a value cannot be changed in-place. This is in contrast with
variables, which can change in place.
Examples
The following examples demonstrate these core concepts.
Functions
The most common and fundamental construct in functional programming is the function. Here's a simple
function that adds 1 to an integer:
let addOne x = x + 1
The signature can be read as, " addOne accepts an int named x and will produce an int ". More formally,
addOne is mapping a value from the set of integers to the set of integers. The -> token signifies this mapping.
In F#, you can usually look at the function signature to get a sense for what it does.
So, why is the signature important? In typed functional programming, the implementation of a function is often
less important than the actual type signature! The fact that addOne adds the value 1 to an integer is interesting
at runtime, but when you are constructing a program, the fact that it accepts and returns an int is what
informs how you will actually use this function. Furthermore, once you use this function correctly (with respect
to its type signature), diagnosing any problems can be done only within the body of the addOne function. This is
the impetus behind typed functional programming.
Expressions
Expressions are constructs that evaluate to a value. In contrast to statements, which perform an action,
expressions can be thought of performing an action that gives back a value. Expressions are almost always used
in functional programming instead of statements.
Consider the previous function, addOne . The body of addOne is an expression:
It is the result of this expression that defines the result type of the addOne function. For example, the expression
that makes up this function could be changed to be a different type, such as a string :
Since any type in F# can have ToString() called on it, the type of x has been made generic (called Automatic
Generalization), and the resultant type is a string .
Expressions are not just the bodies of functions. You can have expressions that produce a value you use
elsewhere. A common one is if :
result
The if expression produces a value called result . Note that you could omit result entirely, making the if
expression the body of the addOneIfOdd function. The key thing to remember about expressions is that they
produce a value.
There is a special type, unit , that is used when there is nothing to return. For example, consider this simple
function:
let printString (str: string) =
printfn $"String is: {str}"
The unit type indicates that there is no actual value being returned. This is useful when you have a routine that
must "do work" despite having no value to return as a result of that work.
This is in sharp contrast to imperative programming, where the equivalent if construct is a statement, and
producing values is often done with mutating variables. For example, in C#, the code might be written like this:
if (IsOdd(input))
{
result = input + 1;
}
return result;
}
It's worth noting that C# and other C-style languages do support the ternary expression, which allows for
expression-based conditional programming.
In functional programming, it is rare to mutate values with statements. Although some functional languages
support statements and mutation, it is not common to use these concepts in functional programming.
Pure functions
As previously mentioned, pure functions are functions that:
Always evaluate to the same value for the same input.
Have no side effects.
It is helpful to think of mathematical functions in this context. In mathematics, functions depend only on their
arguments and do not have any side effects. In the mathematical function f(x) = x + 1 , the value of f(x)
depends only on the value of x . Pure functions in functional programming are the same way.
When writing a pure function, the function must depend only on its arguments and not perform any action that
results in a side effect.
Here is an example of a non-pure function because it depends on global, mutable state:
The addOneToValue function is clearly impure, because value could be changed at any time to have a different
value than 1. This pattern of depending on a global value is to be avoided in functional programming.
Here is another example of a non-pure function, because it performs a side effect:
let addOneToValue x =
printfn $"x is %d{x}"
x + 1
Although this function does not depend on a global value, it writes the value of x to the output of the program.
Although there is nothing inherently wrong with doing this, it does mean that the function is not pure. If another
part of your program depends on something external to the program, such as the output buffer, then calling this
function can affect that other part of your program.
Removing the printfn statement makes the function pure:
let addOneToValue x = x + 1
Although this function is not inherently better than the previous version with the printfn statement, it does
guarantee that all this function does is return a value. Calling this function any number of times produces the
same result: it just produces a value. The predictability given by purity is something many functional
programmers strive for.
Immutability
Finally, one of the most fundamental concepts of typed functional programming is immutability. In F#, all values
are immutable by default. That means they cannot be mutated in-place unless you explicitly mark them as
mutable.
In practice, working with immutable values means that you change your approach to programming from, "I
need to change something", to "I need to produce a new value".
For example, adding 1 to a value means producing a new value, not mutating the existing one:
let value = 1
let secondValue = value + 1
In F#, the following code does not mutate the value function; instead, it performs an equality check:
let value = 1
value = value + 1 // Produces a 'bool' value!
Some functional programming languages do not support mutation at all. In F#, it is supported, but it is not the
default behavior for values.
This concept extends even further to data structures. In functional programming, immutable data structures
such as sets (and many more) have a different implementation than you might initially expect. Conceptually,
something like adding an item to a set does not change the set, it produces a new set with the added value.
Under the covers, this is often accomplished by a different data structure that allows for efficiently tracking a
value so that the appropriate representation of the data can be given as a result.
This style of working with values and data structures is critical, as it forces you to treat any operation that
modifies something as if it creates a new version of that thing. This allows for things like equality and
comparability to be consistent in your programs.
Next steps
The next section will thoroughly cover functions, exploring different ways you can use them in functional
programming.
First-class functions explores functions deeply, showing how you can use them in various contexts.
Further reading
The Thinking Functionally series is another great resource to learn about functional programming with F#. It
covers fundamentals of functional programming in a pragmatic and easy-to-read way, using F# features to
illustrate the concepts.
First-class functions
11/2/2020 • 22 minutes to read • Edit Online
A defining characteristic of functional programming languages is the elevation of functions to first-class status.
You should be able to do with a function whatever you can do with values of the other built-in types, and be able
to do so with a comparable degree of effort.
Typical measures of first-class status include the following:
Can you bind functions to identifiers? That is, can you give them names?
Can you store functions in data structures, such as in a list?
Can you pass a function as an argument in a function call?
Can you return a function from a function call?
The last two measures define what are known as higher-order operations or higher-order functions. Higher-
order functions accept functions as arguments and return functions as the value of function calls. These
operations support such mainstays of functional programming as mapping functions and composition of
functions.
You can name a function just as easily. The following example defines a function named squareIt by binding
the identifier squareIt to the lambda expression fun n -> n * n . Function squareIt has one parameter, n ,
and it returns the square of that parameter.
F# provides the following more concise syntax to achieve the same result with less typing.
let squareIt2 n = n * n
The examples that follow mostly use the first style, let <function-name> = <lambda-expression> , to emphasize the
similarities between the declaration of functions and the declaration of other types of values. However, all the
named functions can also be written with the concise syntax. Some of the examples are written in both ways.
// Tuples.
To verify that a function name stored in a tuple does in fact evaluate to a function, the following example uses
the fst and snd operators to extract the first and second elements from tuple funAndArgTuple . The first
element in the tuple is squareIt and the second element is num . Identifier num is bound in a previous example
to integer 10, a valid argument for the squareIt function. The second expression applies the first element in the
tuple to the second element in the tuple: squareIt num .
// You can pull a function out of a tuple and apply it. Both squareIt and num
// were defined previously.
let funAndArgTuple = (squareIt, num)
Similarly, just as identifier num and integer 10 can be used interchangeably, so can identifier squareIt and
lambda expression fun n -> n * n .
// Make a tuple of values instead of identifiers.
let funAndArgTuple2 = ((fun n -> n * n), 10)
// String.
// Function repeatString concatenates a string with itself.
let repeatString = fun s -> s + s
If functions have first-class status, you must be able to pass them as arguments in the same way. Remember that
this is the first characteristic of higher-order functions.
In the following example, function applyIt has two parameters, op and arg . If you send in a function that has
one parameter for op and an appropriate argument for the function to arg , the function returns the result of
applying op to arg . In the following example, both the function argument and the integer argument are sent
in the same way, by using their names.
// Send squareIt for the function, op, and num for the argument you want to
// apply squareIt to, arg. Both squareIt and num are defined in previous
// examples. The result returned and displayed is 100.
System.Console.WriteLine(applyIt squareIt num)
// The following expression shows the concise syntax for the previous function
// definition.
let applyIt2 op arg = op arg
// The following line also displays 100.
System.Console.WriteLine(applyIt2 squareIt num)
The ability to send a function as an argument to another function underlies common abstractions in functional
programming languages, such as map or filter operations. A map operation, for example, is a higher-order
function that captures the computation shared by functions that step through a list, do something to each
element, and then return a list of the results. You might want to increment each element in a list of integers, or to
square each element, or to change each element in a list of strings to uppercase. The error-prone part of the
computation is the recursive process that steps through the list and builds a list of the results to return. That part
is captured in the mapping function. All you have to write for a particular application is the function that you
want to apply to each list element individually (adding, squaring, changing case). That function is sent as an
argument to the mapping function, just as squareIt is sent to applyIt in the previous example.
F# provides map methods for most collection types, including lists, arrays, and sequences. The following
examples use lists. The syntax is List.map <the function> <the list> .
// Or you can define the action to apply to each list element inline.
// For example, no function that tests for even integers has been defined,
// so the following expression defines the appropriate function inline.
// The function returns true if n is even; otherwise it returns false.
let evenOrNot = List.map (fun n -> n % 2 = 0) integerList
// The following line displays [false; true; false; true; false; true; false]
printfn "%A" evenOrNot
The following function call, declared inline, returns a Boolean value. The value displayed is True .
The ability to return a function as the value of a function call is the second characteristic of higher-order
functions. In the following example, checkFor is defined to be a function that takes one argument, item , and
returns a new function as its value. The returned function takes a list as its argument, lst , and searches for
item in lst . If item is present, the function returns true . If item is not present, the function returns false .
As in the previous section, the following code uses a provided list function, List.exists, to search the list.
let checkFor item =
let functionToReturn = fun lst ->
List.exists (fun a -> a = item) lst
functionToReturn
The following code uses checkFor to create a new function that takes one argument, a list, and searches for 7 in
the list.
The following example uses the first-class status of functions in F# to declare a function, compose , that returns a
composition of two function arguments.
NOTE
For an even shorter version, see the following section, "Curried Functions."
The following code sends two functions as arguments to compose , both of which take a single argument of the
same type. The return value is a new function that is a composition of the two function arguments.
// Functions squareIt and doubleIt were defined in a previous example.
let doubleAndSquare = compose squareIt doubleIt
// The following expression doubles 3, squares 6, and returns and
// displays 36.
System.Console.WriteLine(doubleAndSquare 3)
NOTE
F# provides two operators, << and , that compose functions. For example,
>>
let squareAndDouble2 = doubleIt << squareIt is equivalent to
let squareAndDouble = compose doubleIt squareIt in the previous example.
The following example of returning a function as the value of a function call creates a simple guessing game. To
create a game, call makeGame with the value that you want someone to guess sent in for target . The return
value from function makeGame is a function that takes one argument (the guess) and reports whether the guess
is correct.
The following code calls makeGame , sending the value 7 for target . Identifier playGame is bound to the
returned lambda expression. Therefore, playGame is a function that takes as its one argument a value for guess .
// Output:
// Wrong. Try again.
// Wrong. Try again.
// You win!
// Output:
// Wrong. Try again.
// Wrong. Try again.
// Wrong. Try again.
// You win!
Curried Functions
Many of the examples in the previous section can be written more concisely by taking advantage of the implicit
currying in F# function declarations. Currying is a process that transforms a function that has more than one
parameter into a series of embedded functions, each of which has a single parameter. In F#, functions that have
more than one parameter are inherently curried. For example, compose from the previous section can be written
as shown in the following concise style, with three parameters.
However, the result is a function of one parameter that returns a function of one parameter that in turn returns
another function of one parameter, as shown in compose4curried .
let compose4curried =
fun op1 ->
fun op2 ->
fun n -> op1 (op2 n)
You can access this function in several ways. Each of the following examples returns and displays 18. You can
replace compose4 with compose4curried in any of the examples.
To verify that the function still works as it did before, try the original test cases again.
NOTE
You can restrict currying by enclosing parameters in tuples. For more information, see "Parameter Patterns" in Parameters
and Arguments.
The following example uses implicit currying to write a shorter version of makeGame . The details of how
makeGame constructs and returns the game function are less explicit in this format, but you can verify by using
the original test cases that the result is the same.
let makeGame2 target guess =
if guess = target then
System.Console.WriteLine("You win!")
else
System.Console.WriteLine("Wrong. Try again.")
For more information about currying, see "Partial Application of Arguments" in Functions.
// This example uses the names of the function argument and the integer
// argument. Identifier num is defined in a previous example.
//let num = 10
System.Console.WriteLine(applyIt isNegative num)
// This example substitutes the value that num is bound to for num, and the
// value that isNegative is bound to for isNegative.
System.Console.WriteLine(applyIt (fun n -> n < 0) 10)
To take it one step further, substitute the value that applyIt is bound to for applyIt .
Example
Description
The following code contains all the examples in this topic.
Code
let squareIt2 n = n * n
// Lists.
// Tuples.
// You can pull a function out of a tuple and apply it. Both squareIt and num
// were defined previously.
let funAndArgTuple = (squareIt, num)
// String.
// Function repeatString concatenates a string with itself.
let repeatString = fun s -> s + s
// Send squareIt for the function, op, and num for the argument you want to
// apply squareIt to, arg. Both squareIt and num are defined in previous
// apply squareIt to, arg. Both squareIt and num are defined in previous
// examples. The result returned and displayed is 100.
System.Console.WriteLine(applyIt squareIt num)
// The following expression shows the concise syntax for the previous function
// definition.
let applyIt2 op arg = op arg
// The following line also displays 100.
System.Console.WriteLine(applyIt2 squareIt num)
// Or you can define the action to apply to each list element inline.
// For example, no function that tests for even integers has been defined,
// so the following expression defines the appropriate function inline.
// The function returns true if n is even; otherwise it returns false.
let evenOrNot = List.map (fun n -> n % 2 = 0) integerList
// The following line displays [false; true; false; true; false; true; false]
printfn "%A" evenOrNot
// Output:
// Wrong. Try again.
// Wrong. Try again.
// You win!
// Output:
// Wrong. Try again.
// Wrong. Try again.
// Wrong. Try again.
// You win!
// ** CURRIED FUNCTIONS **
let compose4curried =
fun op1 ->
fun op2 ->
fun n -> op1 (op2 n)
// This example uses the names of the function argument and the integer
// argument. Identifier num is defined in a previous example.
//let num = 10
System.Console.WriteLine(applyIt isNegative num)
// This example substitutes the value that num is bound to for num, and the
// value that isNegative is bound to for isNegative.
System.Console.WriteLine(applyIt (fun n -> n < 0) 10)
See also
Lists
Tuples
Functions
let Bindings
Lambda Expressions: The fun Keyword
Async programming in F#
3/6/2021 • 12 minutes to read • Edit Online
Asynchronous programming is a mechanism that is essential to modern applications for diverse reasons. There
are two primary use cases that most developers will encounter:
Presenting a server process that can service a significant number of concurrent incoming requests, while
minimizing the system resources occupied while request processing awaits inputs from systems or services
external to that process
Maintaining a responsive UI or main thread while concurrently progressing background work
Although background work often does involve the utilization of multiple threads, it's important to consider the
concepts of asynchrony and multi-threading separately. In fact, they are separate concerns, and one does not
imply the other. This article describes the separate concepts in more detail.
Asynchrony defined
The previous point - that asynchrony is independent of the utilization of multiple threads - is worth explaining a
bit further. There are three concepts that are sometimes related, but strictly independent of one another:
Concurrency; when multiple computations execute in overlapping time periods.
Parallelism; when multiple computations or several parts of a single computation run at exactly the same
time.
Asynchrony; when one or more computations can execute separately from the main program flow.
All three are orthogonal concepts, but can be easily conflated, especially when they are used together. For
example, you may need to execute multiple asynchronous computations in parallel. This relationship does not
mean that parallelism or asynchrony imply one another.
If you consider the etymology of the word "asynchronous", there are two pieces involved:
"a", meaning "not".
"synchronous", meaning "at the same time".
When you put these two terms together, you'll see that "asynchronous" means "not at the same time". That's it!
There is no implication of concurrency or parallelism in this definition. This is also true in practice.
In practical terms, asynchronous computations in F# are scheduled to execute independently of the main
program flow. This independent execution doesn't imply concurrency or parallelism, nor does it imply that a
computation always happens in the background. In fact, asynchronous computations can even execute
synchronously, depending on the nature of the computation and the environment the computation is executing
in.
The main takeaway you should have is that asynchronous computations are independent of the main program
flow. Although there are few guarantees about when or how an asynchronous computation executes, there are
some approaches to orchestrating and scheduling them. The rest of this article explores core concepts for F#
asynchrony and how to use the types, functions, and expressions built into F#.
Core concepts
In F#, asynchronous programming is centered around three core concepts:
The Async<'T> type, which represents a composable asynchronous computation.
The Async module functions, which let you schedule asynchronous work, compose asynchronous
computations, and transform asynchronous results.
The async { } computation expression, which provides a convenient syntax for building and controlling
asynchronous computations.
You can see these three concepts in the following example:
open System
open System.IO
[<EntryPoint>]
let main argv =
printTotalFileBytes "path-to-file.txt"
|> Async.RunSynchronously
In the example, the printTotalFileBytes function is of type string -> Async<unit> . Calling the function does
not actually execute the asynchronous computation. Instead, it returns an Async<unit> that acts as a
specification of the work that is to execute asynchronously. It calls Async.AwaitTask in its body, which converts
the result of ReadAllBytesAsync to an appropriate type.
Another important line is the call to Async.RunSynchronously . This is one of the Async module starting functions
that you'll need to call if you want to actually execute an F# asynchronous computation.
This is a fundamental difference with the C#/Visual Basic style of async programming. In F#, asynchronous
computations can be thought of as Cold tasks . They must be explicitly started to actually execute. This has
some advantages, as it allows you to combine and sequence asynchronous work much more easily than in C#
or Visual Basic.
[<EntryPoint>]
let main argv =
argv
|> Seq.map printTotalFileBytes
|> Async.Parallel
|> Async.Ignore
|> Async.RunSynchronously
As you can see, the main function has quite a few more elements. Conceptually, it does the following:
1. Transform the command-line arguments into a sequence of Async<unit> computations with Seq.map .
2. Create an Async<'T[]> that schedules and runs the printTotalFileBytes computations in parallel when it
runs.
3. Create an Async<unit> that will run the parallel computation and ignore its result (which is a unit[] ).
4. Explicitly run the overall composed computation with Async.RunSynchronously , blocking until it completes.
When this program runs, printTotalFileBytes runs in parallel for each command-line argument. Because
asynchronous computations execute independently of program flow, there is no defined order in which they
print their information and finish executing. The computations will be scheduled in parallel, but their order of
execution is not guaranteed.
[<EntryPoint>]
let main argv =
argv
|> Seq.map printTotalFileBytes
|> Async.Sequential
|> Async.Ignore
|> Async.RunSynchronously
|> ignore
This will schedule printTotalFileBytes to execute in the order of the elements of argv rather than scheduling
them in parallel. Because each successive operation will not be scheduled until after the preceding computation
has finished executing, the computations are sequenced such that there is no overlap in their execution.
Important Async module functions
When you write async code in F#, you'll usually interact with a framework that handles scheduling of
computations for you. However, this is not always the case, so it is good to understand the various functions that
can be used to schedule asynchronous work.
Because F# asynchronous computations are a specification of work rather than a representation of work that is
already executing, they must be explicitly started with a starting function. There are many Async starting
methods that are helpful in different contexts. The following section describes some of the more common
starting functions.
Async.StartChild
Starts a child computation within an asynchronous computation. This allows multiple asynchronous
computations to be executed concurrently. The child computation shares a cancellation token with the parent
computation. If the parent computation is canceled, the child computation is also canceled.
Signature:
When to use:
When you want to execute multiple asynchronous computations concurrently rather than one at a time, but
not have them scheduled in parallel.
When you wish to tie the lifetime of a child computation to that of a parent computation.
What to watch out for:
Starting multiple computations with Async.StartChild isn't the same as scheduling them in parallel. If you
wish to schedule computations in parallel, use Async.Parallel .
Canceling a parent computation will trigger cancellation of all child computations it started.
Async.StartImmediate
Runs an asynchronous computation, starting immediately on the current operating system thread. This is
helpful if you need to update something on the calling thread during the computation. For example, if an
asynchronous computation must update a UI (such as updating a progress bar), then Async.StartImmediate
should be used.
Signature:
When to use:
When you need to update something on the calling thread in the middle of an asynchronous computation.
What to watch out for:
Code in the asynchronous computation will run on whatever thread one happens to be scheduled on. This
can be problematic if that thread is in some way sensitive, such as a UI thread. In such cases,
Async.StartImmediate is likely inappropriate to use.
Async.StartAsTask
Executes a computation in the thread pool. Returns a Task<TResult> that will be completed on the
corresponding state once the computation terminates (produces the result, throws exception, or gets canceled).
If no cancellation token is provided, then the default cancellation token is used.
Signature:
When to use:
When you need to call into a .NET API that yields a Task<TResult> to represent the result of an asynchronous
computation.
What to watch out for:
This call will allocate an additional Task object, which can increase overhead if it is used often.
Async.Parallel
Schedules a sequence of asynchronous computations to be executed in parallel, yielding an array of results in
the order they were supplied. The degree of parallelism can be optionally tuned/throttled by specifying the
maxDegreeOfParallelism parameter.
Signature:
Signature:
When to use:
When you are consuming a .NET API that returns a Task<TResult> within an F# asynchronous computation.
What to watch out for:
Exceptions are wrapped in AggregateException following the convention of the Task Parallel Library; this
behavior is different from how F# async generally surfaces exceptions.
Async.Catch
Creates an asynchronous computation that executes a given Async<'T> , returning an Async<Choice<'T, exn>> . If
the given Async<'T> completes successfully, then a Choice1Of2 is returned with the resultant value. If an
exception is thrown before it completes, then a Choice2of2 is returned with the raised exception. If it is used on
an asynchronous computation that is itself composed of many computations, and one of those computations
throws an exception, the encompassing computation will be stopped entirely.
Signature:
When to use:
When you are performing asynchronous work that may fail with an exception and you want to handle that
exception in the caller.
What to watch out for:
When using combined or sequenced asynchronous computations, the encompassing computation will fully
stop if one of its "internal" computations throws an exception.
Async.Ignore
Creates an asynchronous computation that runs the given computation but drops its result.
Signature:
When to use:
When you have an asynchronous computation whose result is not needed. This is analogous to the ignore
function for non-asynchronous code.
What to watch out for:
If you must use Async.Ignore because you wish to use Async.Start or another function that requires
Async<unit> , consider if discarding the result is okay. Avoid discarding results just to fit a type signature.
Async.RunSynchronously
Runs an asynchronous computation and awaits its result on the calling thread. Propagates an exception should
the computation yield one. This call is blocking.
Signature:
Working with .NET async libraries and codebases that use Task<TResult> (that is, async computations that have
return values) is straightforward and has built-in support with F#.
You can use the Async.AwaitTask function to await a .NET asynchronous computation:
To work with APIs that use Task (that is, .NET async computations that do not return a value), you may need to
add an additional function that will convert an Async<'T> to a Task:
module Async =
// Async<unit> -> Task
let startTaskFromAsyncUnit (comp: Async<unit>) =
Async.StartAsTask comp :> Task
There is already an Async.AwaitTask that accepts a Task as input. With this and the previously defined
startTaskFromAsyncUnit function, you can start and await Task types from an F# async computation.
Relationship to multi-threading
Although threading is mentioned throughout this article, there are two important things to remember:
1. There is no affinity between an asynchronous computation and a thread, unless explicitly started on the
current thread.
2. Asynchronous programming in F# is not an abstraction for multi-threading.
For example, a computation may actually run on its caller's thread, depending on the nature of the work. A
computation could also "jump" between threads, borrowing them for a small amount of time to do useful work
in between periods of "waiting" (such as when a network call is in transit).
Although F# provides some abilities to start an asynchronous computation on the current thread (or explicitly
not on the current thread), asynchrony generally is not associated with a particular threading strategy.
See also
The F# Asynchronous Programming Model
Jet.com's F# Async Guide
F# for fun and profit's Asynchronous Programming guide
Async in C# and F#: Asynchronous gotchas in C#
Type Providers
3/6/2021 • 2 minutes to read • Edit Online
An F# type provider is a component that provides types, properties, and methods for use in your program. Type
Providers generate what are known as Provided Types , which are generated by the F# compiler and are based
on an external data source.
For example, an F# Type Provider for SQL can generate types representing tables and columns in a relational
database. In fact, this is what the SQLProvider Type Provider does.
Provided Types depend on input parameters to a Type Provider. Such input can be a sample data source (such as
a JSON schema file), a URL pointing directly to an external service, or a connection string to a data source. A
Type Provider can also ensure that groups of types are only expanded on demand; that is, they are expanded if
the types are actually referenced by your program. This allows for the direct, on-demand integration of large-
scale information spaces such as online data markets in a strongly typed way.
See also
Tutorial: Create a Type Provider
F# Language Reference
Tutorial: Create a Type Provider
3/6/2021 • 36 minutes to read • Edit Online
The type provider mechanism in F# is a significant part of its support for information rich programming. This
tutorial explains how to create your own type providers by walking you through the development of several
simple type providers to illustrate the basic concepts. For more information about the type provider mechanism
in F#, see Type Providers.
The F# ecosystem contains a range of type providers for commonly used Internet and enterprise data services.
For example:
FSharp.Data includes type providers for JSON, XML, CSV and HTML document formats.
SQLProvider provides strongly typed access to SQL databases through a object mapping and F# LINQ
queries against these data sources.
FSharp.Data.SqlClient has a set of type providers for compile-time checked embedding of T-SQL in F#.
FSharp.Data.TypeProviders is an older set of type providers for use only with .NET Framework
programming for accessing SQL, Entity Framework, OData and WSDL data services.
Where necessary, you can create custom type providers, or you can reference type providers that others have
created. For example, your organization could have a data service that provides a large and growing number of
named data sets, each with its own stable data schema. You can create a type provider that reads the schemas
and presents the current data sets to the programmer in a strongly typed way.
namespace Samples.HelloWorldTypeProvider
type Type1 =
/// This is a static property.
static member StaticProperty : string
type Type2 =
…
…
type Type100 =
…
Note that the set of types and members provided is statically known. This example doesn't leverage the ability of
providers to provide types that depend on a schema. The implementation of the type provider is outlined in the
following code, and the details are covered in later sections of this topic.
WARNING
There may be differences between this code and the online samples.
namespace Samples.FSharp.HelloWorldTypeProvider
open System
open System.Reflection
open ProviderImplementation.ProvidedTypes
open FSharp.Core.CompilerServices
open FSharp.Quotations
// This type defines the type provider. When compiled to a DLL, it can be added
// as a reference to an F# command-line compilation, script, or project.
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =
[<assembly:TypeProviderAssembly>]
do()
To use this provider, open a separate instance of Visual Studio, create an F# script, and then add a reference to
the provider from your script by using #r as the following code shows:
#r @".\bin\Debug\Samples.HelloWorldTypeProvider.dll"
obj1.InstanceProperty
obj2.InstanceProperty
Then look for the types under the Samples.HelloWorldTypeProvider namespace that the type provider generated.
Before you recompile the provider, make sure that you have closed all instances of Visual Studio and F#
Interactive that are using the provider DLL. Otherwise, a build error will occur because the output DLL will be
locked.
To debug this provider by using print statements, make a script that exposes a problem with the provider, and
then use the following code:
To debug this provider by using Visual Studio, open the Developer Command Prompt for Visual Studio with
administrative credentials, and run the following command:
As an alternative, open Visual Studio, open the Debug menu, choose Debug/Attach to process… , and attach to
another devenv process where you’re editing your script. By using this method, you can more easily target
particular logic in the type provider by interactively typing expressions into the second instance (with full
IntelliSense and other features).
You can disable Just My Code debugging to better identify errors in generated code. For information about how
to enable or disable this feature, see Navigating through Code with the Debugger. Also, you can also set first-
chance exception catching by opening the Debug menu and then choosing Exceptions or by choosing the
Ctrl+Alt+E keys to open the Exceptions dialog box. In that dialog box, under
Common Language Runtime Exceptions , select the Thrown check box.
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =
This type must be public, and you must mark it with the TypeProvider attribute so that the compiler will
recognize the type provider when a separate F# project references the assembly that contains the type. The
config parameter is optional, and, if present, contains contextual configuration information for the type provider
instance that the F# compiler creates.
Next, you implement the ITypeProvider interface. In this case, you use the TypeProviderForNamespaces type from
the ProvidedTypes API as a base type. This helper type can provide a finite collection of eagerly provided
namespaces, each of which directly contains a finite number of fixed, eagerly provided types. In this context, the
provider eagerly generates types even if they aren't needed or used.
inherit TypeProviderForNamespaces(config)
Next, define local private values that specify the namespace for the provided types, and find the type provider
assembly itself. This assembly is used later as the logical parent type of the erased types that are provided.
Next, create a function to provide each of the types Type1…Type100. This function is explained in more detail
later in this topic.
Finally, add an assembly attribute that indicates that you are creating a type provider DLL:
[<assembly:TypeProviderAssembly>]
do()
This step explains the implementation of this function. First, create the provided type (for example, Type1, when
n = 1, or Type57, when n = 57).
// This is the provided type. It is an erased provided type and, in compiled code,
// will appear as type 'obj'.
let t = ProvidedTypeDefinition(thisAssembly, namespaceName,
"Type" + string n,
baseType = Some typeof<obj>)
Getting this property will always evaluate to the string "Hello!". The GetterCode for the property uses an F#
quotation, which represents the code that the host compiler generates for getting the property. For more
information about quotations, see Code Quotations (F#).
Add XML documentation to the property.
Now attach the provided property to the provided type. You must attach a provided member to one and only
one type. Otherwise, the member will never be accessible.
t.AddMember staticProp
The InvokeCode for the constructor returns an F# quotation, which represents the code that the host compiler
generates when the constructor is called. For example, you can use the following constructor:
new Type10()
An instance of the provided type will be created with underlying data "The object data". The quoted code
includes a conversion to obj because that type is the erasure of this provided type (as you specified when you
declared the provided type).
Add XML documentation to the constructor, and add the provided constructor to the provided type:
t.AddMember ctor
let ctor2 =
ProvidedConstructor(parameters = [ ProvidedParameter("data",typeof<string>) ],
invokeCode = (fun args -> <@@ (%%(args.[0]) : string) :> obj @@>))
The InvokeCode for the constructor again returns an F# quotation, which represents the code that the host
compiler generated for a call to the method. For example, you can use the following constructor:
new Type10("ten")
An instance of the provided type is created with underlying data "ten". You may have already noticed that the
InvokeCode function returns a quotation. The input to this function is a list of expressions, one per constructor
parameter. In this case, an expression that represents the single parameter value is available in args.[0] . The
code for a call to the constructor coerces the return value to the erased type obj . After you add the second
provided constructor to the type, you create a provided instance property:
let instanceProp =
ProvidedProperty(propertyName = "InstanceProperty",
propertyType = typeof<int>,
getterCode= (fun args ->
<@@ ((%%(args.[0]) : obj) :?> string).Length @@>))
instanceProp.AddXmlDocDelayed(fun () -> "This is an instance property")
t.AddMember instanceProp
Getting this property will return the length of the string, which is the representation object. The GetterCode
property returns an F# quotation that specifies the code that the host compiler generates to get the property.
Like InvokeCode , the GetterCode function returns a quotation. The host compiler calls this function with a list of
arguments. In this case, the arguments include just the single expression that represents the instance upon
which the getter is being called, which you can access by using args.[0] . The implementation of GetterCode
then splices into the result quotation at the erased type obj , and a cast is used to satisfy the compiler's
mechanism for checking types that the object is a string. The next part of makeOneProvidedType provides an
instance method with one parameter.
let instanceMeth =
ProvidedMethod(methodName = "InstanceMethod",
parameters = [ProvidedParameter("x",typeof<int>)],
returnType = typeof<char>,
invokeCode = (fun args ->
<@@ ((%%(args.[0]) : obj) :?> string).Chars(%%(args.[1]) : int) @@>))
Finally, create a nested type that contains 100 nested properties. The creation of this nested type and its
properties is delayed, that is, computed on-demand.
t.AddMembersDelayed(fun () ->
let nestedType = ProvidedTypeDefinition("NestedType", Some typeof<obj>)
let p =
ProvidedProperty(propertyName = "StaticProperty" + string i,
propertyType = typeof<string>,
isStatic = true,
getterCode= (fun args -> <@@ valueOfTheProperty @@>))
p.AddXmlDocDelayed(fun () ->
$"This is StaticProperty{i} on NestedType")
p
]
staticPropsInNestedType)
[nestedType])
All representations of a provided type must be compatible with the erasure of the provided type. (Otherwise,
either the F# compiler will give an error for a use of the type provider, or unverifiable .NET code that isn't valid
will be generated. A type provider isn’t valid if it returns code that gives a representation that isn't valid.)
You can choose a representation for provided objects by using either of the following approaches, both of which
are very common:
If you're simply providing a strongly typed wrapper over an existing .NET type, it often makes sense for
your type to erase to that type, use instances of that type as representations, or both. This approach is
appropriate when most of the existing methods on that type still make sense when using the strongly
typed version.
If you want to create an API that differs significantly from any existing .NET API, it makes sense to create
runtime types that will be the type erasure and representations for the provided types.
The example in this document uses strings as representations of provided objects. Frequently, it may be
appropriate to use other objects for representations. For example, you may use a dictionary as a property bag:
ProvidedConstructor(parameters = [],
invokeCode= (fun args -> <@@ (new Dictionary<string,obj>()) :> obj @@>))
As an alternative, you may define a type in your type provider that will be used at runtime to form the
representation, along with one or more runtime operations:
type DataObject() =
let data = Dictionary<string,obj>()
member x.RuntimeOperation() = data.Count
In this case, you may (optionally) use this type as the type erasure by specifying this type as the baseType when
constructing the ProvidedTypeDefinition :
Key Lessons
The previous section explained how to create a simple erasing type provider that provides a range of types,
properties, and methods. This section also explained the concept of type erasure, including some of the
advantages and disadvantages of providing erased types from a type provider, and discussed representations of
erased types.
The following example shows how the type provider translates these calls:
The following code is the core of the logic to implement such a provider, and this example omits the addition of
all members to the provided type. For information about each added member, see the appropriate section later
in this topic. For the full code, download the sample from the F# 3.0 Sample Pack on the CodePlex website.
namespace Samples.FSharp.RegexTypeProvider
open System.Reflection
open Microsoft.FSharp.Core.CompilerServices
open Samples.FSharp.ProvidedTypes
open System.Text.RegularExpressions
[<TypeProvider>]
type public CheckedRegexProvider() as this =
inherit TypeProviderForNamespaces()
// Get the assembly and namespace used to house the provided types
let thisAssembly = Assembly.GetExecutingAssembly()
let rootNamespace = "Samples.FSharp.RegexTypeProvider"
let baseTy = typeof<obj>
let staticParams = [ProvidedStaticParameter("pattern", typeof<string>)]
do regexTy.DefineStaticParameters(
parameters=staticParams,
instantiationFunction=(fun typeName parameterValues ->
...
ty
| _ -> failwith "unexpected parameter values"))
do this.AddNamespace(rootNamespace, [regexTy])
[<TypeProviderAssembly>]
do ()
let isMatch =
ProvidedMethod(
methodName = "IsMatch",
parameters = [ProvidedParameter("input", typeof<string>)],
returnType = typeof<bool>,
isStatic = true,
invokeCode = fun args -> <@@ Regex.IsMatch(%%args.[0], pattern) @@>)
isMatch.AddXmlDoc "Indicates whether the regular expression finds a match in the specified input string."
ty.AddMember isMatch
The previous code defines a method IsMatch , which takes a string as input and returns a bool . The only tricky
part is the use of the args argument within the InvokeCode definition. In this example, args is a list of
quotations that represents the arguments to this method. If the method is an instance method, the first
argument represents the this argument. However, for a static method, the arguments are all just the explicit
arguments to the method. Note that the type of the quoted value should match the specified return type (in this
case, bool ). Also note that this code uses the AddXmlDoc method to make sure that the provided method also
has useful documentation, which you can supply through IntelliSense.
Next, add an instance Match method. However, this method should return a value of a provided Match type so
that the groups can be accessed in a strongly typed fashion. Thus, you first declare the Match type. Because this
type depends on the pattern that was supplied as a static argument, this type must be nested within the
parameterized type definition:
let matchTy =
ProvidedTypeDefinition(
"MatchType",
baseType = Some baseTy,
hideObjectMethods = true)
ty.AddMember matchTy
You then add one property to the Match type for each group. At runtime, a match is represented as a Match
value, so the quotation that defines the property must use the Groups indexed property to get the relevant
group.
for group in r.GetGroupNames() do
// Ignore the group named 0, which represents all input.
if group <> "0" then
let prop =
ProvidedProperty(
propertyName = group,
propertyType = typeof<Group>,
getterCode = fun args -> <@@ ((%%args.[0]:obj) :?> Match).Groups.[group] @@>)
prop.AddXmlDoc($"""Gets the ""{group}"" group from this match""")
matchTy.AddMember prop
Again, note that you’re adding XML documentation to the provided property. Also note that a property can be
read if a GetterCode function is provided, and the property can be written if a SetterCode function is provided,
so the resulting property is read only.
Now you can create an instance method that returns a value of this Match type:
let matchMethod =
ProvidedMethod(
methodName = "Match",
parameters = [ProvidedParameter("input", typeof<string>)],
returnType = matchTy,
invokeCode = fun args -> <@@ ((%%args.[0]:obj) :?> Regex).Match(%%args.[1]) :> obj @@>)
matchMeth.AddXmlDoc "Searches the specified input string for the first occurrence of this regular
expression"
ty.AddMember matchMeth
Because you are creating an instance method, args.[0] represents the RegexTyped instance on which the
method is being called, and args.[1] is the input argument.
Finally, provide a constructor so that instances of the provided type can be created.
let ctor =
ProvidedConstructor(
parameters = [],
invokeCode = fun args -> <@@ Regex(pattern, options) :> obj @@>)
ty.AddMember ctor
The constructor merely erases to the creation of a standard .NET Regex instance, which is again boxed to an
object because obj is the erasure of the provided type. With that change, the sample API usage that specified
earlier in the topic works as expected. The following code is complete and final:
namespace Samples.FSharp.RegexTypeProvider
open System.Reflection
open Microsoft.FSharp.Core.CompilerServices
open Samples.FSharp.ProvidedTypes
open System.Text.RegularExpressions
[<TypeProvider>]
type public CheckedRegexProvider() as this =
inherit TypeProviderForNamespaces()
// Get the assembly and namespace used to house the provided types.
let thisAssembly = Assembly.GetExecutingAssembly()
let rootNamespace = "Samples.FSharp.RegexTypeProvider"
let rootNamespace = "Samples.FSharp.RegexTypeProvider"
let baseTy = typeof<obj>
let staticParams = [ProvidedStaticParameter("pattern", typeof<string>)]
do regexTy.DefineStaticParameters(
parameters=staticParams,
instantiationFunction=(fun typeName parameterValues ->
let r = System.Text.RegularExpressions.Regex(pattern)
let ty =
ProvidedTypeDefinition(
thisAssembly,
rootNamespace,
typeName,
baseType = Some baseTy)
isMatch.AddXmlDoc "Indicates whether the regular expression finds a match in the specified
input string"
ty.AddMember isMatch
ty.AddMember matchMeth
// Declare a constructor.
let ctor =
ProvidedConstructor(
parameters = [],
invokeCode = fun args -> <@@ Regex(pattern) :> obj @@>)
ty.AddMember ctor
ty
| _ -> failwith "unexpected parameter values"))
do this.AddNamespace(rootNamespace, [regexTy])
[<TypeProviderAssembly>]
do ()
Key Lessons
This section explained how to create a type provider that operates on its static parameters. The provider checks
the static parameter and provides operations based on its value.
50.0 3.7
100.0 5.2
150.0 6.4
This section shows how to provide a type that you can use to get rows with a Distance property of type
float<meter> and a Time property of type float<second> . For simplicity, the following assumptions are made:
Header names are either unit-less or have the form "Name (unit)" and don't contain commas.
Units are all System International (SI) units as the FSharp.Data.UnitSystems.SI.UnitNames Module (F#)
module defines.
Units are all simple (for example, meter) rather than compound (for example, meter/second).
All columns contain floating point data.
A more complete provider would loosen these restrictions.
Again the first step is to consider how the API should look. Given an info.csv file with the contents from the
previous table (in comma-separated format), users of the provider should be able to write code that resembles
the following example:
In this case, the compiler should convert these calls into something like the following example:
The optimal translation will require the type provider to define a real CsvFile type in the type provider's
assembly. Type providers often rely on a few helper types and methods to wrap important logic. Because
measures are erased at runtime, you can use a float[] as the erased type for a row. The compiler will treat
different columns as having different measure types. For example, the first column in our example has type
float<meter> , and the second has float<second> . However, the erased representation can remain quite simple.
[<TypeProvider>]
type public MiniCsvProvider(cfg:TypeProviderConfig) as this =
inherit TypeProviderForNamespaces(cfg)
// Get the assembly and namespace used to house the provided types.
let asm = System.Reflection.Assembly.GetExecutingAssembly()
let ns = "Samples.FSharp.MiniCsvProvider"
else
// no units, just treat it as a normal float
headerText, typeof<float>
let prop =
ProvidedProperty(fieldName, fieldTy,
getterCode = fun [row] -> <@@ (%%row:float[]).[i] @@>)
// Add metadata that defines the property's location in the referenced file.
prop.AddDefinitionLocation(1, headers.[i].Index + 1, filename)
rowTy.AddMember(prop)
// Add a parameterless constructor that loads the file that was used to define the schema.
let ctor0 =
ProvidedConstructor([],
invokeCode = fun [] -> <@@ CsvFile(resolvedFilename) @@>)
ty.AddMember ctor0
// Add a more strongly typed Data property, which uses the existing property at runtime.
let prop =
ProvidedProperty("Data", typedefof<seq<_>>.MakeGenericType(rowTy),
getterCode = fun [csvFile] -> <@@ (%%csvFile:CsvFile).Data @@>)
ty.AddMember prop
Key Lessons
This section explained how to create a type provider for a local data source with a simple schema that's
contained in the data source itself.
Going Further
The following sections include suggestions for further study.
A Look at the Compiled Code for Erased Types
To give you some idea of how the use of the type provider corresponds to the code that's emitted, look at the
following function by using the HelloWorldTypeProvider that's used earlier in this topic.
let function1 () =
let obj1 = Samples.HelloWorldTypeProvider.Type1("some data")
obj1.InstanceProperty
As the example shows, all mentions of the type Type1 and the InstanceProperty property have been erased,
leaving only operations on the runtime types involved.
Design and Naming Conventions for Type Providers
Observe the following conventions when authoring type providers.
Providers for Connectivity Protocols In general, names of most provider DLLs for data and service
connectivity protocols, such as OData or SQL connections, should end in TypeProvider or TypeProviders . For
example, use a DLL name that resembles the following string:
Fabrikam.Management.BasicTypeProviders.dll
Ensure that your provided types are members of the corresponding namespace, and indicate the connectivity
protocol that you implemented:
Fabrikam.Management.BasicTypeProviders.WmiConnection<…>
Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>
Utility Providers for General Coding . For a utility type provider such as that for regular expressions, the
type provider may be part of a base library, as the following example shows:
#r "Fabrikam.Core.Text.Utilities.dll"
In this case, the provided type would appear at an appropriate point according to normal .NET design
conventions:
open Fabrikam.Core.Text.RegexTyped
Singleton Data Sources . Some type providers connect to a single dedicated data source and provide only
data. In this case, you should drop the TypeProvider suffix and use normal conventions for .NET naming:
#r "Fabrikam.Data.Freebase.dll"
For more information, see the GetConnection design convention that's described later in this topic.
Design Patterns for Type Providers
The following sections describe design patterns you can use when authoring type providers.
The GetConnection Design Pattern
Most type providers should be written to use the GetConnection pattern that's used by the type providers in
FSharp.Data.TypeProviders.dll, as the following example shows:
#r "Fabrikam.Data.WebDataStore.dll"
type ProvidedType =
member AddMemberDelayed : (unit -> MemberInfo) -> unit
member AddMembersDelayed : (unit -> MemberInfo list) -> unit
NOTE
In some cases you may have to use the helper in ProvidedTypeBuilder.MakeGenericType . See the Type Provider SDK
documentation for more details.
open Microsoft.FSharp.TypeProviders
The ProvidedTypes-0.2 helper code that is part of the F# 3.0 release has only limited support for providing
generated types. The following statements must be true for a generated type definition:
isErased must be set to false .
The generated type must be added to a newly constructed ProvidedAssembly() , which represents a
container for generated code fragments.
The provider must have an assembly that has an actual backing .NET .dll file with a matching .dll file on
disk.
Development Tips
You might find the following tips helpful during the development process:
Run two instances of Visual Studio
You can develop the type provider in one instance and test the provider in the other because the test IDE will
take a lock on the .dll file that prevents the type provider from being rebuilt. Thus, you must close the second
instance of Visual Studio while the provider is built in the first instance, and then you must reopen the second
instance after the provider is built.
Debug type providers by using invocations of fsc.exe
You can invoke type providers by using the following tools:
fsc.exe (The F# command line compiler)
fsi.exe (The F# Interactive compiler)
devenv.exe (Visual Studio)
You can often debug type providers most easily by using fsc.exe on a test script file (for example, script.fsx). You
can launch a debugger from a command prompt.
See also
Type Providers
The Type Provider SDK
Type Provider Security
5/15/2019 • 2 minutes to read • Edit Online
Type providers are assemblies (DLLs) referenced by your F# project or script that contain code to connect to
external data sources and surface this type information to the F# type environment. Typically, code in referenced
assemblies is only run when you compile and then execute the code (or in the case of a script, send the code to
F# Interactive). However, a type provider assembly will run inside Visual Studio when the code is merely
browsed in the editor. This happens because type providers need to run to add extra information to the editor,
such as Quick Info tooltips, IntelliSense completions, and so on. As a result, there are extra security
considerations for type provider assemblies, since they run automatically inside the Visual Studio process.
See also
Type Providers
Troubleshooting Type Providers
5/15/2019 • 2 minutes to read • Edit Online
This topic describes and provides potential solutions for the problems that you are most likely to encounter
when you use type providers.
P RO B L EM SUGGEST ED A C T IO N S
Schema Changes . Type providers work best when the data Clean or rebuild the project. To clean the project, choose
source schema is stable. If you add a data table or column or Build , Clean ProjectName on the menu bar. To rebuild the
make another change to that schema, the type provider project, choose Build , Rebuild ProjectName on the menu
doesn’t automatically recognize these changes. bar. These actions reset all type provider state and force the
provider to reconnect to the data source and obtain
updated schema information.
Connection Failure . The URL or connection string is For a web service or OData service, you can try the URL in
incorrect, the network is down, or the data source or service Internet Explorer to verify whether the URL is correct and
is unavailable. the service is available. For a database connection string, you
can use the data connection tools in Ser ver Explorer to
verify whether the connection string is valid and the
database is available. After you restore your connection, you
should then clean or rebuild the project so that the type
provider will reconnect to the network.
Not Valid Credentials . You must have valid permissions For a SQL connection, the username and the password that
for the data source or web service. are specified in the connection string or configuration file
must be valid for the database. If you are using Windows
Authentication, you must have access to the database. The
database administrator can identify what permissions you
need for access to each database and each element within a
database.
Not Valid Path . A path to a file was not valid. Verify whether the path is correct and the file exists. In
addition, you must either quote any backlashes in the path
appropriately or use a verbatim string or triple-quoted
string.
See also
Type Providers
What's new in F# 5.0
3/23/2021 • 12 minutes to read • Edit Online
F# 5.0 adds several improvements to the F# language and F# Interactive. It is released with .NET 5 .
You can download the latest .NET SDK from the .NET downloads page.
Get started
F# 5.0 is available in all .NET Core distributions and Visual Studio tooling. For more information, see Get started
with F# to learn more.
#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json
let o = {| X = 2; Y = "Hello" |}
You can also supply an explicit version after the name of the package like this:
#r "nuget: Newtonsoft.Json,11.0.1"
#r "nuget: FParsec"
open FParsec
This feature implements F# Tooling RFC FST-1027. For more information on package references, see the F#
Interactive tutorial.
String interpolation
F# interpolated strings are fairly similar to C# or JavaScript interpolated strings, in that they let you write code in
"holes" inside of a string literal. Here's a basic example:
However, F# interpolated strings also allow for typed interpolations, just like the sprintf function, to enforce
that an expression inside of an interpolated context conforms to a particular type. It uses the same format
specifiers.
In the preceding typed interpolation example, the %s requires the interpolation to be of type string , whereas
the %d requires the interpolation to be an integer .
Additionally, any arbitrary F# expression (or expressions) can be placed in side of an interpolation context. It is
even possible to write a more complicated expression, like so:
let str =
$"""The result of squaring each odd item in {[1..10]} is:
{
let square x = x * x
let isOdd x = x % 2 <> 0
let oddSquares xs =
xs
|> List.filter isOdd
|> List.map square
oddSquares [1..10]
}
"""
months.[month-1]
The last line will throw an exception and "month" will be shown in the error message.
You can take a name of nearly every F# construct:
module M =
let f x = nameof x
Three final additions are changes to how operators work: the addition of the nameof<'type-parameter> form for
generic type parameters, and the ability to use nameof as a pattern in a pattern match expression.
Taking a name of an operator gives its source string. If you need the compiled form, use the compiled name of
an operator:
nameof(+) // "+"
nameof op_Addition // "op_Addition"
type C<'TType> =
member _.TypeName = nameof<'TType>
[<Struct; IsByRefLike>]
type RecordedEvent = { EventType: string; Data: ReadOnlySpan<byte> }
type MyEvent =
| AData of int
| BData of string
module M =
type DU = A | B | C
let someOtherFunction x = x + 1
printfn $"{A}"
Unlike C#, when you open type on two types that expose a member with the same name, the member from the
last type being open ed shadows the other name. This is consistent with F# semantics around shadowing that
exist already.
This feature implements F# RFC FS-1068.
let l = [ 1..10 ]
let a = [| 1..10 |]
let s = "hello!"
X\ Y 0 1
0 0 1
1 2 3
z=1
X\ Y 0 1
0 4 5
1 6 7
What if you wanted to extract the slice [| 4; 5 |] from the array? This is now very simple!
let dim = 2
let m = Array3D.zeroCreate<int> dim dim dim
for z in 0..dim-1 do
for y in 0..dim-1 do
for x in 0..dim-1 do
m.[x,y,z] <- count
count <- count + 1
F# quotations improvements
F# code quotations now have the ability to retain type constraint information. Consider the following example:
open FSharp.Linq.RuntimeHelpers
The constraint generated by the inline function is retained in the code quotation. The negate function's
quotated form can now be evaluated.
This feature implements F# RFC FS-1071.
Applicative Computation Expressions
Computation expressions (CEs) are used today to model "contextual computations", or in more functional
programming-friendly terminology, monadic computations.
F# 5 introduces applicative CEs, which offer a different computational model. Applicative CEs allow for more
efficient computations provided that every computation is independent, and their results are accumulated at the
end. When computations are independent of one another, they are also trivially parallelizable, allowing CE
authors to write more efficient libraries. This benefit comes at a restriction, though: computations that depend
on previously computed values are not allowed.
The follow example shows a basic applicative CE for the Result type.
let run r1 r2 r3 =
// And here is our applicative!
let res1: Result<int, string> =
result {
let! a = r1
and! b = r2
and! c = r3
return a + b - c
}
let printApplicatives () =
let r1 = Ok 2
let r2 = Ok 3 // Error "fail!"
let r3 = Ok 4
run r1 r2 r3
run r1 (Error "failure!") r3
If you're a library author who exposes CEs in their library today, there are some additional considerations you'll
need to be aware of.
This feature implements F# RFC FS-1063.
type MyClass() =
interface IA<int> with
member x.Get() = 1
interface IA<string> with
member x.Get() = "hello"
let mc = MyClass()
let iaInt = mc :> IA<int>
let iaString = mc :> IA<string>
iaInt.Get() // 1
iaString.Get() // "hello"
using System;
namespace CSharp
{
public interface MyDimasd
{
public int Z => 0;
}
}
You can consume it in F# through any of the standard means of implementing an interface:
open CSharp
interface MyDim
This lets you safely take advantage of C# code and .NET components written in modern C# when they expect
users to be able to consume a default implementation.
This feature implements F# RFC FS-1074.
#r "nuget: Microsoft.Data.Analysis"
open Microsoft.Data.Analysis
let xs = [1..10]
You can also define reverse indexes for your own types. To do so, you'll need to implement the following
method:
let run () =
let sp = [| 1; 2; 3; 4; 5 |].AsSpan()
type InputKind =
| Text of placeholder:string option
| Password of placeholder: string option
type InputOptions =
{ Label: string option
Kind : InputKind
Validators : (string -> bool) array }
type InputBuilder() =
member t.Yield(_) =
{ Label = None
Kind = Text None
Validators = [||] }
[<CustomOperation("text")>]
member this.Text(io, ?placeholder) =
{ io with Kind = Text placeholder }
[<CustomOperation("password")>]
member this.Password(io, ?placeholder) =
{ io with Kind = Password placeholder }
[<CustomOperation("label")>]
member this.Label(io, label) =
{ io with Label = Some label }
[<CustomOperation("with_validators")>]
member this.Validators(io, [<ParamArray>] validators) =
{ io with Validators = validators }
let name =
input {
label "Name"
text
with_validators
(String.IsNullOrWhiteSpace >> not)
}
let email =
input {
label "Email"
text "Your email"
with_validators
(String.IsNullOrWhiteSpace >> not)
(fun s -> s.Contains "@")
}
let password =
input {
label "Password"
password "Must contains at least 6 characters, one number and one uppercase"
with_validators
(String.exists Char.IsUpper)
(String.exists Char.IsDigit)
(fun s -> s.Length >= 6)
}
Prior to this change, you could write the InputBuilder type as it is, but you couldn't use it the way it's used in
the example. Since overloads, optional parameters, and now System.ParamArray types are allowed, everything
just works as you'd expect it to.
This feature implements F# RFC FS-1056.
What's new in F# 4.7
3/10/2020 • 2 minutes to read • Edit Online
Get started
F# 4.7 is available in all .NET Core distributions and Visual Studio tooling. Get started with F# to learn more.
Language version
The F# 4.7 compiler introduces the ability to set your effective language version via a property in your project
file:
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
You can set it to the values 4.6 , 4.7 , latest , and preview . The default is latest .
If you set it to preview , your compiler will activate all F# preview features that are implemented in your
compiler.
Implicit yields
You no longer need to apply the yield keyword in arrays, lists, sequences, or computation expressions where
the type can be inferred. In the following example, both expressions required the yield statement for each
entry prior to F# 4.7:
let s = seq { 1; 2; 3; 4; 5 }
If you introduce a single yield keyword, every other item must also have yield applied to it.
Implicit yields are not activated when used in an expression that also uses yield! to do something like flatten a
sequence. You must continue to use yield today in these cases.
Wildcard identifiers
In F# code involving classes, the self-identifier needs to always be explicit in member declarations. But in cases
where the self-identifier is never used, it has traditionally been convention to use a double-underscore to
indicate a nameless self-identifiers. You can now use a single underscore:
type C() =
member _.M() = ()
Indentation relaxations
Prior to F# 4.7, the indentation requirements for primary constructor and static member arguments required
excessive indentation. They now only require a single indentation scope:
type OffsideCheck(a:int,
b:int, c:int,
d:int) = class end
type C() =
static member M(a:int,
b:int, c:int,
d:int) = 1
What's new in F# 4.6
2/4/2020 • 2 minutes to read • Edit Online
Get started
F# 4.6 is available in all .NET Core distributions and Visual Studio tooling. Get started with F# to learn more.
Anonymous records
Anonymous records are a new kind of F# type introduced in F# 4.6. They are simple aggregates of named values
that don't need to be declared before use. You can declare them as either structs or reference types. They're
reference types by default.
open System
let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
They can also be declared as structs for when you want to group value types and are operating in performance-
sensitive scenarios:
open System
let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
They're quite powerful and can be used in numerous scenarios. Learn more at Anonymous records.
ValueOption functions
The ValueOption type added in F# 4.5 now has "module-bound function parity" with the Option type. Some of
the more commonly-used examples are as follows:
// Multiply a value option by 2 if it has value
let xOpt = ValueSome 99
let result = xOpt |> ValueOption.map (fun v -> v * 2)
This allows for ValueOption to be used just like Option in scenarios where having a value type improves
performance.
What's new in F# 4.5
11/2/2020 • 3 minutes to read • Edit Online
F# 4.5 adds multiple improvements to the F# language. Many of these features were added together to enable
you to write efficient code in F# while also ensuring this code is safe. Doing so means adding a few concepts to
the language and a significant amount of compiler analysis when using these constructs.
Get started
F# 4.5 is available in all .NET Core distributions and Visual Studio tooling. Get started with F# to learn more.
// managed memory
let arrayMemory = Array.zeroCreate<byte>(100)
let arraySpan = new Span<byte>(arrayMemory)
// native memory
let nativeMemory = Marshal.AllocHGlobal(100);
let nativeSpan = new Span<byte>(nativeMemory.ToPointer(), 100)
// stack memory
let mem = NativePtr.stackalloc<byte>(100)
let mem2 = mem |> NativePtr.toVoidPtr
let stackSpan = Span<byte>(mem2, 100)
An important aspect to this is that Span and other byref-like structs have very rigid static analysis performed by
the compiler that restrict their usage in ways you might find to be unexpected. This is the fundamental tradeoff
between performance, expressiveness, and safety that is introduced in F# 4.5.
Revamped byrefs
Prior to F# 4.5, Byrefs in F# were unsafe and unsound for numerous applications. Soundness issues around
byrefs have been addressed in F# 4.5 and the same static analysis done for span and byref-like structs was also
applied.
inref<'T> and outref<'T>
To represent the notion of a read-only, write-only, and read/write managed pointer, F# 4.5 introduces the
inref<'T> , outref<'T> types to represent read-only and write-only pointers, respectively. Each have different
semantics. For example, you cannot write to an inref<'T> :
By default, type inference will infer managed pointers as inref<'T> to be in line with the immutable nature of
F# code, unless something has already been declared as mutable. To make something writable, you'll need to
declare a type as mutable before passing its address to a function or member that manipulates it. To learn more,
see Byrefs.
Readonly structs
Starting with F# 4.5, you can annotate a struct with IsReadOnlyAttribute as such:
[<IsReadOnly; Struct>]
type S(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
This disallows you from declaring a mutable member in the struct and emits metadata that allows F# and C# to
treat it as readonly when consumed from an assembly. To learn more, see ReadOnly structs.
Void pointers
The voidptr type is added to F# 4.5, as are the following functions:
NativePtr.ofVoidPtr to convert a void pointer into a native int pointer
NativePtr.toVoidPtr to convert a native int pointer to a void pointer
This is helpful when interoperating with a native component that makes use of void pointers.
This allows you to shorten code that often involves mixing options (or other types) with computation
expressions such as async. To learn more, see match!.
Relaxed upcasting requirements in array, list, and sequence
expressions
Mixing types where one may inherit from another inside of array, list, and sequence expressions has traditionally
required you to upcast any derived type to its parent type with :> or upcast . This is now relaxed,
demonstrated as follows:
let x3 : obj list = [ yield "a" ] // Now ok for F# 4.5, and can replace x2
module NoExcessiveIndenting =
System.Console.WriteLine(format="{0}", arg = [|
"hello"
|])
System.Console.WriteLine([|
"hello"
|])
F# Language Reference
5/20/2021 • 8 minutes to read • Edit Online
This section is a reference for the F# language, a multi-paradigm programming language targeting .NET. The F#
language supports functional, object-oriented, and imperative programming models.
F# Tokens
The following table shows reference articles that provide tables of keywords, symbols, and literals that are used
as tokens in F#.
T IT L E DESC RIP T IO N
Symbol and Operator Reference Contains a table of symbols and operators that are used in
the F# language.
F# Language Concepts
The following table shows reference topics available that describe language concepts.
T IT L E DESC RIP T IO N
F# Types Describes the types that are used in F# and how F# types
are named and described.
Type Inference Describes how the F# compiler infers the types of values,
variables, parameters, and return values.
Parameters and Arguments Describes language support for defining parameters and
passing arguments to functions, methods, and properties. It
includes information about how to pass by reference.
Pattern Matching Describes patterns, which are rules for transforming input
data and are used throughout the F# language. You can
compare data with a pattern, decompose data into
constituent parts, or extract information from data in various
ways.
Resource Management: The use Keyword Describes the keywords use and using , which can
control the initialization and release of resources
Import Declarations: The open Keyword Describes how open works. An import declaration specifies
a module or namespace whose elements you can reference
without using a fully qualified name.
T IT L E DESC RIP T IO N
Plain Text Formatting Learn how to use sprintf and other plain text formatting in
F# applications and scripts.
F# Types
The following table shows reference topics available that describe types supported by the F# language.
T IT L E DESC RIP T IO N
Basic Types Describes the fundamental basic types that are used in the
F# language. It also provides the corresponding .NET types
and the minimum and maximum values for each type.
Unit Type Describes the unit type, which is a type that indicates the
absence of a specific value; the unit type has only a single
value, which acts as a placeholder when no other value exists
or is needed.
Reference Cells Describes reference cells, which are storage locations that
enable you to create mutable variables with reference
semantics.
Type Abbreviations Describes type abbreviations, which are alternate names for
types.
Classes Describes classes, which are types that represent objects that
can have properties, methods, and events.
Abstract Classes Describes abstract classes, which are classes that leave some
or all members unimplemented, so that implementations can
be provided by derived classes.
Type Extensions Describes type extensions, which let you add new members
to a previously defined object type.
F# Expressions
The following table lists topics that describe F# expressions.
T IT L E DESC RIP T IO N
Loops: for...to Expression Describes the for...to expression, which is used to iterate
in a loop over a range of values of a loop variable.
Lazy Expressions Describes lazy expressions, which are computations that are
not evaluated immediately, but are instead evaluated when
the result is actually needed.
T IT L E DESC RIP T IO N
Compiler-supported Constructs
The following table lists topics that describe special compiler-supported constructs.
TO P IC DESC RIP T IO N
Source Line, File, and Path Identifiers Describes the identifiers __LINE__ ,
__SOURCE_DIRECTORY__ , and __SOURCE_FILE__ , which are
built-in values that enable you to access the source line
number, directory and file name in your code.
Keyword Reference
6/16/2021 • 8 minutes to read • Edit Online
F# Keyword Table
The following table shows all F# keywords in alphabetical order, together with brief descriptions and links to
relevant topics that contain more information.
K EY W O RD L IN K DESC RIP T IO N
Constraints
Verbose Syntax
Exception Types
try Exceptions: The try...with Expression Used to introduce a block of code that
might generate an exception. Used
Exceptions: The try...finally Expression together with with or finally .
K EY W O RD L IN K DESC RIP T IO N
Structures
Enumerations
Discriminated Unions
Type Abbreviations
Units of Measure
use Resource Management: The use Used instead of let for values that
Keyword require Dispose to be called to free
resources.
val Explicit Fields: The val Keyword Used in a signature to indicate a value,
or in a type to declare a member, in
Signatures limited situations.
Members
The following tokens are reserved in F# because they are keywords in the OCaml language:
asr
land
lor
lsl
lsr
lxor
mod
sig
If you use the --mlcompatibility compiler option, the above keywords are available for use as identifiers.
The following tokens are reserved as keywords for future expansion of the F# language:
break
checked
component
const
constraint
continue
event
external
include
mixin
parallel
process
protected
pure
sealed
tailcall
trait
virtual
The following tokens were once reserved as keywords but were released in F# 4.1, so now you can use them as
identifiers:
K EY W O RD REA SO N
atomic this was related to the fad for transactional memory circa
2006. In F# this would now be a library-defined
computation expression
See also
F# Language Reference
Symbol and Operator Reference
Compiler Options
Symbol and operator reference
4/7/2021 • 8 minutes to read • Edit Online
This article includes a table of symbols and operators that are used in the F# language.
Strings
Operator precedence
The following table shows the order of precedence of operators and other expression keywords in the F#
language, in order from lowest precedence to the highest precedence. Also listed is the associativity, if
applicable.
as Right
when Right
| (pipe) Left
; Right
let Nonassociative
if Nonassociative
not Right
-> Right
:= Right
O P ERATO R A SSO C IAT IVIT Y
, Nonassociative
or , || Left
^ op Right
(including ^^^ )
:: Right
:? Not associative
** op Right
. Left
f(x) Left
F# supports custom operator overloading. This means that you can define your own operators. In the previous
table, op can be any valid (possibly empty) sequence of operator characters, either built-in or user-defined. Thus,
you can use this table to determine what sequence of characters to use for a custom operator to achieve the
desired level of precedence. Leading . characters are ignored when the compiler determines precedence.
See also
F# Language Reference
Operator Overloading
Arithmetic Operators
7/30/2019 • 3 minutes to read • Edit Online
This topic describes arithmetic operators that are available in the F# language.
B IN A RY O P ERATO R N OT ES
** (exponentiation, to the power of) Possible overflow condition when the result exceeds the
maximum absolute value for the type.
UN A RY O P ERATO R N OT ES
O P ERATO R N OT ES
See also
Symbol and Operator Reference
Operator Overloading
Bitwise Operators
Boolean Operators
Boolean Operators
5/15/2019 • 2 minutes to read • Edit Online
This topic describes the support for Boolean operators in the F# language.
|| Boolean OR
The Boolean AND and OR operators perform short-circuit evaluation, that is, they evaluate the expression on the
right of the operator only when it is necessary to determine the overall result of the expression. The second
expression of the && operator is evaluated only if the first expression evaluates to true ; the second expression
of the || operator is evaluated only if the first expression evaluates to false .
See also
Bitwise Operators
Arithmetic Operators
Symbol and Operator Reference
Bitwise Operators
5/15/2019 • 2 minutes to read • Edit Online
This topic describes bitwise operators that are available in the F# language.
O P ERATO R N OT ES
&&& Bitwise AND operator. Bits in the result have the value 1 if
and only if the corresponding bits in both source operands
are 1.
<<< Bitwise left-shift operator. The result is the first operand with
bits shifted left by the number of bits in the second operand.
Bits shifted off the most significant position are not rotated
into the least significant position. The least significant bits
are padded with zeros. The type of the second argument is
int32 .
The following types can be used with bitwise operators: byte , sbyte , int16 , uint16 , int32 (int) , uint32 ,
int64 , uint64 , nativeint , and unativeint .
See also
Symbol and Operator Reference
Arithmetic Operators
Boolean Operators
Nullable Operators
3/6/2021 • 3 minutes to read • Edit Online
Nullable operators are binary arithmetic or comparison operators that work with nullable arithmetic types on
one or both sides. Nullable types arise frequently when you work with data from sources such as databases that
allow nulls in place of actual values. Nullable operators are used frequently in query expressions. In addition to
nullable operators for arithmetic and comparison, conversion operators can be used to convert between
nullable types. There are also nullable versions of certain query operators.
N UL L A B L E O N L EF T N UL L A B L E O N RIGH T B OT H SIDES N UL L A B L E
?= =? ?=?
?+ +? ?+?
?- -? ?-?
?* *? ?*?
?/ /? ?/?
?% %? ?%?
Remarks
The nullable operators are included in the NullableOperators module in the namespace FSharp.Linq. The type
for nullable data is System.Nullable<'T> .
In query expressions, nullable types arise when selecting data from a data source that allows nulls instead of
values. In a SQL Server database, each data column in a table has an attribute that indicates whether nulls are
allowed. If nulls are allowed, the data returned from the database can contain nulls that cannot be represented
by a primitive data type such as int , float , and so on. Therefore, the data is returned as a
System.Nullable<int> instead of int , and System.Nullable<float> instead of float . The actual value can be
obtained from a System.Nullable<'T> object by using the Value property, and you can determine if a
System.Nullable<'T> object has a value by calling the HasValue method. Another useful method is the
System.Nullable<'T>.GetValueOrDefault method, which allows you to get the value or a default value of the
appropriate type. The default value is some form of "zero" value, such as 0, 0.0, or false .
Nullable types may be converted to non-nullable primitive types using the usual conversion operators such as
int or float . It is also possible to convert from one nullable type to another nullable type by using the
conversion operators for nullable types. The appropriate conversion operators have the same name as the
standard ones, but they are in a separate module, the Nullable module in the FSharp.Linq namespace. Typically,
you open this namespace when working with query expressions. In that case, you can use the nullable
conversion operators by adding the prefix Nullable. to the appropriate conversion operator, as shown in the
following code.
open Microsoft.FSharp.Linq
// Use the Nullable.float conversion operator to convert from one nullable type to another nullable type.
let nullableFloat = Nullable.float nullableInt
open System
open System.Data
open System.Data.Linq
open Microsoft.FSharp.Data.TypeProviders
open Microsoft.FSharp.Linq
[<Generate>]
type dbSchema = SqlDataConnection<"Data Source=MYSERVER\INSTANCE;Initial Catalog=MyDatabase;Integrated
Security=SSPI;">
let db = dbSchema.GetDataContext()
query {
for row in db.Table2 do
where (row.TestData1.HasValue && row.TestData1.Value > 2)
select row
} |> Seq.iter (fun row -> printfn $"%d{row.TestData1.Value} %s{row.Name}")
query {
for row in db.Table2 do
// Use a nullable operator ?>
where (row.TestData1 ?> 2)
select row
} |> Seq.iter (fun row -> printfn "%d{row.TestData1.GetValueOrDefault()} %s{row.Name}")
See also
Type Providers
Query Expressions
Functions
11/2/2020 • 9 minutes to read • Edit Online
Functions are the fundamental unit of program execution in any programming language. As in other languages,
an F# function has a name, can have parameters and take arguments, and has a body. F# also supports
functional programming constructs such as treating functions as values, using unnamed functions in
expressions, composition of functions to form new functions, curried functions, and the implicit definition of
functions by way of the partial application of function arguments.
You define functions by using the let keyword, or, if the function is recursive, the let rec keyword
combination.
Syntax
// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body
Remarks
The function-name is an identifier that represents the function. The parameter-list consists of successive
parameters that are separated by spaces. You can specify an explicit type for each parameter, as described in the
Parameters section. If you do not specify a specific argument type, the compiler attempts to infer the type from
the function body. The function-body consists of an expression. The expression that makes up the function body
is typically a compound expression consisting of a number of expressions that culminate in a final expression
that is the return value. The return-type is a colon followed by a type and is optional. If you do not specify the
type of the return value explicitly, the compiler determines the return type from the final expression.
A simple function definition resembles the following:
let f x = x + 1
In the previous example, the function name is f , the argument is x , which has type int , the function body is
x + 1 , and the return value is of type int .
Functions can be marked inline . For information about inline , see Inline Functions.
Scope
At any level of scope other than module scope, it is not an error to reuse a value or function name. If you reuse a
name, the name declared later shadows the name declared earlier. However, at the top level scope in a module,
names must be unique. For example, the following code produces an error when it appears at module scope, but
not when it appears inside a function:
let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []
let function1 =
let list1 = [1; 2; 3]
let list1 = []
list1
let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
let list1 = [1; 5; 10]
x + List.sum list1
Parameters
Names of parameters are listed after the function name. You can specify a type for a parameter, as shown in the
following example:
let f (x : int) = x + 1
If you specify a type, it follows the name of the parameter and is separated from the name by a colon. If you
omit the type for the parameter, the parameter type is inferred by the compiler. For example, in the following
function definition, the argument x is inferred to be of type int because 1 is of type int .
let f x = x + 1
However, the compiler will attempt to make the function as generic as possible. For example, note the following
code:
let f x = (x, x)
The function creates a tuple from one argument of any type. Because the type is not specified, the function can
be used with any argument type. For more information, see Automatic Generalization.
Function Bodies
A function body can contain definitions of local variables and functions. Such variables and functions are in
scope in the body of the current function but not outside it. When you have the lightweight syntax option
enabled, you must use indentation to indicate that a definition is in a function body, as shown in the following
example:
For more information, see Code Formatting Guidelines and Verbose Syntax.
Return Values
The compiler uses the final expression in a function body to determine the return value and type. The compiler
might infer the type of the final expression from previous expressions. In the function cylinderVolume , shown in
the previous section, the type of pi is determined from the type of the literal 3.14159 to be float . The
compiler uses the type of pi to determine the type of the expression h * pi * r * r to be float . Therefore,
the overall return type of the function is float .
To specify the return value explicitly, write the code as follows:
As the code is written above, the compiler applies float to the entire function; if you mean to apply it to the
parameter types as well, use the following code:
Calling a Function
You call functions by specifying the function name followed by a space and then any arguments separated by
spaces. For example, to call the function cylinderVolume and assign the result to the value vol , you write the
following code:
You would then supply the additional argument as needed for various lengths of pipe of the two different sizes:
Some recursive functions might overflow the program stack or perform inefficiently if you do not write them
with care and with awareness of special techniques, such as the use of accumulators and continuations.
Function Values
In F#, all functions are considered values; in fact, they are known as function values. Because functions are
values, they can be used as arguments to other functions or in other contexts where values are used. Following
is an example of a function that takes a function value as an argument:
You specify the type of a function value by using the -> token. On the left side of this token is the type of the
argument, and on the right side is the return value. In the previous example, apply1 is a function that takes a
function transform as an argument, where transform is a function that takes an integer and returns another
integer. The following code shows how to use apply1 :
let increment x = x + 1
The value of result will be 101 after the previous code runs.
Multiple arguments are separated by successive -> tokens, as shown in the following example:
let mul x y = x * y
Lambda Expressions
A lambda expression is an unnamed function. In the previous examples, instead of defining named functions
increment and mul , you could use lambda expressions as follows:
You define lambda expressions by using the fun keyword. A lambda expression resembles a function definition,
except that instead of the = token, the -> token is used to separate the argument list from the function body.
As in a regular function definition, the argument types can be inferred or specified explicitly, and the return type
of the lambda expression is inferred from the type of the last expression in the body. For more information, see
Lambda Expressions: The fun Keyword.
let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
let addOne x = x + 1
let timesTwo x = 2 * x
// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo
// Result is 5
let result1 = Compose1 2
// Result is 6
let result2 = Compose2 2
// Pipelining
// Pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo
// Result is 5
let result3 = Pipeline1 2
// Result is 6
let result4 = Pipeline2 2
Overloading Functions
You can overload methods of a type but not functions. For more information, see Methods.
See also
Values
F# Language Reference
let Bindings
11/2/2020 • 5 minutes to read • Edit Online
A binding associates an identifier with a value or function. You use the let keyword to bind a name to a value
or function.
Syntax
// Binding a value:
let identifier-or-pattern [: type] =expressionbody-expression
// Binding a function value:
let identifier parameter-list [: return-type ] =expressionbody-expression
Remarks
The let keyword is used in binding expressions to define values or function values for one or more names. The
simplest form of the let expression binds a name to a simple value, as follows.
let i = 1
If you separate the expression from the identifier by using a new line, you must indent each line of the
expression, as in the following code.
let someVeryLongIdentifier =
// Note indentation below.
3 * 4 + 5 * 6
Instead of just a name, a pattern that contains names can be specified, for example, a tuple, as shown in the
following code.
let i, j, k = (1, 2, 3)
The body-expression is the expression in which the names are used. The body expression appears on its own
line, indented to line up exactly with the first character in the let keyword:
let result =
let i, j, k = (1, 2, 3)
// Body expression:
i + 2*j + 3*k
A let binding can appear at the module level, in the definition of a class type, or in local scopes, such as in a
function definition. A let binding at the top level in a module or in a class type does not need to have a body
expression, but at other scope levels, the body expression is required. The bound names are usable after the
point of definition, but not at any point before the let binding appears, as is illustrated in the following code.
// Error:
printfn "%d" x
let x = 100
// OK:
printfn "%d" x
Function Bindings
Function bindings follow the rules for value bindings, except that function bindings include the function name
and the parameters, as shown in the following code.
let function1 a =
a + 1
A let binding expression evaluates to the value of the last expression. Therefore, in the following code
example, the value of result is computed from 100 * function3 (1, 2) , which evaluates to 300 .
let result =
let function3 (a, b) = a + b
100 * function3 (1, 2)
Type Annotations
You can specify types for parameters by including a colon (:) followed by a type name, all enclosed in
parentheses. You can also specify the type of the return value by appending the colon and type after the last
parameter. The full type annotations for function1 , with integers as the parameter types, would be as follows.
When there are no explicit type parameters, type inference is used to determine the types of parameters of
functions. This can include automatically generalizing the type of a parameter to be generic.
For more information, see Automatic Generalization and Type Inference.
The scopes of field1 and field2 are limited to the type in which they are declared. For more information, see
let Bindings in Classes and Classes.
[<Obsolete>]
let function1 x y = x + y
module Module1 =
let function1 x = x + 1.0
module Module2 =
let function2 x =
Module1.function1 x
open Module1
let function3 x =
function1 x
Some modules have the attribute RequireQualifiedAccess, which means that the functions that they expose must
be qualified with the name of the module. For example, the F# List module has this attribute.
For more information on modules and access control, see Modules and Access Control.
See also
Functions
let Bindings in Classes
do Bindings
7/30/2019 • 2 minutes to read • Edit Online
A do binding is used to execute code without defining a function or value. Also, do bindings can be used in
classes, see do Bindings in Classes.
Syntax
[ attributes ]
[ do ]expression
Remarks
Use a do binding when you want to execute code independently of a function or value definition. The
expression in a do binding must return unit . Code in a top-level do binding is executed when the module is
initialized. The keyword do is optional.
Attributes can be applied to a top-level do binding. For example, if your program uses COM interop, you might
want to apply the STAThread attribute to your program. You can do this by using an attribute on a do binding,
as shown in the following code.
open System
open System.Windows.Forms
[<STAThread>]
do
Application.Run(form1)
See also
F# Language Reference
Functions
Lambda Expressions: The fun Keyword (F#)
7/30/2019 • 2 minutes to read • Edit Online
The fun keyword is used to define a lambda expression, that is, an anonymous function.
Syntax
fun parameter-list -> expression
Remarks
The parameter-list typically consists of names and, optionally, types of parameters. More generally, the
parameter-list can be composed of any F# patterns. For a full list of possible patterns, see Pattern Matching. Lists
of valid parameters include the following examples.
The expression is the body of the function, the last expression of which generates a return value. Examples of
valid lambda expressions include the following:
fun x -> x + 1
fun a b c -> printfn "%A %A %A" a b c
fun (a: int) (b: int) (c: int) -> a + b * c
fun x y -> let swap (a, b) = (b, a) in swap (x, y)
See also
Functions
Recursive Functions: The rec Keyword
3/6/2021 • 3 minutes to read • Edit Online
The rec keyword is used together with the let keyword to define a recursive function.
Syntax
// Recursive function:
let rec function-nameparameter-list =
function-body
and function2-nameparameter-list =
function2-body
...
Remarks
Recursive functions - functions that call themselves - are identified explicitly in the F# language with the rec
keyword. The rec keyword makes the name of the let binding available in its body.
The following example shows a recursive function that computes the nth Fibonacci number using the
mathematical definition.
NOTE
In practice, code like the previous sample is not ideal because it unecessarily recomputes values that have already been
computed. This is because it is not tail recursive, which is explained further in this article.
Methods are implicitly recursive within the type they are defined in, meaning there is no need to add the rec
keyword. For example:
type MyClass() =
member this.Fib(n) =
match n with
| 0 | 1 -> n
| n -> this.Fib(n-1) + this.Fib(n-2)
Let bindings within classes are not implicitly recursive, though. All let -bound functions require the rec
keyword.
Tail recursion
For some recursive functions, it is necessary to refactor a more "pure" definition to one that is tail recursive. This
prevents unnecessary recomputations. For example, the previous fibonacci number generator can be rewritten
like this:
let fib n =
let rec loop acc1 acc2 n =
match n with
| 0 -> acc1
| 1 -> acc2
| _ ->
loop acc2 (acc1 + acc2) (n - 1)
loop 0 1 n
This is a more complicated implementation. Generating a fibonacci number is a great example of a "naive"
algorithm that's mathematically pure but inefficient in practice. Several aspects make it efficient in F# while still
remaining recursively defined:
A recursive inner function named loop , which is an idiomatic F# pattern.
Two accumulator parameters, which pass accumulate values to recursive calls.
A check on the value of n to return a specific accumulate.
If this example were written iteratively with a loop, the code would look similar with two different values
accumulating numbers until a particular condition was met.
The reason why this is tail-recursive is because the recursive call does not need to save any values on the call
stack. All intermediate values being calculated are accumulated via inputs to the inner function. This also allows
the F# compiler to optimize the code to be just as fast as if you had written something like a while loop.
It's common to write F# code that recursively processes something with an inner and outer function, as the
previous example shows. The inner function uses tail recursion, while the outer function has a better interface
for callers.
Recursive values
You can also define a let -bound value to be recursive. This is sometimes done for logging. With F# 5 and the
nameof function, you can do this:
This topic describes the method that you use to set the entry point to an F# program.
Syntax
[<EntryPoint>]
let-function-binding
Remarks
In the previous syntax, let-function-binding is the definition of a function in a let binding.
The entry point to a program that is compiled as an executable file is where execution formally starts. You
specify the entry point to an F# application by applying the EntryPoint attribute to the program's main
function. This function (created by using a let binding) must be the last function in the last compiled file. The
last compiled file is the last file in the project or the last file that is passed to the command line.
The entry point function has type string array -> int . The arguments provided on the command line are
passed to the main function in the array of strings. The first element of the array is the first argument; the name
of the executable file is not included in the array, as it is in some other languages. The return value is used as the
exit code for the process. Zero usually indicates success; nonzero values indicate an error. There is no convention
for the specific meaning of nonzero return codes; the meanings of the return codes are application-specific.
The following example illustrates a simple main function.
[<EntryPoint>]
let main args =
printfn "Arguments passed to function : %A" args
// Return 0. This indicates success.
0
When this code is executed with the command line EntryPoint.exe 1 2 3 , the output is as follows.
See also
Functions
let Bindings
External Functions
8/22/2019 • 2 minutes to read • Edit Online
This topic describes F# language support for calling functions in native code.
Syntax
[<DllImport( arguments )>]
extern declaration
Remarks
In the previous syntax, arguments represents arguments that are supplied to the
System.Runtime.InteropServices.DllImportAttribute attribute. The first argument is a string that represents the
name of the DLL that contains this function, without the .dll extension. Additional arguments can be supplied for
any of the public properties of the System.Runtime.InteropServices.DllImportAttribute class, such as the calling
convention.
Assume you have a native C++ DLL that contains the following exported function.
#include <stdio.h>
extern "C" void __declspec(dllexport) HelloWorld()
{
printf("Hello world, invoked by F#!\n");
}
You can call this function from F# by using the following code.
open System.Runtime.InteropServices
module InteropWithNative =
[<DllImport(@"C:\bin\nativedll", CallingConvention = CallingConvention.Cdecl)>]
extern void HelloWorld()
InteropWithNative.HelloWorld()
Interoperability with native code is referred to as platform invoke and is a feature of the CLR. For more
information, see Interoperating with Unmanaged Code. The information in that section is applicable to F#.
See also
Functions
Inline Functions
7/30/2019 • 2 minutes to read • Edit Online
Inline functions are functions that are integrated directly into the calling code.
Without the inline modifier, type inference forces the function to take a specific type, in this case int . But with
the inline modifier, the function is also inferred to have a statically resolved type parameter. With the inline
modifier, the type is inferred to be the following:
This means that the function accepts any type that supports a conversion to float .
See also
Functions
Constraints
Statically Resolved Type Parameters
Values
5/20/2021 • 3 minutes to read • Edit Online
Values in F# are quantities that have a specific type; values can be integral or floating point numbers, characters
or text, lists, sequences, arrays, tuples, discriminated unions, records, class types, or function values.
Binding a Value
The term binding means associating a name with a definition. The let keyword binds a value, as in the
following examples:
let a = 1
let b = 100u
let str = "text"
let f x = x + 1
The type of a value is inferred from the definition. For a primitive type, such as an integral or floating point
number, the type is determined from the type of the literal. Therefore, in the previous example, the compiler
infers the type of b to be unsigned int , whereas the compiler infers the type of a to be int . The type of a
function value is determined from the return value in the function body. For more information about function
value types, see Functions. For more information about literal types, see Literals.
The compiler does not issue diagnostics about unused bindings by default. To receive these messages, enable
warning 1182 in your project file or when invoking the compiler (see --warnon under Compiler Options).
Why Immutable?
Immutable values are values that cannot be changed throughout the course of a program's execution. If you are
used to languages such as C++, Visual Basic, or C#, you might find it surprising that F# puts primacy over
immutable values rather than variables that can be assigned new values during the execution of a program.
Immutable data is an important element of functional programming. In a multithreaded environment, shared
mutable variables that can be changed by many different threads are difficult to manage. Also, with mutable
variables, it can sometimes be hard to tell if a variable might be changed when it is passed to another function.
In pure functional languages, there are no variables, and functions behave strictly as mathematical functions.
Where code in a procedural language uses a variable assignment to alter a value, the equivalent code in a
functional language has an immutable value that is the input, an immutable function, and different immutable
values as the output. This mathematical strictness allows for tighter reasoning about the behavior of the
program. This tighter reasoning is what enables compilers to check code more stringently and to optimize more
effectively, and helps make it easier for developers to understand and write correct code. Functional code is
therefore likely to be easier to debug than ordinary procedural code.
F# is not a pure functional language, yet it fully supports functional programming. Using immutable values is a
good practice because doing this allows your code to benefit from an important aspect of functional
programming.
Mutable Variables
You can use the keyword mutable to specify a variable that can be changed. Mutable variables in F# should
generally have a limited scope, either as a field of a type or as a local value. Mutable variables with a limited
scope are easier to control and are less likely to be modified in incorrect ways.
You can assign an initial value to a mutable variable by using the let keyword in the same way as you would
define a value. However, the difference is that you can subsequently assign new values to mutable variables by
using the <- operator, as in the following example.
let mutable x = 1
x <- x + 1
Values marked mutable may be automatically promoted to 'a ref if captured by a closure, including forms
that create closures, such as seq builders. If you wish to be notified when this occurs, enable warning 3180 in
your project file or when invoking the compiler.
Related Topics
T IT L E DESC RIP T IO N
let Bindings Provides information about using the let keyword to bind
names to values and functions.
See also
Null Values
F# Language Reference
Null Values
11/2/2020 • 3 minutes to read • Edit Online
Null Value
The null value is not normally used in F# for values or variables. However, null appears as an abnormal value in
certain situations. If a type is defined in F#, null is not permitted as a regular value unless the AllowNullLiteral
attribute is applied to the type. If a type is defined in some other .NET language, null is a possible value, and
when you are interoperating with such types, your F# code might encounter null values.
For a type defined in F# and used strictly from F#, the only way to create a null value using the F# library directly
is to use Unchecked.defaultof or Array.zeroCreate. However, for an F# type that is used from other .NET
languages, or if you are using that type with an API that is not written in F#, such as the .NET Framework, null
values can occur.
You can use the option type in F# when you might use a reference variable with a possible null value in another
.NET language. Instead of null, with an F# option type, you use the option value None if there is no object. You
use the option value Some(obj) with an object obj when there is an object. For more information, see Options.
Note that you can still pack a null value into an Option if, for Some x , x happens to be null . Because of this,
it is important you use None when a value is null .
The null keyword is a valid keyword in the F# language, and you have to use it when you are working with
.NET Framework APIs or other APIs that are written in another .NET language. The two situations in which you
might need a null value are when you call a .NET API and pass a null value as an argument, and when you
interpret the return value or an output parameter from a .NET method call.
To pass a null value to a .NET method, just use the null keyword in the calling code. The following code
example illustrates this.
open System
To interpret a null value that is obtained from a .NET method, use pattern matching if you can. The following
code example shows how to use pattern matching to interpret the null value that is returned from ReadLine
when it tries to read past the end of an input stream.
// Open a file and create a stream reader.
let fileStream1 =
try
System.IO.File.OpenRead("TextFile1.txt")
with
| :? System.IO.FileNotFoundException -> printfn "Error: TextFile1.txt not found."; exit(1)
Null values for F# types can also be generated in other ways, such as when you use Array.zeroCreate , which
calls Unchecked.defaultof . You must be careful with such code to keep the null values encapsulated. In a library
intended only for F#, you do not have to check for null values in every function. If you are writing a library for
interoperation with other .NET languages, you might have to add checks for null input parameters and throw an
ArgumentNullException , just as you do in C# or Visual Basic code.
You can use the following code to check if an arbitrary value is null.
See also
Values
Match Expressions
Literals
5/20/2021 • 2 minutes to read • Edit Online
This article provides a table that shows how to specify the type of a literal in F#.
Literal types
The following table shows the literal types in F#. Characters that represent digits in hexadecimal notation are not
case-sensitive; characters that identify the type are case-sensitive.
0b00000101y
int32 86l
lf 0x00000000lf
TYPE DESC RIP T IO N SUF F IX O R P REF IX EXA M P L ES
LF 0x0000000000000000LF
or
@"c:\filename"
or
"""<book
title="Paradise
Lost">"""
or
"string1" + "string2"
@"\\server\share"B
(ASCII)
Named literals
Values that are intended to be constants can be marked with the Literal attribute. This attribute has the effect of
causing a value to be compiled as a constant.
In pattern matching expressions, identifiers that begin with lowercase characters are always treated as variables
to be bound, rather than as literals, so you should generally use initial capitals when you define literals.
[<Literal>]
let SomeJson = """{"numbers":[1,2,3,4,5]}"""
[<Literal>]
let Literal1 = "a" + "b"
[<Literal>]
let FileLocation = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let Literal2 = 1 ||| 64
[<Literal>]
let Literal3 = System.IO.FileAccess.Read ||| System.IO.FileAccess.Write
Remarks
Unicode strings can contain explicit encodings that you can specify by using \u followed by a 16-bit
hexadecimal code (0000 - FFFF), or UTF-32 encodings that you can specify by using \U followed by a 32-bit
hexadecimal code that represents any Unicode code point (00000000 - 0010FFFF).
The use of other bitwise operators other than ||| isn't allowed.
This topic describes the types that are used in F# and how F# types are named and described.
Summary of F# Types
Some types are considered primitive types, such as the Boolean type bool and integral and floating point types
of various sizes, which include types for bytes and characters. These types are described in Primitive Types.
Other types that are built into the language include tuples, lists, arrays, sequences, records, and discriminated
unions. If you have experience with other .NET languages and are learning F#, you should read the topics for
each of these types. These F#-specific types support styles of programming that are common to functional
programming languages. Many of these types have associated modules in the F# library that support common
operations on these types.
The type of a function includes information about the parameter types and return type.
The .NET Framework is the source of object types, interface types, delegate types, and others. You can define
your own object types just as you can in any other .NET language.
Also, F# code can define aliases, which are named type abbreviations, that are alternative names for types. You
might use type abbreviations when the type might change in the future and you want to avoid changing the
code that depends on the type. Or, you might use a type abbreviation as a friendly name for a type that can
make code easier to read and understand.
F# provides useful collection types that are designed with functional programming in mind. Using these
collection types helps you write code that is more functional in style. For more information, see F# Collection
Types.
TYPE T Y P E SY N TA X EXA M P L ES
float
string
or
modules.type-name
or
namespaces.modules.type-name
int array
float[,]
or list<'a>
list<string>
ref<int>
Dictionary<int, string>
function type that has a single parameter-type1 -> return-type A function that takes an int and
parameter returns a string has type
int -> string
function type that has multiple parameter-type1 -> parameter-type2 A function that takes an int and a
parameters -> ... -> return-type float and returns a string has
type int -> float -> string
TYPE T Y P E SY N TA X EXA M P L ES
#seq<int>
Related Topics
TO P IC DESC RIP T IO N
Primitive Types Describes built-in simple types such as integral types, the
Boolean type, and character types.
Unit Type Describes the unit type, a type that has one value and
that is indicated by (); equivalent to void in C# and
Nothing in Visual Basic.
Options Describes the option type, a type that may either have a
value or be empty.
Discriminated Unions Describes the discriminated union type, a type whose values
can be any one of a set of possible types.
This topic describes how the F# compiler infers the types of values, variables, parameters and return values.
let f a b = a + b + 100
You can influence type inference by changing the literals. If you make the 100 a uint32 by appending the suffix
u , the types of a , b , and the return value are inferred to be uint32 .
You can also influence type inference by using other constructs that imply restrictions on the type, such as
functions and methods that work with only a particular type.
Also, you can apply explicit type annotations to function or method parameters or to variables in expressions, as
shown in the following examples. Errors result if conflicts occur between different constraints.
You can also explicitly specify the return value of a function by providing a type annotation after all the
parameters.
let addu1 x y : uint32 =
x + y
A common case where a type annotation is useful on a parameter is when the parameter is an object type and
you want to use a member.
Automatic Generalization
If the function code is not dependent on the type of a parameter, the compiler considers the parameter to be
generic. This is called automatic generalization, and it can be a powerful aid to writing generic code without
increasing complexity.
For example, the following function combines two parameters of any type into a tuple.
Additional Information
Type inference is described in more detail in the F# Language Specification.
See also
Automatic Generalization
Basic types
3/6/2021 • 2 minutes to read • Edit Online
This topic lists the basic types that are defined in the F# language. These types are the most fundamental in F#,
forming the basis of nearly every F# program. They are a superset of .NET primitive types.
NOTE
You can perform computations with integers too big for the 64-bit integer type by using the bigint type. bigint is
not considered a basic type; it is an abbreviation for System.Numerics.BigInteger .
See also
F# Language Reference
Unit Type
11/1/2019 • 2 minutes to read • Edit Online
The unit type is a type that indicates the absence of a specific value; the unit type has only a single value,
which acts as a placeholder when no other value exists or is needed.
Syntax
// The value of the unit type.
()
Remarks
Every F# expression must evaluate to a value. For expressions that do not generate a value that is of interest, the
value of type unit is used. The unit type resembles the void type in languages such as C# and C++.
The unit type has a single value, and that value is indicated by the token () .
The value of the unit type is often used in F# programming to hold the place where a value is required by the
language syntax, but when no value is needed or desired. An example might be the return value of a printf
function. Because the important actions of the printf operation occur in the function, the function does not
have to return an actual value. Therefore, the return value is of type unit .
Some constructs expect a unit value. For example, a do binding or any code at the top level of a module is
expected to evaluate to a unit value. The compiler reports a warning when a do binding or code at the top
level of a module produces a result other than the unit value that is not used, as shown in the following
example.
let function1 x y = x + y
// The next line results in a compiler warning.
function1 10 20
// Changing the code to one of the following eliminates the warning.
// Use this when you do want the return value.
let result = function1 10 20
// Use this if you are only calling the function for its side effects,
// and do not want the return value.
function1 10 20 |> ignore
This warning is a characteristic of functional programming; it does not appear in other .NET programming
languages. In a purely functional program, in which functions do not have any side effects, the final return value
is the only result of a function call. Therefore, when the result is ignored, it is a possible programming error.
Although F# is not a purely functional programming language, it is a good practice to follow functional
programming style whenever possible.
See also
Primitive
F# Language Reference
Strings
11/2/2020 • 3 minutes to read • Edit Online
Remarks
String literals are delimited by the quotation mark (") character. The backslash character ( \ ) is used to encode
certain special characters. The backslash and the next character together are known as an escape sequence.
Escape sequences supported in F# string literals are shown in the following table.
Alert \a
Backspace \b
Form feed \f
Newline \n
Carriage return \r
Tab \t
Vertical tab \v
Backslash \\
Apostrophe \'
NOTE
Being constrained to a range of 0 - 255 (0xFF), the \DDD and \x escape sequences are effectively the ISO-8859-1
character set, since that matches the first 256 Unicode code points.
Verbatim Strings
If preceded by the @ symbol, the literal is a verbatim string. This means that any escape sequences are ignored,
except that two quotation mark characters are interpreted as one quotation mark character.
In code, strings that have line breaks are accepted and the line breaks are interpreted literally as newlines, unless
a backslash character is the last character before the line break. Leading white space on the next line is ignored
when the backslash character is used. The following code produces a string str1 that has value "abc\ndef"
and a string str2 that has value "abcdef" .
The output is b .
Or you can extract substrings by using array slice syntax, as shown in the following code.
printfn "%s" (str1.[0..2])
printfn "%s" (str2.[3..5])
abc
def
You can represent ASCII strings by arrays of unsigned bytes, type byte[] . You add the suffix B to a string literal
to indicate that it is an ASCII string. ASCII string literals used with byte arrays support the same escape
sequences as Unicode strings, except for the Unicode escape sequences.
String Operators
The + operator can be used to concatenate strings, maintaining compatibility with the .NET Framework string
handling features. The following example illustrates string concatenation.
String Class
Because the string type in F# is actually a .NET Framework System.String type, all the System.String members
are available. This includes the + operator, which is used to concatenate strings, the Length property, and the
Chars property, which returns the string as an array of Unicode characters. For more information about strings,
see System.String .
By using the Chars property of System.String , you can access the individual characters in a string by
specifying an index, as is shown in the following code.
String Module
Additional functionality for string handling is included in the String module in the FSharp.Core namespace.
For more information, see String Module.
See also
F# Language Reference
Interpolated strings
3/6/2021 • 2 minutes to read • Edit Online
Interpolated strings are strings that allow you to embed F# expressions into them. They are helpful in a wide
range of scenarios where the value of a string may change based on the result of a value or expression.
Syntax
$"string-text {expr}"
$"string-text %format-specifier{expr}"
$"""string-text {"embedded string literal"}"""
Remarks
Interpolated strings let you write code in "holes" inside of a string literal. Here's a basic example:
In the previous example, the code mistakenly passes the age value where name should be, and vice/versa.
Because the interpolated strings use format specifiers, this is a compile error instead of a subtle runtime bug.
All format specifiers covered in plaintext formatting are valid inside of an interpolated string.
printfn $"""|{"Left",-7}|{"Right",7}|"""
// |Left | Right|
Additionally, an interpolated string can also be type checked as a FormattableString via a type annotation:
Note that the type annotation must be on the interpolated string expression itself. F# does not implicitly convert
an interpolated string into a FormattableString.
See also
Strings
Tuples
6/8/2021 • 5 minutes to read • Edit Online
A tuple is a grouping of unnamed but ordered values, possibly of different types. Tuples can either be reference
types or structs.
Syntax
(element, ... , element)
struct(element, ... ,element )
Remarks
Each element in the previous syntax can be any valid F# expression.
Examples
Examples of tuples include pairs, triples, and so on, of the same or different types. Some examples are illustrated
in the following code.
(1, 2)
// Triple of strings.
("one", "two", "three")
You can also deconstruct a tuple via pattern matching outside of a match expression via let binding:
// Or as a struct
let struct (c, d) = struct (1, 2)
Or you can pattern match on tuples as inputs to functions:
If you need only one element of the tuple, the wildcard character (the underscore) can be used to avoid creating
a new name for a value that you do not need.
Copying elements from a reference tuple into a struct tuple is also simple:
The functions fst and snd (reference tuples only) return the first and second elements of a tuple, respectively.
There is no built-in function that returns the third element of a triple, but you can easily write one as follows.
Using Tuples
Tuples provide a convenient way to return multiple values from a function, as shown in the following example.
This example performs integer division and returns the rounded result of the operation as a first member of a
tuple pair and the remainder as a second member of the pair.
let divRem a b =
let x = a / b
let y = a % b
(x, y)
Tuples can also be used as function arguments when you want to avoid the implicit currying of function
arguments that is implied by the usual function syntax.
The usual syntax for defining the function let sum a b = a + b enables you to define a function that is the
partial application of the first argument of the function, as shown in the following code.
let sum a b = a + b
Using a tuple as the parameter disables currying. For more information, see "Partial Application of Arguments"
in Functions.
Note that outer parentheses are mandatory when creating a type alias for a struct tuple type.
namespace CSharpTupleInterop
{
public static class Example
{
public static (int, int) AddOneToXAndY((int x, int y) a) =>
(a.x + 1, a.y + 1);
}
}
In your F# code, you can then pass a struct tuple as the parameter and consume the result as a struct tuple.
open TupleInterop
// Won't compile!
let f(t: struct(int*int)): int*int = t
You must pattern match on one tuple and construct the other with the constituent parts. For example:
// Construct a new tuple from the parts you pattern matched on.
let struct (c, d) = struct (a, b)
See also
F# Language Reference
F# Types
F# collection types
11/2/2020 • 15 minutes to read • Edit Online
By reviewing this topic, you can determine which F# collection type best suits a particular need. These collection
types differ from the collection types in .NET, such as those in the System.Collections.Generic namespace, in
that the F# collection types are designed from a functional programming perspective rather than an object-
oriented perspective. More specifically, only the array collection has mutable elements. Therefore, when you
modify a collection, you create an instance of the modified collection instead of altering the original collection.
Collection types also differ in the type of data structure in which objects are stored. Data structures such as hash
tables, linked lists, and arrays have different performance characteristics and a different set of available
operations.
Array2D Module
Array3D Module
Table of functions
This section compares the functions that are available on F# collection types. The computational complexity of
the function is given, where N is the size of the first collection, and M is the size of the second collection, if any. A
dash (-) indicates that this function isn't available on the collection. Because sequences are lazily evaluated, a
function such as Seq.distinct may be O(1) because it returns immediately, although it still affects the
performance of the sequence when enumerated.
F UN C T IO N A RRAY L IST SEQ UEN C E MAP SET DESC RIP T IO N
See also
F# Types
F# Language Reference
Lists
11/2/2020 • 23 minutes to read • Edit Online
A list in F# is an ordered, immutable series of elements of the same type. To perform basic operations on lists,
use the functions in the List module.
let list123 = [ 1; 2; 3 ]
You can also put line breaks between elements, in which case the semicolons are optional. The latter syntax can
result in more readable code when the element initialization expressions are longer, or when you want to
include a comment for each element.
let list123 = [
1
2
3 ]
Normally, all list elements must be the same type. An exception is that a list in which the elements are specified
to be a base type can have elements that are derived types. Thus the following is acceptable, because both
Button and CheckBox derive from Control .
You can also define list elements by using a range indicated by integers separated by the range operator ( .. ),
as shown in the following code.
let list1 = [ 1 .. 10 ]
An empty list is specified by a pair of square brackets with nothing in between them.
// An empty list.
let listEmpty = []
You can also use a sequence expression to create a list. See Sequence Expressions for more information. For
example, the following code creates a list of squares of integers from 1 to 10.
You can concatenate lists that have compatible types by using the @ operator, as in the following code. If list1
is [2; 3; 4] and list2 is [100; 2; 3; 4] , this code creates list3 as [2; 3; 4; 100; 2; 3; 4] .
Functions for performing operations on lists are available in the List module.
Because lists in F# are immutable, any modifying operations generate new lists instead of modifying existing
lists.
Lists in F# are implemented as singly linked lists, which means that operations that access only the head of the
list are O(1), and element access is O(n).
Properties
The list type supports the following properties:
let list1 = [ 1; 2; 3 ]
// Properties
printfn "list1.IsEmpty is %b" (list1.IsEmpty)
printfn "list1.Length is %d" (list1.Length)
printfn "list1.Head is %d" (list1.Head)
printfn "list1.Tail.Head is %d" (list1.Tail.Head)
printfn "list1.Tail.Tail.Head is %d" (list1.Tail.Tail.Head)
printfn "list1.Item(1) is %d" (list1.Item(1))
Using Lists
Programming with lists enables you to perform complex operations with a small amount of code. This section
describes common operations on lists that are important to functional programming.
Recursion with Lists
Lists are uniquely suited to recursive programming techniques. Consider an operation that must be performed
on every element of a list. You can do this recursively by operating on the head of the list and then passing the
tail of the list, which is a smaller list that consists of the original list without the first element, back again to the
next level of recursion.
To write such a recursive function, you use the cons operator ( :: ) in pattern matching, which enables you to
separate the head of a list from the tail.
The following code example shows how to use pattern matching to implement a recursive function that
performs operations on a list.
The previous code works well for small lists, but for larger lists, it could overflow the stack. The following code
improves on this code by using an accumulator argument, a standard technique for working with recursive
functions. The use of the accumulator argument makes the function tail recursive, which saves stack space.
The function RemoveAllMultiples is a recursive function that takes two lists. The first list contains the numbers
whose multiples will be removed, and the second list is the list from which to remove the numbers. The code in
the following example uses this recursive function to eliminate all the non-prime numbers from a list, leaving a
list of prime numbers as the result.
let IsPrimeMultipleTest n x =
x = n || x % n <> 0
let GetPrimesUpTo n =
let max = int (sqrt (float n))
RemoveAllMultiples [ 2 .. max ] [ 1 .. n ]
Primes Up To 100:
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59; 61; 67; 71; 73; 79; 83; 89; 97]
Module Functions
The List module provides functions that access the elements of a list. The head element is the fastest and easiest
to access. Use the property Head or the module function List.head. You can access the tail of a list by using the
Tail property or the List.tail function. To find an element by index, use the List.nth function. List.nth traverses
the list. Therefore, it is O(n). If your code uses List.nth frequently, you might want to consider using an array
instead of a list. Element access in arrays is O(1).
Boolean Operations on Lists
The List.isEmpty function determines whether a list has any elements.
The List.exists function applies a Boolean test to elements of a list and returns true if any element satisfies the
test. List.exists2 is similar but operates on successive pairs of elements in two lists.
The following code demonstrates the use of List.exists .
// Use List.exists to determine whether there is an element of a list satisfies a given Boolean expression.
// containsNumber returns true if any of the elements of the supplied list match
// the supplied number.
let containsNumber number list = List.exists (fun elem -> elem = number) list
let list0to3 = [0 .. 3]
printfn "For list %A, contains zero is %b" list0to3 (containsNumber 0 list0to3)
Lists [1; 2; 3; 4; 5] and [5; 4; 3; 2; 1] have at least one equal element at the same position.
You can use List.forall if you want to test whether all the elements of a list meet a condition.
let isAllZeroes list = List.forall (fun elem -> elem = 0.0) list
printfn "%b" (isAllZeroes [0.0; 0.0])
printfn "%b" (isAllZeroes [0.0; 1.0])
true
false
Similarly, List.forall2 determines whether all elements in the corresponding positions in two lists satisfy a
Boolean expression that involves each pair of elements.
let listEqual list1 list2 = List.forall2 (fun elem1 elem2 -> elem1 = elem2) list1 list2
printfn "%b" (listEqual [0; 1; 2] [0; 1; 2])
printfn "%b" (listEqual [0; 0; 0] [0; 1; 0])
true
false
[-2; 1; 4; 5; 8]
let sortedList2 = List.sortBy (fun elem -> abs elem) [1; 4; 8; -2; 5]
printfn "%A" sortedList2
[1; -2; 4; 5; 8]
The next example demonstrates the use of List.sortWith . In this example, the custom comparison function
compareWidgets is used to first compare one field of a custom type, and then another when the values of the
first field are equal.
type Widget = { ID: int; Rev: int }
let listToCompare = [
{ ID = 92; Rev = 1 }
{ ID = 110; Rev = 1 }
{ ID = 100; Rev = 5 }
{ ID = 100; Rev = 2 }
{ ID = 92; Rev = 1 }
]
[{ID = 92;
Rev = 1;}; {ID = 92;
Rev = 1;}; {ID = 100;
Rev = 2;}; {ID = 100;
Rev = 5;}; {ID = 110;
Rev = 1;}]
The result is 5.
If the elements must be transformed first, call List.pick, which takes a function that returns an option, and looks
for the first option value that is Some(x) . Instead of returning the element, List.pick returns the result x . If no
matching element is found, List.pick throws System.Collections.Generic.KeyNotFoundException . The following
code shows the use of List.pick .
Another group of search operations, List.tryFind and related functions, return an option value. The List.tryFind
function returns the first element of a list that satisfies a condition if such an element exists, but the option value
None if not. The variation List.tryFindIndex returns the index of the element, if one is found, rather than the
element itself. These functions are illustrated in the following code.
let list1d = [1; 3; 7; 9; 11; 13; 15; 19; 22; 29; 36]
let isEven x = x % 2 = 0
match List.tryFind isEven list1d with
| Some value -> printfn "The first even value is %d." value
| None -> printfn "There is no even value in the list."
// Compute the sum of the squares of the elements of a list by using List.sumBy.
let sum2 = List.sumBy (fun elem -> elem*elem) [1 .. 10]
let list1 = [ 1; 2; 3 ]
let list2 = [ -1; -2; -3 ]
let listZip = List.zip list1 list2
printfn "%A" listZip
let list3 = [ 0; 0; 0]
let listZip3 = List.zip3 list1 list2 list3
printfn "%A" listZip3
The corresponding unzip versions, List.unzip and List.unzip3, take lists of tuples and return lists in a tuple, where
the first list contains all the elements that were first in each tuple, and the second list contains the second
element of each tuple, and so on.
The following code example demonstrates the use of List.unzip.
List.iter: element is 1
List.iter: element is 2
List.iter: element is 3
List.iteri: element 0 is 1
List.iteri: element 1 is 2
List.iteri: element 2 is 3
List.iter2: elements are 1 4
List.iter2: elements are 2 5
List.iter2: elements are 3 6
List.iteri2: element 0 of list1 is 1; element 0 of list2 is 4
List.iteri2: element 1 of list1 is 2; element 1 of list2 is 5
List.iteri2: element 2 of list1 is 3; element 2 of list2 is 6
Another frequently used function that transforms list elements is List.map, which enables you to apply a
function to each element of a list and put all the results into a new list. List.map2 and List.map3 are variations
that take multiple lists. You can also use List.mapi and List.mapi2, if, in addition to the element, the function
needs to be passed the index of each element. The only difference between List.mapi2 and List.mapi is that
List.mapi2 works with two lists. The following example illustrates List.map.
[2; 3; 4]
[5; 7; 9]
[1; 3; 5]
[0; 7; 18]
List.collect is like List.map , except that each element produces a list and all these lists are concatenated into a
final list. In the following code, each element of the list generates three numbers. These are all collected into one
list.
let collectList = List.collect (fun x -> [for i in 1..3 -> x * i]) list1
printfn "%A" collectList
[1; 2; 3; 2; 4; 6; 3; 6; 9]
You can also use List.filter, which takes a Boolean condition and produces a new list that consists only of
elements that satisfy the given condition.
["Rome's"; "Bob's"]
testList [1; 1; 1]
testList [1; 2; 1]
testList [1; 2; 3]
The versions of these functions that have a digit in the function name operate on more than one list. For
example, List.fold2 performs computations on two lists.
The following example demonstrates the use of List.fold2 .
// Use List.fold2 to perform computations over two lists (of equal size) at the same time.
// Example: Sum the greater element at each list position.
let sumGreatest list1 list2 = List.fold2 (fun acc elem1 elem2 ->
acc + max elem1 elem2) 0 list1 list2
List.fold and List.scan differ in that List.fold returns the final value of the extra parameter, but List.scan
returns the list of the intermediate values (along with the final value) of the extra parameter.
Each of these functions includes a reverse variation, for example, List.foldBack, which differs in the order in
which the list is traversed and the order of the arguments. Also, List.fold and List.foldBack have variations,
List.fold2 and List.foldBack2, that take two lists of equal length. The function that executes on each element can
use corresponding elements of both lists to perform some action. The element types of the two lists can be
different, as in the following example, in which one list contains transaction amounts for a bank account, and the
other list contains the type of transaction: deposit or withdrawal.
// Discriminated union type that encodes the transaction type.
type Transaction =
| Deposit
| Withdrawal
// Use fold2 to perform a calculation on the list to update the account balance.
let endingBalance = List.fold2 (fun acc elem1 elem2 ->
match elem1 with
| Deposit -> acc + elem2
| Withdrawal -> acc - elem2)
initialBalance
transactionTypes
transactionAmounts
printfn "%f" endingBalance
For a calculation like summation, List.fold and List.foldBack have the same effect because the result does
not depend on the order of traversal. In the following example, List.foldBack is used to add the elements in a
list.
let sumListBack list = List.foldBack (fun elem acc -> acc + elem) list 0
printfn "%d" (sumListBack [1; 2; 3])
// For a calculation in which the order of traversal is important, fold and foldBack have different
// results. For example, replacing fold with foldBack in the listReverse function
// produces a function that copies the list, rather than reversing it.
let copyList list = List.foldBack (fun elem acc -> elem::acc) list []
printfn "%A" (copyList [1 .. 10])
The following example returns to the bank account example. This time a new transaction type is added: an
interest calculation. The ending balance now depends on the order of transactions.
type Transaction2 =
| Deposit
| Withdrawal
| Interest
The function List.reduce is somewhat like List.fold and List.scan , except that instead of passing around a
separate accumulator, List.reduce takes a function that takes two arguments of the element type instead of just
one, and one of those arguments acts as the accumulator, meaning that it stores the intermediate result of the
computation. List.reduce starts by operating on the first two list elements, and then uses the result of the
operation along with the next element. Because there is not a separate accumulator that has its own type,
List.reduce can be used in place of List.fold only when the accumulator and the element type have the same
type. The following code demonstrates the use of List.reduce . List.reduce throws an exception if the list
provided has no elements.
In the following code, the first call to the lambda expression is given the arguments 2 and 4, and returns 6, and
the next call is given the arguments 6 and 10, so the result is 16.
See also
F# Language Reference
F# Types
Sequences
Arrays
Options
Arrays
3/6/2021 • 17 minutes to read • Edit Online
Arrays are fixed-size, zero-based, mutable collections of consecutive data elements that are all of the same type.
Create arrays
You can create arrays in several ways. You can create a small array by listing consecutive values between [|
and |] and separated by semicolons, as shown in the following examples.
let array1 = [| 1; 2; 3 |]
You can also put each element on a separate line, in which case the semicolon separator is optional.
let array1 =
[|
1
2
3
|]
The type of the array elements is inferred from the literals used and must be consistent. The following code
causes an error because 1.0 is a float and 2 and 3 are integers.
// Causes an error.
// let array2 = [| 1.0; 2; 3 |]
You can also use sequence expressions to create arrays. Following is an example that creates an array of squares
of integers from 1 to 10.
To create an array in which all the elements are initialized to zero, use Array.zeroCreate .
Access elements
You can access array elements by using a dot operator ( . ) and brackets ( [ and ] ).
array1.[0]
array1.[0..2]
array1.[..2]
array1.[2..]
0 1 2 3 4 5 6 7 8 9
printfn "Array of squares: %A" (Array.init 10 (fun index -> index * index))
Array.copy creates a new array that contains elements that are copied from an existing array. Note that the
copy is a shallow copy, which means that if the element type is a reference type, only the reference is copied, not
the underlying object. The following code example illustrates this.
open System.Text
let firstArray : StringBuilder array = Array.init 3 (fun index -> new StringBuilder(""))
let secondArray = Array.copy firstArray
// Reset an element of the first array to a new value.
firstArray.[0] <- new StringBuilder("Test1")
// Change an element of the first array.
firstArray.[1].Insert(0, "Test2") |> ignore
printfn "%A" firstArray
printfn "%A" secondArray
[|Test1; Test2; |]
[|; Test2; |]
The string Test1 appears only in the first array because the operation of creating a new element overwrites the
reference in firstArray but does not affect the original reference to an empty string that is still present in
secondArray . The string Test2 appears in both arrays because the Insert operation on the
System.Text.StringBuilder type affects the underlying System.Text.StringBuilder object, which is referenced in
both arrays.
Array.sub generates a new array from a subrange of an array. You specify the subrange by providing the
starting index and the length. The following code demonstrates the use of Array.sub .
let a1 = [| 0 .. 99 |]
let a2 = Array.sub a1 5 10
printfn "%A" a2
The output shows that the subarray starts at element 5 and contains 10 elements.
[|1; 2; 3; 4; 5; 6|]
Array.choose selects elements of an array to include in a new array. The following code demonstrates
Array.choose . Note that the element type of the array does not have to match the type of the value returned in
the option type. In this example, the element type is int and the option is the result of a polynomial function,
elem*elem - 1 , as a floating point number.
Array.collect runs a specified function on each array element of an existing array and then collects the
elements generated by the function and combines them into a new array. The following code demonstrates
Array.collect .
[|0; 1; 0; 1; 2; 3; 4; 5; 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]
Array.concat takes a sequence of arrays and combines them into a single array. The following code
demonstrates Array.concat .
[|(1, 1, 1); (1, 2, 2); (1, 3, 3); (2, 1, 2); (2, 2, 4); (2, 3, 6); (3, 1, 3);
(3, 2, 6); (3, 3, 9)|]
Array.filter takes a Boolean condition function and generates a new array that contains only those elements
from the input array for which the condition is true. The following code demonstrates Array.filter .
[|2; 4; 6; 8; 10|]
Array.rev generates a new array by reversing the order of an existing array. The following code demonstrates
Array.rev .
let stringReverse (s: string) =
System.String(Array.rev (s.ToCharArray()))
"Hello world!"
You can easily combine functions in the array module that transform arrays by using the pipeline operator ( |> ),
as shown in the following example.
[| 1 .. 10 |]
|> Array.filter (fun elem -> elem % 2 = 0)
|> Array.choose (fun elem -> if (elem <> 8) then Some(elem*elem) else None)
|> Array.rev
|> printfn "%A"
The output is
Multidimensional arrays
A multidimensional array can be created, but there is no syntax for writing a multidimensional array literal. Use
the operator array2D to create an array from a sequence of sequences of array elements. The sequences can be
array or list literals. For example, the following code creates a two-dimensional array.
You can also use the function Array2D.init to initialize arrays of two dimensions, and similar functions are
available for arrays of three and four dimensions. These functions take a function that is used to create the
elements. To create a two-dimensional array that contains elements set to an initial value instead of specifying a
function, use the Array2D.create function, which is also available for arrays up to four dimensions. The
following code example first shows how to create an array of arrays that contain the desired elements, and then
uses Array2D.init to generate the desired two-dimensional array.
Array indexing and slicing syntax is supported for arrays up to rank 4. When you specify an index in multiple
dimensions, you use commas to separate the indexes, as illustrated in the following code example.
The type of a two-dimensional array is written out as <type>[,] (for example, int[,] , double[,] ), and the type
of a three-dimensional array is written as <type>[,,] , and so on for arrays of higher dimensions.
Only a subset of the functions available for one-dimensional arrays is also available for multidimensional arrays.
Array slicing and multidimensional arrays
In a two-dimensional array (a matrix), you can extract a sub-matrix by specifying ranges and using a wildcard (
* ) character to specify whole rows or columns.
You can decompose a multidimensional array into subarrays of the same or lower dimension. For example, you
can obtain a vector from a matrix by specifying a single row or column.
You can use this slicing syntax for types that implement the element access operators and overloaded GetSlice
methods. For example, the following code creates a Matrix type that wraps the F# 2D array, implements an Item
property to provide support for array indexing, and implements three versions of GetSlice . If you can use this
code as a template for your matrix types, you can use all the slicing operations that this section describes.
type Matrix<'T>(N: int, M: int) =
let internalArray = Array2D.zeroCreate<'T> N M
member this.Item
with get(a: int, b: int) = internalArray.[a, b]
and set(a: int, b: int) (value:'T) = internalArray.[a, b] <- value
member this.GetSlice(rowStart: int option, rowFinish : int option, colStart: int option, colFinish : int
option) =
let rowStart =
match rowStart with
| Some(v) -> v
| None -> 0
let rowFinish =
match rowFinish with
| Some(v) -> v
| None -> internalArray.GetLength(0) - 1
let colStart =
match colStart with
| Some(v) -> v
| None -> 0
let colFinish =
match colFinish with
| Some(v) -> v
| None -> internalArray.GetLength(1) - 1
internalArray.[rowStart..rowFinish, colStart..colFinish]
module test =
let generateTestMatrix x y =
let matrix = new Matrix<float>(3, 3)
for i in 0..2 do
for j in 0..2 do
matrix.[i, j] <- float(i) * x - float(j) * y
matrix
let allNegative = Array.exists (fun elem -> abs (elem) = elem) >> not
printfn "%A" (allNegative [| -1; -2; -3 |])
printfn "%A" (allNegative [| -10; -1; 5 |])
printfn "%A" (allNegative [| 0 |])
true
false
false
true
Similarly, the function Array.forall tests an array to determine whether every element satisfies a Boolean
condition. The variation Array.forall2 does the same thing by using a Boolean function that involves elements
of two arrays of equal length. The following code illustrates the use of these functions.
false
true
true
false
Search arrays
Array.find takes a Boolean function and returns the first element for which the function returns true , or
raises a System.Collections.Generic.KeyNotFoundException if no element that satisfies the condition is found.
Array.findIndex is like Array.find , except that it returns the index of the element instead of the element itself.
The following code uses Array.find and Array.findIndex to locate a number that is both a perfect square and
perfect cube.
let arrayA = [| 2 .. 100 |]
let delta = 1.0e-10
let isPerfectSquare (x:int) =
let y = sqrt (float x)
abs(y - round y) < delta
let isPerfectCube (x:int) =
let y = System.Math.Pow(float x, 1.0/3.0)
abs(y - round y) < delta
let element = Array.find (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
let index = Array.findIndex (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
printfn "The first element that is both a square and a cube is %d and its index is %d." element index
The first element that is both a square and a cube is 64 and its index is 62.
Array.tryFind is like Array.find, except that its result is an option type, and it returns None if no element is
found. Array.tryFind should be used instead of Array.find when you do not know whether a matching
element is in the array. Similarly, Array.tryFindIndex is like Array.findIndex except that the option type is the
return value. If no element is found, the option is None .
The following code demonstrates the use of Array.tryFind . This code depends on the previous code.
lookForCubeAndSquare [| 1 .. 10 |]
lookForCubeAndSquare [| 100 .. 1000 |]
lookForCubeAndSquare [| 2 .. 50 |]
Found an element: 1
Found an element: 729
Failed to find a matching element.
Use Array.tryPick when you need to transform an element in addition to finding it. The result is the first
element for which the function returns the transformed element as an option value, or None if no such element
is found.
The following code shows the use of Array.tryPick . In this case, instead of a lambda expression, several local
helper functions are defined to simplify the code.
let findPerfectSquareAndCube array1 =
let delta = 1.0e-10
let isPerfectSquare (x:int) =
let y = sqrt (float x)
abs(y - round y) < delta
let isPerfectCube (x:int) =
let y = System.Math.Pow(float x, 1.0/3.0)
abs(y - round y) < delta
// intFunction : (float -> float) -> int -> int
// Allows the use of a floating point function with integers.
let intFunction function1 number = int (round (function1 (float number)))
let cubeRoot x = System.Math.Pow(x, 1.0/3.0)
// testElement: int -> (int * int * int) option
// Test an element to see whether it is a perfect square and a perfect
// cube, and, if so, return the element, square root, and cube root
// as an option value. Otherwise, return None.
let testElement elem =
if isPerfectSquare elem && isPerfectCube elem then
Some(elem, intFunction sqrt elem, intFunction cubeRoot elem)
else None
match Array.tryPick testElement array1 with
| Some (n, sqrt, cuberoot) -> printfn "Found an element %d with square root %d and cube root %d." n sqrt
cuberoot
| None -> printfn "Did not find an element that is both a perfect square and a perfect cube."
findPerfectSquareAndCube [| 1 .. 10 |]
findPerfectSquareAndCube [| 2 .. 100 |]
findPerfectSquareAndCube [| 100 .. 1000 |]
findPerfectSquareAndCube [| 1000 .. 10000 |]
findPerfectSquareAndCube [| 2 .. 50 |]
These functions for performing computations correspond to the functions of the same name in the List module.
For usage examples, see Lists.
Modify arrays
Array.set sets an element to a specified value. Array.fill sets a range of elements in an array to a specified
value. The following code provides an example of Array.fill .
let arrayFill1 = [| 1 .. 25 |]
Array.fill arrayFill1 2 20 0
printfn "%A" arrayFill1
You can use Array.blit to copy a subsection of one array to another array.
Convert to and from other types
Array.ofList creates an array from a list. Array.ofSeq creates an array from a sequence. Array.toList and
Array.toSeq convert to these other collection types from the array type.
Sort arrays
Use Array.sort to sort an array by using the generic comparison function. Use Array.sortBy to specify a
function that generates a value, referred to as a key, to sort by using the generic comparison function on the key.
Use Array.sortWith if you want to provide a custom comparison function. Array.sort , Array.sortBy , and
Array.sortWith all return the sorted array as a new array. The variations Array.sortInPlace ,
Array.sortInPlaceBy , and Array.sortInPlaceWith modify the existing array instead of returning a new one.
See also
F# Language Reference
F# Types
Sequences
11/2/2020 • 19 minutes to read • Edit Online
A sequence is a logical series of elements all of one type. Sequences are particularly useful when you have a
large, ordered collection of data but do not necessarily expect to use all of the elements. Individual sequence
elements are computed only as required, so a sequence can provide better performance than a list in situations
in which not all the elements are used. Sequences are represented by the seq<'T> type, which is an alias for
IEnumerable<T>. Therefore, any .NET type that implements IEnumerable<T> interface can be used as a
sequence. The Seq module provides support for manipulations involving sequences.
Sequence Expressions
A sequence expression is an expression that evaluates to a sequence. Sequence expressions can take a number
of forms. The simplest form specifies a range. For example, seq { 1 .. 5 } creates a sequence that contains five
elements, including the endpoints 1 and 5. You can also specify an increment (or decrement) between two
double periods. For example, the following code creates the sequence of multiples of 10.
Sequence expressions are made up of F# expressions that produce values of the sequence. You can also
generate values programmatically:
The previous sample uses the -> operator, which allows you to specify an expression whose value will become
a part of the sequence. You can only use -> if every part of the code that follows it returns a value.
Alternatively, you can specify the do keyword, with an optional yield that follows:
The following code generates a list of coordinate pairs along with an index into an array that represents the grid.
Note that the first for expression requires a do to be specified.
seq {
for row in 0 .. width - 1 do
for col in 0 .. height - 1 ->
(row, col, row*width + col)
}
An if expression used in a sequence is a filter. For example, to generate a sequence of only prime numbers,
assuming that you have a function isprime of type int -> bool , construct the sequence as follows.
seq { for n in 1 .. 100 do if isprime n then n }
As mentioned previously, do is required here because there is no else branch that goes with the if . If you
try to use -> , you'll get an error saying that not all branches return a value.
Another way of thinking of yield! is that it flattens an inner sequence and then includes that in the containing
sequence.
When yield! is used in an expression, all other single values must use the yield keyword:
The previous example will produce the value of x in addition to all values from 1 to x for each x .
Examples
The first example uses a sequence expression that contains an iteration, a filter, and a yield to generate an array.
This code prints a sequence of prime numbers between 1 and 100 to the console.
let aSequence =
seq {
for n in 1..100 do
if isprime n then
n
}
for x in aSequence do
printfn "%d" x
The following example creates a multiplication table that consists of tuples of three elements, each consisting of
two factors and the product:
let multiplicationTable =
seq {
for i in 1..9 do
for j in 1..9 ->
(i, j, i*j)
}
The following example demonstrates the use of yield! to combine individual sequences into a single final
sequence. In this case, the sequences for each subtree in a binary tree are concatenated in a recursive function to
produce the final sequence.
Using Sequences
Sequences support many of the same functions as lists. Sequences also support operations such as grouping
and counting by using key-generating functions. Sequences also support more diverse functions for extracting
subsequences.
Many data types, such as lists, arrays, sets, and maps are implicitly sequences because they are enumerable
collections. A function that takes a sequence as an argument works with any of the common F# data types, in
addition to any .NET data type that implements System.Collections.Generic.IEnumerable<'T> . Contrast this to a
function that takes a list as an argument, which can only take lists. The type seq<'T> is a type abbreviation for
IEnumerable<'T> . This means that any type that implements the generic
System.Collections.Generic.IEnumerable<'T> , which includes arrays, lists, sets, and maps in F#, and also most
.NET collection types, is compatible with the seq type and can be used wherever a sequence is expected.
Module Functions
The Seq module in the FSharp.Collections namespace contains functions for working with sequences. These
functions work with lists, arrays, maps, and sets as well, because all of those types are enumerable, and
therefore can be treated as sequences.
Creating Sequences
You can create sequences by using sequence expressions, as described previously, or by using certain functions.
You can create an empty sequence by using Seq.empty, or you can create a sequence of just one specified
element by using Seq.singleton.
let seqEmpty = Seq.empty
let seqOne = Seq.singleton 10
You can use Seq.init to create a sequence for which the elements are created by using a function that you
provide. You also provide a size for the sequence. This function is just like List.init, except that the elements are
not created until you iterate through the sequence. The following code illustrates the use of Seq.init .
The output is
0 10 20 30 40
By using Seq.ofArray and Seq.ofList<'T> Function, you can create sequences from arrays and lists. However, you
can also convert arrays and lists to sequences by using a cast operator. Both techniques are shown in the
following code.
By using Seq.cast, you can create a sequence from a weakly typed collection, such as those defined in
System.Collections . Such weakly typed collections have the element type System.Object and are enumerated
by using the non-generic System.Collections.Generic.IEnumerable`1 type. The following code illustrates the
use of Seq.cast to convert an System.Collections.ArrayList into a sequence.
open System
for i in 1 .. 10 do
arr.Add(10)
You can define infinite sequences by using the Seq.initInfinite function. For such a sequence, you provide a
function that generates each element from the index of the element. Infinite sequences are possible because of
lazy evaluation; elements are created as needed by calling the function that you specify. The following code
example produces an infinite sequence of floating point numbers, in this case the alternating series of
reciprocals of squares of successive integers.
let seqInfinite =
Seq.initInfinite (fun index ->
let n = float (index + 1)
1.0 / (n * n * (if ((index + 1) % 2 = 0) then 1.0 else -1.0)))
Seq.unfold generates a sequence from a computation function that takes a state and transforms it to produce
each subsequent element in the sequence. The state is just a value that is used to compute each element, and
can change as each element is computed. The second argument to Seq.unfold is the initial value that is used to
start the sequence. Seq.unfold uses an option type for the state, which enables you to terminate the sequence
by returning the None value. The following code shows two examples of sequences, seq1 and fib , that are
generated by an unfold operation. The first, seq1 , is just a simple sequence with numbers up to 20. The
second, fib , uses unfold to compute the Fibonacci sequence. Because each element in the Fibonacci sequence
is the sum of the previous two Fibonacci numbers, the state value is a tuple that consists of the previous two
numbers in the sequence. The initial value is (1,1) , the first two numbers in the sequence.
let seq1 =
0 // Initial state
|> Seq.unfold (fun state ->
if (state > 20) then
None
else
Some(state, state + 1))
for x in seq1 do
printf "%d " x
let fib =
(1, 1) // Initial state
|> Seq.unfold (fun state ->
if (snd state > 1000) then
None
else
Some(fst state + snd state, (snd state, fst state + snd state)))
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
The following code is an example that uses many of the sequence module functions described here to generate
and compute the values of infinite sequences. The code might take a few minutes to run.
// generateInfiniteSequence generates sequences of floating point
// numbers. The sequences generated are computed from the fDenominator
// function, which has the type (int -> float) and computes the
// denominator of each term in the sequence from the index of that
// term. The isAlternating parameter is true if the sequence has
// alternating signs.
let generateInfiniteSequence fDenominator isAlternating =
if (isAlternating) then
Seq.initInfinite (fun index ->
1.0 /(fDenominator index) * (if (index % 2 = 0) then -1.0 else 1.0))
else
Seq.initInfinite (fun index -> 1.0 /(fDenominator index))
// Compute the sums for three sequences that converge, and compare
// the sums to the expected theoretical values.
let result1 = infiniteSum harmonicAlternatingSeries 0.00001 100000
printfn "Result: %f ln2: %f" result1 (log 2.0)
let pi = Math.PI
let result2 = infiniteSum oddNumberSeries 0.00001 10000
printfn "Result: %f pi/4: %f" result2 (pi/4.0)
Obtaining Subsequences
Seq.filter and Seq.choose are like the corresponding functions that are available for lists, except that the filtering
and choosing does not occur until the sequence elements are evaluated.
Seq.truncate creates a sequence from another sequence, but limits the sequence to a specified number of
elements. Seq.take creates a new sequence that contains only a specified number of elements from the start of a
sequence. If there are fewer elements in the sequence than you specify to take, Seq.take throws a
System.InvalidOperationException . The difference between Seq.take and Seq.truncate is that Seq.truncate
does not produce an error if the number of elements is fewer than the number you specify.
The following code shows the behavior of and differences between Seq.truncate and Seq.take .
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
By using Seq.takeWhile, you can specify a predicate function (a Boolean function) and create a sequence from
another sequence made up of those elements of the original sequence for which the predicate is true , but stop
before the first element for which the predicate returns false . Seq.skip returns a sequence that skips a
specified number of the first elements of another sequence and returns the remaining elements. Seq.skipWhile
returns a sequence that skips the first elements of another sequence as long as the predicate returns true , and
then returns the remaining elements, starting with the first element for which the predicate returns false .
The following code example illustrates the behavior of and differences between Seq.takeWhile , Seq.skip , and
Seq.skipWhile .
// takeWhile
let mySeqLessThan10 = Seq.takeWhile (fun elem -> elem < 10) mySeq
mySeqLessThan10 |> printSeq
// skip
let mySeqSkipFirst5 = Seq.skip 5 mySeq
mySeqSkipFirst5 |> printSeq
// skipWhile
let mySeqSkipWhileLessThan10 = Seq.skipWhile (fun elem -> elem < 10) mySeq
mySeqSkipWhileLessThan10 |> printSeq
1 4 9
36 49 64 81 100
16 25 36 49 64 81 100
Transforming Sequences
Seq.pairwise creates a new sequence in which successive elements of the input sequence are grouped into
tuples.
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
let seqPairwise = Seq.pairwise (seq { for i in 1 .. 10 -> i*i })
printSeq seqPairwise
printfn ""
let seqDelta = Seq.map (fun elem -> snd elem - fst elem) seqPairwise
printSeq seqDelta
Seq.windowed is like Seq.pairwise , except that instead of producing a sequence of tuples, it produces a
sequence of arrays that contain copies of adjacent elements (a window ) from the sequence. You specify the
number of adjacent elements you want in each array.
The following code example demonstrates the use of Seq.windowed . In this case the number of elements in the
window is 3. The example uses printSeq , which is defined in the previous code example.
let seqNumbers = [ 1.0; 1.5; 2.0; 1.5; 1.0; 1.5 ] :> seq<float>
let seqWindows = Seq.windowed 3 seqNumbers
let seqMovingAverage = Seq.map Array.average seqWindows
printfn "Initial sequence: "
printSeq seqNumbers
printfn "\nWindows of length 3: "
printSeq seqWindows
printfn "\nMoving average: "
printSeq seqMovingAverage
Windows of length 3:
[|1.0; 1.5; 2.0|] [|1.5; 2.0; 1.5|] [|2.0; 1.5; 1.0|] [|1.5; 1.0; 1.5|]
Moving average:
1.5 1.666666667 1.5 1.333333333
In the previous code, only the first element is computed and examined, and the result is -1.
Seq.countBy takes a function that generates a value called a key for each element. A key is generated for each
element by calling this function on each element. Seq.countBy then returns a sequence that contains the key
values, and a count of the number of elements that generated each value of the key.
let mySeq1 = seq { 1.. 100 }
let seqResult =
mySeq1
|> Seq.countBy (fun elem ->
if elem % 3 = 0 then 0
elif elem % 3 = 1 then 1
else 2)
printSeq seqResult
The previous output shows that there were 34 elements of the original sequence that produced the key 1, 33
values that produced the key 2, and 33 values that produced the key 0.
You can group elements of a sequence by calling Seq.groupBy. Seq.groupBy takes a sequence and a function
that generates a key from an element. The function is executed on each element of the sequence. Seq.groupBy
returns a sequence of tuples, where the first element of each tuple is the key and the second is a sequence of
elements that produce that key.
The following code example shows the use of Seq.groupBy to partition the sequence of numbers from 1 to 100
into three groups that have the distinct key values 0, 1, and 2.
let sequences3 =
sequences
|> Seq.groupBy (fun index ->
if (index % 3 = 0) then 0
elif (index % 3 = 1) then 1
else 2)
(1, seq [1; 4; 7; 10; ...]) (2, seq [2; 5; 8; 11; ...]) (0, seq [3; 6; 9; 12; ...])
You can create a sequence that eliminates duplicate elements by calling Seq.distinct. Or you can use
Seq.distinctBy, which takes a key-generating function to be called on each element. The resulting sequence
contains elements of the original sequence that have unique keys; later elements that produce a duplicate key to
an earlier element are discarded.
The following code example illustrates the use of Seq.distinct . Seq.distinct is demonstrated by generating
sequences that represent binary numbers, and then showing that the only distinct elements are 0 and 1.
let binary n =
let rec generateBinary n =
if (n / 2 = 0) then [n]
else (n % 2) :: generateBinary (n / 2)
generateBinary n
|> List.rev
|> Seq.ofList
The following code demonstrates Seq.distinctBy by starting with a sequence that contains negative and
positive numbers and using the absolute value function as the key-generating function. The resulting sequence
is missing all the positive numbers that correspond to the negative numbers in the sequence, because the
negative numbers appear earlier in the sequence and therefore are selected instead of the positive numbers that
have the same absolute value, or key.
let inputSequence = { -5 .. 10 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
Seq.cache creates a stored version of a sequence. Use Seq.cache to avoid reevaluation of a sequence, or when
you have multiple threads that use a sequence, but you must make sure that each element is acted upon only
one time. When you have a sequence that is being used by multiple threads, you can have one thread that
enumerates and computes the values for the original sequence, and remaining threads can use the cached
sequence.
Performing Computations on Sequences
Simple arithmetic operations are like those of lists, such as Seq.average, Seq.sum, Seq.averageBy, Seq.sumBy,
and so on.
Seq.fold, Seq.reduce, and Seq.scan are like the corresponding functions that are available for lists. Sequences
support a subset of the full variations of these functions that lists support. For more information and examples,
see Lists.
See also
F# Language Reference
F# Types
Slices
3/6/2021 • 5 minutes to read • Edit Online
This article explains how to take slices from existing F# types and how to define your own slices.
In F#, a slice is a subset of any data type. Slices are similar to indexers, but instead of yielding a single value from
the underlying data structure, they yield multiple ones. Slices use the .. operator syntax to select the range of
specified indices in a data type. For more information, see the looping expression reference article.
F# currently has intrinsic support for slicing strings, lists, arrays, and multidimensional (2D, 3D, 4D) arrays.
Slicing is most commonly used with F# arrays and lists. You can add slicing to your custom data types by using
the GetSlice method in your type definition or in an in-scope type extension.
open System
let sp = [| 1; 2; 3; 4; 5 |].AsSpan()
printSpan sp.[0..] // [|1; 2; 3; 4; 5|]
printSpan sp.[..5] // [|1; 2; 3; 4; 5|]
printSpan sp.[0..3] // [|1; 2; 3|]
printSpan sp.[1..3] // |2; 3|]
let l = [ 1..10 ]
let a = [| 1..10 |]
let s = "hello!"
IMPORTANT
C# developers may expect these to throw an exception rather than produce an empty slice. This is a design decision
rooted in the fact that empty collections compose in F#. An empty F# list can be composed with another F# list, an empty
string can be added to an existing string, and so on. It can be common to take slices based on values passed in as
parameters, and being tolerant of out-of-bounds > by producing an empty collection fits with the compositional nature of
F# code.
Fixed-index slices for 3D and 4D arrays
For F# 3D and 4D arrays, you can "fix" a particular index and slice other dimensions with that index fixed.
To illustrate this, consider the following 3D array:
z=0
X\ Y 0 1
0 0 1
1 2 3
z=1
X\ Y 0 1
0 4 5
1 6 7
If you want to extract the slice [| 4; 5 |] from the array, use a fixed-index slice.
let dim = 2
let m = Array3D.zeroCreate<int> dim dim dim
for z in 0..dim-1 do
for y in 0..dim-1 do
for x in 0..dim-1 do
m.[x,y,z] <- count
count <- count + 1
The last line fixes the y and z indices of the 3D array and takes the rest of the x values that correspond to
the matrix.
See also
Indexed properties
Options
11/2/2020 • 4 minutes to read • Edit Online
The option type in F# is used when an actual value might not exist for a named value or variable. An option has
an underlying type and can hold a value of that type, or it might not have a value.
Remarks
The following code illustrates a function which generates an option type.
As you can see, if the input a is greater than 0, Some(a) is generated. Otherwise, None is generated.
The value None is used when an option does not have an actual value. Otherwise, the expression Some( ... )
gives the option a value. The values Some and None are useful in pattern matching, as in the following function
exists , which returns true if the option has a value and false if it does not.
Using Options
Options are commonly used when a search does not return a matching result, as shown in the following code.
In the previous code, a list is searched recursively. The function tryFindMatch takes a predicate function pred
that returns a Boolean value, and a list to search. If an element that satisfies the predicate is found, the recursion
ends and the function returns the value as an option in the expression Some(head) . The recursion ends when the
empty list is matched. At that point the value head has not been found, and None is returned.
Many F# library functions that search a collection for a value that may or may not exist return the option type.
By convention, these functions begin with the try prefix, for example, Seq.tryFindIndex .
Options can also be useful when a value might not exist, for example if it is possible that an exception will be
thrown when you try to construct a value. The following code example illustrates this.
open System.IO
let openFile filename =
try
let file = File.Open (filename, FileMode.Create)
Some(file)
with
| ex -> eprintf "An exception occurred with message %s" ex.Message
None
The openFile function in the previous example has type string -> File option because it returns a File
object if the file opens successfully and None if an exception occurs. Depending on the situation, it may not be
an appropriate design choice to catch an exception rather than allowing it to propagate.
Additionally, it is still possible to pass null or a value that is null to the Some case of an option. This is generally
to be avoided, and typically is in routine F# programming, but is possible due to the nature of reference types in
.NET.
Option Module
There is a module, Option, that contains useful functions that perform operations on options. Some functions
repeat the functionality of the properties but are useful in contexts where a function is needed. Option.isSome
and Option.isNone are both module functions that test whether an option holds a value. Option.get obtains the
value, if there is one. If there is no value, it throws System.ArgumentException .
The Option.bind function executes a function on the value, if there is a value. The function must take exactly one
argument, and its parameter type must be the option type. The return value of the function is another option
type.
The option module also includes functions that correspond to the functions that are available for lists, arrays,
sequences, and other collection types. These functions include Option.map , Option.iter , Option.forall ,
Option.exists , Option.foldBack , Option.fold , and Option.count . These functions enable options to be used
like a collection of zero or one elements. For more information and examples, see the discussion of collection
functions in Lists.
See also
F# Language Reference
F# Types
Value Options
12/5/2019 • 2 minutes to read • Edit Online
The Value Option type in F# is used when the following two circumstances hold:
1. A scenario is appropriate for an F# Option.
2. Using a struct provides a performance benefit in your scenario.
Not all performance-sensitive scenarios are "solved" by using structs. You must consider the additional cost of
copying when using them instead of reference types. However, large F# programs commonly instantiate many
optional types that flow through hot paths, and in such cases, structs can often yield better overall performance
over the lifetime of a program.
Definition
Value Option is defined as a struct discriminated union that is similar to the reference option type. Its definition
can be thought of this way:
[<StructuralEquality; StructuralComparison>]
[<Struct>]
type ValueOption<'T> =
| ValueNone
| ValueSome of 'T
Value Option conforms to structural equality and comparison. The main difference is that the compiled name,
type name, and case names all indicate that it is a value type.
As with Options, the naming convention for a function that returns ValueOption is to prefix it with try .
This acts just like defaultArg in the Option module, but operates on a Value Option instead.
See also
Options
Results
3/6/2021 • 2 minutes to read • Edit Online
The Result<'T,'TFailure> type lets you write error-tolerant code that can be composed.
Syntax
// The definition of Result in FSharp.Core
[<StructuralEquality; StructuralComparison>]
[<CompiledName("FSharpResult`2")>]
[<Struct>]
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
Remarks
See the Result module for the built-in combinators for the Result . type.
Note that the result type is a struct discriminated union. Structural equality semantics apply here.
The Result type is typically used in monadic error-handling, which is often referred to as Railway-oriented
Programming within the F# community. The following trivial example demonstrates this approach.
// Define a simple type which has fields that can be validated
type Request =
{ Name: string
Email: string }
let test() =
// Now, create a Request and pattern match on the result.
let req1 = { Name = "Phillip"; Email = "[email protected]" }
let res1 = validateRequest (Ok req1)
match res1 with
| Ok req -> printfn $"My request was valid! Name: {req.Name} Email {req.Email}"
| Error e -> printfn $"Error: {e}"
// Prints: "My request was valid! Name: Phillip Email: [email protected]"
test()
As you can see, it's quite easy to chain together various validation functions if you force them all to return a
Result . This lets you break up functionality like this into small pieces which are as composable as you need
them to be. This also has the added value of enforcing the use of pattern matching at the end of a round of
validation, which in turns enforces a higher degree of program correctness.
See also
Discriminated Unions
Pattern Matching
Generics
8/20/2019 • 6 minutes to read • Edit Online
F# function values, methods, properties, and aggregate types such as classes, records, and discriminated unions
can be generic. Generic constructs contain at least one type parameter, which is usually supplied by the user of
the generic construct. Generic functions and types enable you to write code that works with a variety of types
without repeating the code for each type. Making your code generic can be simple in F#, because often your
code is implicitly inferred to be generic by the compiler's type inference and automatic generalization
mechanisms.
Syntax
// Explicitly generic function.
let function-name<type-parameters> parameter-list =
function-body
Remarks
The declaration of an explicitly generic function or type is much like that of a non-generic function or type,
except for the specification (and use) of the type parameters, in angle brackets after the function or type name.
Declarations are often implicitly generic. If you do not fully specify the type of every parameter that is used to
compose a function or type, the compiler attempts to infer the type of each parameter, value, and variable from
the code you write. For more information, see Type Inference. If the code for your type or function does not
otherwise constrain the types of parameters, the function or type is implicitly generic. This process is named
automatic generalization. There are some limits on automatic generalization. For example, if the F# compiler is
unable to infer the types for a generic construct, the compiler reports an error that refers to a restriction called
the value restriction. In that case, you may have to add some type annotations. For more information about
automatic generalization and the value restriction, and how to change your code to address the problem, see
Automatic Generalization.
In the previous syntax, type-parameters is a comma-separated list of parameters that represent unknown types,
each of which starts with a single quotation mark, optionally with a constraint clause that further limits what
types may be used for that type parameter. For the syntax for constraint clauses of various kinds and other
information about constraints, see Constraints.
The type-definition in the syntax is the same as the type definition for a non-generic type. It includes the
constructor parameters for a class type, an optional as clause, the equal symbol, the record fields, the inherit
clause, the choices for a discriminated union, let and do bindings, member definitions, and anything else
permitted in a non-generic type definition.
The other syntax elements are the same as those for non-generic functions and types. For example, object-
identifier is an identifier that represents the containing object itself.
Properties, fields, and constructors cannot be more generic than the enclosing type. Also, values in a module
cannot be generic.
let makeList a b =
[a; b]
The signature of the function is inferred to be 'a -> 'a -> 'a list . Note that a and b in this example are
inferred to have the same type. This is because they are included in a list together, and all elements of a list must
be of the same type.
You can also make a function generic by using the single quotation mark syntax in a type annotation to indicate
that a parameter type is a generic type parameter. In the following code, function1 is generic because its
parameters are declared in this manner, as type parameters.
let function2<'T> x y =
printfn "%A, %A" x y
Examples
// A generic function.
// In this example, the generic type parameter 'a makes function3 generic.
let function3 (x : 'a) (y : 'a) =
printf "%A %A" x y
// A generic class.
type C<'a>(a : 'a, b : 'a) =
let z = a
let y = b
member this.GenericMethod(x : 'a) =
printfn "%A %A %A" x y z
type Test() =
// A generic member
member this.Function1<'a>(x, y) =
printfn "%A, %A" x y
See also
Language Reference
Types
Statically Resolved Type Parameters
Generics
Automatic Generalization
Constraints
Automatic Generalization
7/30/2019 • 3 minutes to read • Edit Online
F# uses type inference to evaluate the types of functions and expressions. This topic describes how F#
automatically generalizes the arguments and types of functions so that they work with multiple types when this
is possible.
Automatic Generalization
The F# compiler, when it performs type inference on a function, determines whether a given parameter can be
generic. The compiler examines each parameter and determines whether the function has a dependency on the
specific type of that parameter. If it does not, the type is inferred to be generic.
The following code example illustrates a function that the compiler infers to be generic.
However, the two arguments must be of the same type. The signature is 'a -> 'a -> 'a , not 'a -> 'b -> 'a .
Therefore, the following code produces an error because the types do not match.
The max function also works with any type that supports the greater-than operator. Therefore, you could also
use it on a string, as shown in the following code.
Value Restriction
The compiler performs automatic generalization only on complete function definitions that have explicit
arguments, and on simple immutable values.
This means that the compiler issues an error if you try to compile code that is not sufficiently constrained to be a
specific type, but is also not generalizable. The error message for this problem refers to this restriction on
automatic generalization for values as the value restriction.
Typically, the value restriction error occurs either when you want a construct to be generic but the compiler has
insufficient information to generalize it, or when you unintentionally omit sufficient type information in a
nongeneric construct. The solution to the value restriction error is to provide more explicit information to more
fully constrain the type inference problem, in one of the following ways:
Constrain a type to be nongeneric by adding an explicit type annotation to a value or parameter.
If the problem is using a nongeneralizable construct to define a generic function, such as a function
composition or incompletely applied curried function arguments, try to rewrite the function as an
ordinary function definition.
If the problem is an expression that is too complex to be generalized, make it into a function by adding an
extra, unused parameter.
Add explicit generic type parameters. This option is rarely used.
The following code examples illustrate each of these scenarios.
Case 1: Too complex an expression. In this example, the list counter is intended to be int option ref , but it is
not defined as a simple immutable value.
Case 2: Using a nongeneralizable construct to define a generic function. In this example, the construct is
nongeneralizable because it involves partial application of function arguments.
Case 3: Adding an extra, unused parameter. Because this expression is not simple enough for generalization, the
compiler issues the value restriction error.
In the last case, the value becomes a type function, which may be used to create values of many different types,
for example as follows:
See also
Type Inference
Generics
Statically Resolved Type Parameters
Constraints
Constraints
11/1/2019 • 4 minutes to read • Edit Online
This topic describes constraints that you can apply to generic type parameters to specify the requirements for a
type argument in a generic type or function.
Syntax
type-parameter-list when constraint1 [ and constraint2]
Remarks
There are several different constraints you can apply to limit the types that can be used in a generic type. The
following table lists and describes these constraints.
C O N ST RA IN T SY N TA X DESC RIP T IO N
Type Constraint type-parameter :> type The provided type must be equal to or
derived from the type specified, or, if
the type is an interface, the provided
type must implement the interface.
Null Constraint type-parameter : null The provided type must support the
null literal. This includes all .NET object
types but not F# list, tuple, function,
class, record, or union types.
Explicit Member Constraint [(]type-parameter [or ... or type- At least one of the type arguments
parameter)] : (member-signature) provided must have a member that
has the specified signature; not
intended for common use. Members
must be either explicitly defined on the
type or part of an implicit type
extension to be valid targets for an
Explicit Member Constraint.
Constructor Constraint type-parameter : ( new : unit -> 'a ) The provided type must have a
parameterless constructor.
Reference Type Constraint : not struct The provided type must be a .NET
reference type.
You have to add a constraint when your code has to use a feature that is available on the constraint type but not
on types in general. For example, if you use the type constraint to specify a class type, you can use any one of
the methods of that class in the generic function or type.
Specifying constraints is sometimes required when writing type parameters explicitly, because without a
constraint, the compiler has no way of verifying that the features that you are using will be available on any type
that might be supplied at run time for the type parameter.
The most common constraints you use in F# code are type constraints that specify base classes or interfaces.
The other constraints are either used by the F# library to implement certain functionality, such as the explicit
member constraint, which is used to implement operator overloading for arithmetic operators, or are provided
mainly because F# supports the complete set of constraints that is supported by the common language runtime.
During the type inference process, some constraints are inferred automatically by the compiler. For example, if
you use the + operator in a function, the compiler infers an explicit member constraint on variable types that
are used in the expression.
The following code illustrates some constraint declarations:
// Base Type Constraint
type Class1<'T when 'T :> System.Exception> =
class end
// Null constraint
type Class3<'T when 'T : null> =
class end
// Constructor constraint
type Class7<'T when 'T : (new : unit -> 'T)>() =
member val Field = new 'T()
// 'T must support equality. This is true for any type that does not
// have the NoEquality attribute.
type Class11<'T when 'T : equality> =
class end
// If there are multiple constraints, use the and keyword to separate them.
type Class14<'T,'U when 'T : equality and 'U : equality> =
class end
See also
Generics
Constraints
Statically Resolved Type Parameters
11/1/2019 • 3 minutes to read • Edit Online
A statically resolved type parameter is a type parameter that is replaced with an actual type at compile time
instead of at run time. They are preceded by a caret (^) symbol.
Syntax
ˆtype-parameter
Remarks
In the F# language, there are two distinct kinds of type parameters. The first kind is the standard generic type
parameter. These are indicated by an apostrophe ('), as in 'T and 'U . They are equivalent to generic type
parameters in other .NET Framework languages. The other kind is statically resolved and is indicated by a caret
symbol, as in ^T and ^U .
Statically resolved type parameters are primarily useful in conjunction with member constraints, which are
constraints that allow you to specify that a type argument must have a particular member or members in order
to be used. There is no way to create this kind of constraint by using a regular generic type parameter.
The following table summarizes the similarities and differences between the two kinds of type parameters.
Member constraints Cannot be used with member Can be used with member constraints.
constraints.
Code generation A type (or method) with standard Multiple instantiations of types and
generic type parameters results in the methods are generated, one for each
generation of a single generic type or type that is needed.
method.
Use with inline functions No. An inline function cannot be Yes. Statically resolved type parameters
parameterized with a standard generic cannot be used on functions or
type parameter. methods that are not inline.
Many F# core library functions, especially operators, have statically resolved type parameters. These functions
and operators are inline, and result in efficient code generation for numeric computations.
Inline methods and functions that use operators, or use other functions that have statically resolved type
parameters, can also use statically resolved type parameters themselves. Often, type inference infers such inline
functions to have statically resolved type parameters. The following example illustrates an operator definition
that is inferred to have a statically resolved type parameter.
let inline (+@) x y = x + x * y
// Call that uses int.
printfn "%d" (1 +@ 1)
// Call that uses float.
printfn "%f" (1.0 +@ 0.5)
The resolved type of (+@) is based on the use of both (+) and (*) , both of which cause type inference to
infer member constraints on the statically resolved type parameters. The resolved type, as shown in the F#
interpreter, is as follows.
^a -> ^c -> ^d
when (^a or ^b) : (static member ( + ) : ^a * ^b -> ^d) and
(^a or ^c) : (static member ( * ) : ^a * ^c -> ^b)
2
1.500000
Starting with F# 4.1, you can also specify concrete type names in statically resolved type parameter signatures.
In previous versions of the language, the type name could actually be inferred by the compiler, but could not
actually be specified in the signature. As of F# 4.1, you may also specify concrete type names in statically
resolved type parameter signatures. Here's an example:
type CFunctor() =
static member inline fmap (f: ^a -> ^b, a: ^a list) = List.map f a
static member inline fmap (f: ^a -> ^b, a: ^a option) =
match a with
| None -> None
| Some x -> Some (f x)
let inline replace_instance< ^a, ^b, ^c, ^d when (^a or ^c): (static member replace: ^b * ^c -> ^d)> (a: ^b,
f: ^c) =
((^a or ^c): (static member replace: ^b * ^c -> ^d) (a, f))
See also
Generics
Type Inference
Automatic Generalization
Constraints
Inline Functions
Records
3/6/2021 • 7 minutes to read • Edit Online
Records represent simple aggregates of named values, optionally with members. They can either be structs or
reference types. They are reference types by default.
Syntax
[ attributes ]
type [accessibility-modifier] typename =
{ [ mutable ] label1 : type1;
[ mutable ] label2 : type2;
... }
[ member-list ]
Remarks
In the previous syntax, typename is the name of the record type, label1 and label2 are names of values, referred
to as labels, and type1 and type2 are the types of these values. member-list is the optional list of members for
the type. You can use the [<Struct>] attribute to create a struct record rather than a record which is a reference
type.
Following are some examples.
// You can define labels on their own line with or without a semicolon.
type Customer =
{ First: string
Last: string;
SSN: uint32
AccountNumber: uint32; }
// A struct record.
[<Struct>]
type StructPoint =
{ X: float
Y: float
Z: float }
Do not use the shortened form if there could be another type that also has the same labels.
type Point = { X: float; Y: float; Z: float; }
type Point3D = { X: float; Y: float; Z: float }
// Ambiguity: Point or Point3D?
let mypoint3D = { X = 1.0; Y = 1.0; Z = 0.0; }
The labels of the most recently declared type take precedence over those of the previously declared type, so in
the preceding example, mypoint3D is inferred to be Point3D . You can explicitly specify the record type, as in the
following code.
Methods can be defined for record types just as for class types.
type MyRecord =
{ X: int
Y: int
Z: int }
let myRecord1 = { X = 1; Y = 2; Z = 3; }
The semicolons after the last field in the record expression and in the type definition are optional, regardless of
whether the fields are all in one line.
When you create a record, you must supply values for each field. You cannot refer to the values of other fields in
the initialization expression for any field.
In the following code, the type of myRecord2 is inferred from the names of the fields. Optionally, you can specify
the type name explicitly.
Another form of record construction can be useful when you have to copy an existing record, and possibly
change some of the field values. The following line of code illustrates this.
This form of the record expression is called the copy and update record expression.
Records are immutable by default; however, you can easily create modified records by using a copy and update
expression. You can also explicitly specify a mutable field.
type Car =
{ Make : string
Model : string
mutable Odometer : int }
Don't use the DefaultValue attribute with record fields. A better approach is to define default instances of records
with fields that are initialized to default values and then use a copy and update record expression to set any
fields that differ from the default values.
// Create a Person type and use the Address type that is not defined
type Person =
{ Name: string
Age: int
Address: Address }
// Define the Address type which is used in the Person record
and Address =
{ Line1: string
Line2: string
PostCode: string
Occupant: Person }
If you were to define the previous example without the and keyword, then it would not compile. The and
keyword is required for mutually recursive definitions.
type Person =
{ Name: string
Age: int
Address: string }
If you use a self identifier, that identifier refers to the instance of the record whose member is called:
type Person =
{ Name: string
Age: int
Address: string }
member this.WeirdToString() =
this.Name + this.Address + string this.Age
let record1 = { X = 1; Y = 2 }
let record2 = { X = 1; Y = 2 }
If you write the same code with classes, the two class objects would be unequal because the two values would
represent two objects on the heap and only the addresses would be compared (unless the class type overrides
the System.Object.Equals method).
If you need reference equality for records, add the attribute [<ReferenceEquality>] above the record.
See also
F# Types
Classes
F# Language Reference
Reference-Equality
Pattern Matching
Anonymous Records
5/19/2021 • 8 minutes to read • Edit Online
Anonymous records are simple aggregates of named values that don't need to be declared before use. You can
declare them as either structs or reference types. They're reference types by default.
Syntax
The following examples demonstrate the anonymous record syntax. Items delimited as [item] are optional.
Basic usage
Anonymous records are best thought of as F# record types that don't need to be declared before instantiation.
For example, here how you can interact with a function that produces an anonymous record:
open System
let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
The following example expands on the previous one with a printCircleStats function that takes an anonymous
record as input:
open System
let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) =
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
let r = 2.0
let stats = getCircleStats r
printCircleStats r stats
Calling printCircleStats with any anonymous record type that doesn't have the same "shape" as the input type
will fail to compile:
open System
// Note that the keyword comes before the '{| |}' brace pair
struct {| Area = a; Circumference = c; Diameter = d |}
// the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
let r = 2.0
let stats = getCircleStats r
printCircleStats r stats
Structness inference
Struct anonymous records also allow for "structness inference" where you do not need to specify the struct
keyword at the call site. In this example, you elide the struct keyword when calling printCircleStats :
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
// Note that using a named record for Manager and Executive would require mutually recursive definitions.
type Employee =
| Engineer of FullName
| Manager of {| Name: FullName; Reports: Employee list |}
| Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |}
let getFirstName e =
match e with
| Engineer fullName -> fullName.FirstName
| Manager m -> m.Name.FirstName
| Executive ex -> ex.Name.FirstName
let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}
However, unlike named records, anonymous records allow you to construct entirely different forms with copy
and update expressions. The follow example takes the same anonymous record from the previous example and
expands it into a new anonymous record:
let data = {| X = 1; Y = 2 |}
let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |}
type R = { X: int }
let data = { X = 1 }
let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |}
You can also copy data to and from reference and struct anonymous records:
// Copy data from a reference record into a struct anonymous record
type R1 = { X: int }
let r1 = { X = 1 }
// Copy the reference anonymous record data into a struct anonymous record
let data3 = struct {| data2 with Z = r2.X |}
let x = {| X = 1 |}
let y = {| Y = 1 |}
The x and y values have different types and are not compatible with one another. They are not equatable and
they are not comparable. To illustrate this, consider a named record equivalent:
type X = { X: int }
type Y = { Y: int }
let x = { X = 1 }
let y = { Y = 1 }
There isn't anything inherently different about anonymous records when compared with their named record
equivalents when concerning type equivalency or comparison.
Anonymous records use structural equality and comparison
Like record types, anonymous records are structurally equatable and comparable. This is only true if all
constituent types support equality and comparison, like with record types. To support equality or comparison,
two anonymous records must have the same "shape".
{| a = 1+1 |} = {| a = 2 |} // true
{| a = 1+1 |} > {| a = 1 |} // true
// error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]'
{| a = 1 + 1 |} = {| a = 2; b = 1|}
Anonymous records are useful for sending lightweight data over a network without the need to define a domain
for your serialized/deserialized types up front.
Anonymous records interoperate with C# anonymous types
It is possible to use a .NET API that requires the use of C# anonymous types. C# anonymous types are trivial to
interoperate with by using anonymous records. The following example shows how to use anonymous records to
call a LINQ overload that requires an anonymous type:
open System.Linq
There are a multitude of other APIs used throughout .NET that require the use of passing in an anonymous type.
Anonymous records are your tool for working with them.
Limitations
Anonymous records have some restrictions in their usage. Some are inherent to their design, but others are
amenable to change.
Limitations with pattern matching
Anonymous records do not support pattern matching, unlike named records. There are three reasons:
1. A pattern would have to account for every field of an anonymous record, unlike named record types. This is
because anonymous records do not support structural subtyping – they are nominal types.
2. Because of (1), there is no ability to have additional patterns in a pattern match expression, as each distinct
pattern would imply a different anonymous record type.
3. Because of (3), any anonymous record pattern would be more verbose than the use of “dot” notation.
There is an open language suggestion to allow pattern matching in limited contexts.
Limitations with mutability
It is not currently possible to define an anonymous record with mutable data. There is an open language
suggestion to allow mutable data.
Limitations with struct anonymous records
It is not possible to declare struct anonymous records as IsByRefLike or IsReadOnly . There is an open language
suggestion to for IsByRefLike and IsReadOnly anonymous records.
Copy and Update Record Expressions
4/21/2020 • 2 minutes to read • Edit Online
A copy and update record expression is an expression that copies an existing record, updates specified fields,
and returns the updated record.
Syntax
{ record-name with
updated-labels }
{| anonymous-record-name with
updated-labels |}
Remarks
Records and anonymous records are immutable by default, so it is not possible to update an existing record. To
create an updated record all the fields of a record would have to be specified again. To simplify this task a copy
and update expression can be used. This expression takes an existing record, creates a new one of the same type
by using specified fields from the expression and the missing field specified by the expression.
This can be useful when you have to copy an existing record, and possibly change some of the field values.
Take for instance a newly created record.
To update only two fields in that record you can use the copy and update record expression:
See also
Records
Anonymous Records
F# Language Reference
Discriminated Unions
3/6/2021 • 9 minutes to read • Edit Online
Discriminated unions provide support for values that can be one of a number of named cases, possibly each
with different values and types. Discriminated unions are useful for heterogeneous data; data that can have
special cases, including valid and error cases; data that varies in type from one instance to another; and as an
alternative for small object hierarchies. In addition, recursive discriminated unions are used to represent tree
data structures.
Syntax
[ attributes ]
type [accessibility-modifier] type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
[ member-list ]
Remarks
Discriminated unions are similar to union types in other languages, but there are differences. As with a union
type in C++ or a variant type in Visual Basic, the data stored in the value is not fixed; it can be one of several
distinct options. Unlike unions in these other languages, however, each of the possible options is given a case
identifier. The case identifiers are names for the various possible types of values that objects of this type could
be; the values are optional. If values are not present, the case is equivalent to an enumeration case. If values are
present, each value can either be a single value of a specified type, or a tuple that aggregates multiple fields of
the same or different types. You can give an individual field a name, but the name is optional, even if other fields
in the same case are named.
Accessibility for discriminated unions defaults to public .
For example, consider the following declaration of a Shape type.
type Shape =
| Rectangle of width : float * length : float
| Circle of radius : float
| Prism of width : float * float * height : float
The preceding code declares a discriminated union Shape, which can have values of any of three cases:
Rectangle, Circle, and Prism. Each case has a different set of fields. The Rectangle case has two named fields,
both of type float , that have the names width and length. The Circle case has just one named field, radius. The
Prism case has three fields, two of which (width and height) are named fields. Unnamed fields are referred to as
anonymous fields.
You construct objects by providing values for the named and anonymous fields according to the following
examples.
The previous code specifies that the type Option is a discriminated union that has two cases, Some and None .
The Some case has an associated value that consists of one anonymous field whose type is represented by the
type parameter 'a . The None case has no associated value. Thus the option type specifies a generic type that
either has a value of some type or no value. The type Option also has a lowercase type alias, option , that is
more commonly used.
The case identifiers can be used as constructors for the discriminated union type. For example, the following
code is used to create values of the option type.
The case identifiers are also used in pattern matching expressions. In a pattern matching expression, identifiers
are provided for the values associated with the individual cases. For example, in the following code, x is the
identifier given the value that is associated with the Some case of the option type.
In pattern matching expressions, you can use named fields to specify discriminated union matches. For the
Shape type that was declared previously, you can use the named fields as the following code shows to extract
the values of the fields.
Normally, the case identifiers can be used without qualifying them with the name of the union. If you want the
name to always be qualified with the name of the union, you can apply the RequireQualifiedAccess attribute to
the union type definition.
Unwrapping Discriminated Unions
In F# Discriminated Unions are often used in domain-modeling for wrapping a single type. It's easy to extract
the underlying value via pattern matching as well. You don't need to use a match expression for a single case:
Pattern matching is also allowed directly in function parameters, so you can unwrap a single case there:
[<Struct>]
type SingleCase = Case of string
[<Struct>]
type Multicase =
| Case1 of Case1 : string
| Case2 of Case2 : int
| Case3 of Case3 : double
Because these are value types and not reference types, there are extra considerations compared with reference
discriminated unions:
1. They are copied as value types and have value type semantics.
2. You cannot use a recursive type definition with a multicase struct Discriminated Union.
3. You must provide unique case names for a multicase struct Discriminated Union.
type Shape =
// The value here is the radius.
| Circle of float
// The value here is the side length.
| EquilateralTriangle of double
// The value here is the side length.
| Square of double
// The values here are the height and width.
| Rectangle of double * double
Instead of a virtual method to compute an area or perimeter, as you would use in an object-oriented
implementation, you can use pattern matching to branch to appropriate formulas to compute these quantities.
In the following example, different formulas are used to compute the area, depending on the shape.
let pi = 3.141592654
type Tree =
| Tip
| Node of int * Tree * Tree
In the previous code, resultSumTree has the value 10. The following illustration shows the tree structure for
myTree .
Discriminated unions work well if the nodes in the tree are heterogeneous. In the following code, the type
Expression represents the abstract syntax tree of an expression in a simple programming language that
supports addition and multiplication of numbers and variables. Some of the union cases are not recursive and
represent either numbers ( Number ) or variables ( Variable ). Other cases are recursive, and represent operations
( Add and Multiply ), where the operands are also expressions. The Evaluate function uses a match expression
to recursively process the syntax tree.
type Expression =
| Number of int
| Add of Expression * Expression
| Multiply of Expression * Expression
| Variable of string
Members
It is possible to define members on discriminated unions. The following example shows how to define a
property and implement an interface:
open System
type IPrintable =
abstract Print: unit -> unit
type Shape =
| Circle of float
| EquilateralTriangle of float
| Square of float
| Rectangle of float * float
member this.Area =
match this with
| Circle r -> 2.0 * Math.PI * r
| EquilateralTriangle s -> s * s * sqrt 3.0 / 4.0
| Square s -> s * s
| Rectangle(l, w) -> l * w
Common attributes
The following attributes are commonly seen in discriminated unions:
[<RequireQualifiedAccess>]
[<NoEquality>]
[<NoComparison>]
[<Struct>]
See also
F# Language Reference
Enumerations
11/2/2020 • 2 minutes to read • Edit Online
Enumerations, also known as enums, , are integral types where labels are assigned to a subset of the values. You
can use them in place of literals to make code more readable and maintainable.
Syntax
type enum-name =
| value1 = integer-literal1
| value2 = integer-literal2
...
Remarks
An enumeration looks much like a discriminated union that has simple values, except that the values can be
specified. The values are typically integers that start at 0 or 1, or integers that represent bit positions. If an
enumeration is intended to represent bit positions, you should also use the Flags attribute.
The underlying type of the enumeration is determined from the literal that is used, so that, for example, you can
use literals with a suffix, such as 1u , 2u , and so on, for an unsigned integer ( uint32 ) type.
When you refer to the named values, you must use the name of the enumeration type itself as a qualifier, that is,
enum-name.value1 , not just value1 . This behavior differs from that of discriminated unions. This is because
enumerations always have the RequireQualifiedAccess attribute.
The following code shows the declaration and use of an enumeration.
// Declaration of an enumeration.
type Color =
| Red = 0
| Green = 1
| Blue = 2
// Use of an enumeration.
let col1 : Color = Color.Red
You can easily convert enumerations to the underlying type by using the appropriate operator, as shown in the
following code.
Enumerated types can have one of the following underlying types: sbyte , byte , int16 , uint16 , int32 ,
uint32 , int64 , uint16 , uint64 , and char . Enumeration types are represented in the .NET Framework as
types that are inherited from System.Enum , which in turn is inherited from System.ValueType . Thus, they are
value types that are located on the stack or inline in the containing object, and any value of the underlying type
is a valid value of the enumeration. This is significant when pattern matching on enumeration values, because
you have to provide a pattern that catches the unnamed values.
The enum function in the F# library can be used to generate an enumeration value, even a value other than one
of the predefined, named values. You use the enum function as follows.
The default enum function works with type int32 . Therefore, it cannot be used with enumeration types that
have other underlying types. Instead, use the following.
type uColor =
| Red = 0u
| Green = 1u
| Blue = 2u
let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)
Additionally, cases for enums are always emitted as public . This is so that they align with C# and the rest of the
.NET platform.
See also
F# Language Reference
Casting and Conversions
Type Abbreviations
7/30/2019 • 2 minutes to read • Edit Online
Syntax
type [accessibility-modifier] type-abbreviation = type-name
Remarks
You can use type abbreviations to give a type a more meaningful name, in order to make code easier to read.
You can also use them to create an easy to use name for a type that is otherwise cumbersome to write out.
Additionally, you can use type abbreviations to make it easier to change an underlying type without changing all
the code that uses the type. The following is a simple type abbreviation.
Accessibility of type abbreviations defaults to public .
In the previous code, Transform is a type abbreviation that represents a function that takes a single argument of
any type and that returns a single value of that same type.
Type abbreviations are not preserved in the .NET Framework MSIL code. Therefore, when you use an F#
assembly from another .NET Framework language, you must use the underlying type name for a type
abbreviation.
Type abbreviations can also be used on units of measure. For more information, see Units of Measure.
See also
F# Language Reference
Classes (F#)
5/20/2021 • 8 minutes to read • Edit Online
Classes are types that represent objects that can have properties, methods, and events.
Syntax
// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
Remarks
Classes represent the fundamental description of .NET object types; the class is the primary type concept that
supports object-oriented programming in F#.
In the preceding syntax, the type-name is any valid identifier. The type-params describes optional generic type
parameters. It consists of type parameter names and constraints enclosed in angle brackets ( < and > ). For
more information, see Generics and Constraints. The parameter-list describes constructor parameters. The first
access modifier pertains to the type; the second pertains to the primary constructor. In both cases, the default is
public .
You specify the base class for a class by using the inherit keyword. You must supply arguments, in
parentheses, for the base class constructor.
You declare fields or function values that are local to the class by using let bindings, and you must follow the
general rules for let bindings. The do-bindings section includes code to be executed upon object construction.
The member-list consists of additional constructors, instance and static method declarations, interface
declarations, abstract bindings, and property and event declarations. These are described in Members.
The identifier that is used with the optional as keyword gives a name to the instance variable, or self
identifier, which can be used in the type definition to refer to the instance of the type. For more information, see
the section Self Identifiers later in this topic.
The keywords class and end that mark the start and end of the definition are optional.
Mutually recursive types, which are types that reference each other, are joined together with the and keyword
just as mutually recursive functions are. For an example, see the section Mutually Recursive Types.
Constructors
The constructor is code that creates an instance of the class type. Constructors for classes work somewhat
differently in F# than they do in other .NET languages. In an F# class, there is always a primary constructor
whose arguments are described in the parameter-list that follows the type name, and whose body consists of
the let (and let rec ) bindings at the start of the class declaration and the do bindings that follow. The
arguments of the primary constructor are in scope throughout the class declaration.
You can add additional constructors by using the new keyword to add a member, as follows:
new ( argument-list ) = constructor-body
The body of the new constructor must invoke the primary constructor that is specified at the top of the class
declaration.
The following example illustrates this concept. In the following code, MyClass has two constructors, a primary
constructor that takes two arguments and another constructor that takes no arguments.
Self Identifiers
A self identifier is a name that represents the current instance. Self identifiers resemble the this keyword in C#
or C++ or Me in Visual Basic. You can define a self identifier in two different ways, depending on whether you
want the self identifier to be in scope for the whole class definition or just for an individual method.
To define a self identifier for the whole class, use the as keyword after the closing parentheses of the
constructor parameter list, and specify the identifier name.
To define a self identifier for just one method, provide the self identifier in the member declaration, just before
the method name and a period (.) as a separator.
The following code example illustrates the two ways to create a self identifier. In the first line, the as keyword is
used to define the self identifier. In the fifth line, the identifier this is used to define a self identifier whose
scope is restricted to the method PrintMessage .
Type arguments are inferred when the type is used. In the following code, the inferred type is a sequence of
tuples.
Specifying Inheritance
The inherit clause identifies the direct base class, if there is one. In F#, only one direct base class is allowed.
Interfaces that a class implements are not considered base classes. Interfaces are discussed in the Interfaces
topic.
You can access the methods and properties of the base class from the derived class by using the language
keyword base as an identifier, followed by a period (.) and the name of the member.
For more information, see Inheritance.
Members Section
You can define static or instance methods, properties, interface implementations, abstract members, event
declarations, and additional constructors in this section. Let and do bindings cannot appear in this section.
Because members can be added to a variety of F# types in addition to classes, they are discussed in a separate
topic, Members.
See also
F# Language Reference
Members
Inheritance
Interfaces
Structures
8/28/2019 • 4 minutes to read • Edit Online
A structure is a compact object type that can be more efficient than a class for types that have a small amount of
data and simple behavior.
Syntax
[ attributes ]
type [accessibility-modifier] type-name =
struct
type-definition-elements-and-members
end
// or
[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
type-definition-elements-and-members
Remarks
Structures are value types, which means that they are stored directly on the stack or, when they are used as
fields or array elements, inline in the parent type. Unlike classes and records, structures have pass-by-value
semantics. This means that they are useful primarily for small aggregates of data that are accessed and copied
frequently.
In the previous syntax, two forms are shown. The first is not the lightweight syntax, but it is nevertheless
frequently used because, when you use the struct and end keywords, you can omit the StructAttribute
attribute, which appears in the second form. You can abbreviate StructAttribute to just Struct .
The type-definition-elements-and-members in the previous syntax represents member declarations and
definitions. Structures can have constructors and mutable and immutable fields, and they can declare members
and interface implementations. For more information, see Members.
Structures cannot participate in inheritance, cannot contain let or do bindings, and cannot recursively contain
fields of their own type (although they can contain reference cells that reference their own type).
Because structures do not allow let bindings, you must declare fields in structures by using the val keyword.
The val keyword defines a field and its type but does not allow initialization. Instead, val declarations are
initialized to zero or null. For this reason, structures that have an implicit constructor (that is, parameters that are
given immediately after the structure name in the declaration) require that val declarations be annotated with
the DefaultValue attribute. Structures that have a defined constructor still support zero-initialization. Therefore,
the DefaultValue attribute is a declaration that such a zero value is valid for the field. Implicit constructors for
structures do not perform any actions because let and do bindings aren’t allowed on the type, but the
implicit constructor parameter values passed in are available as private fields.
Explicit constructors might involve initialization of field values. When you have a structure that has an explicit
constructor, it still supports zero-initialization; however, you do not use the DefaultValue attribute on the val
declarations because it conflicts with the explicit constructor. For more information about val declarations, see
Explicit Fields: The val Keyword.
Attributes and accessibility modifiers are allowed on structures, and follow the same rules as those for other
types. For more information, see Attributes and Access Control.
The following code examples illustrate structure definitions.
dX + dY
|> sqrt
end
ByRefLike structs
You can define your own structs that can adhere to byref -like semantics: see Byrefs for more information. This
is done with the IsByRefLikeAttribute attribute:
open System
open System.Runtime.CompilerServices
[<IsByRefLike; Struct>]
type S(count1: Span<int>, count2: Span<int>) =
member x.Count1 = count1
member x.Count2 = count2
IsByRefLike does not imply Struct . Both must be present on the type.
A " byref -like" struct in F# is a stack-bound value type. It is never allocated on the managed heap. A byref -like
struct is useful for high-performance programming, as it is enforced with set of strong checks about lifetime and
non-capture. The rules are:
They can be used as function parameters, method parameters, local variables, method returns.
They cannot be static or instance members of a class or normal struct.
They cannot be captured by any closure construct ( async methods or lambda expressions).
They cannot be used as a generic parameter.
Although these rules very strongly restrict usage, they do so to fulfill the promise of high-performance
computing in a safe manner.
ReadOnly structs
You can annotate structs with the IsReadOnlyAttribute attribute. For example:
[<IsReadOnly; Struct>]
type S(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
IsReadOnly does not imply Struct . You must add both to have an IsReadOnly struct.
Use of this attribute emits metadata letting F# and C# know to treat it as inref<'T> and in ref , respectively.
Defining a mutable value inside of a readonly struct produces an error.
See also
F# Language Reference
Classes
Records
Members
Nullable value types
3/6/2021 • 2 minutes to read • Edit Online
A nullable value type Nullable<'T> represents any struct type that could also be null . This is helpful when
interacting with libraries and components that may choose to represent these kinds of types, like integers, with a
null value for efficiency reasons. The underlying type that backs this construct is System.Nullable<T>.
Syntax
Nullable<'T>
Nullable value
open System
let x = 12
let nullableX = Nullable<int> x
You can also elide the generic type parameter and allow type inference to resolve it:
open System
let x = 12
let nullableX = Nullable x
To assign to a nullable value type, you'll need to also be explicit. There is no implicit conversion for F#-defined
nullable value types:
open System
Assign null
You cannot directly assign null to a nullable value type. Use Nullable() instead:
type C() =
member _.M(x: Nullable<int>) = x.HasValue
member val NVT = Nullable 12 with get, set
let c = C()
c.M(12)
c.NVT <- 12
In the previous example, you can pass 12 to the method M . You can also assign 12 to the auto property NVT .
If the input can be constructed as a nullable value type and it matches the target type, the F# compiler will
implicitly convert such calls or assignments.
open System
let a = Nullable 42
if a.HasValue then
printfn $"{a} is {a.Value}"
else
printfn $"{a} has no value."
Nullable operators
Operations on nullable value types, such as arithmetic or comparison, can require the use of nullable operators.
You can convert from one nullable value type to another using conversion operators from the FSharp.Linq
namespace:
open System
open FSharp.Linq
You can also use an appropriate non-nullable operator to convert to a primitive type, risking an exception if it
has no value:
open System
open FSharp.Linq
You can also use nullable operators as a short-hand for checking HasValue and Value :
open System
open FSharp.Linq
The ?> comparison checks if the left-hand side is nullable and only succeeds if it has a value. It is equivalent to
the line that follows it.
See also
Structures
F# Options
Inheritance
7/30/2019 • 3 minutes to read • Edit Online
type MyDerived(...) =
inherit MyBase(...)
A class can have at most one direct base class. If you do not specify a base class by using the inherit keyword,
the class implicitly inherits from System.Object .
Inherited Members
If a class inherits from another class, the methods and members of the base class are available to users of the
derived class as if they were direct members of the derived class.
Any let bindings and constructor parameters are private to a class and, therefore, cannot be accessed from
derived classes.
The keyword base is available in derived classes and refers to the base class instance. It is used like the self-
identifier.
And in a derived class, an override of this virtual method follows this pattern:
If you omit the default implementation in the base class, the base class becomes an abstract class.
The following code example illustrates the declaration of a new virtual method function1 in a base class and
how to override it in a derived class.
type MyClassBase1() =
let mutable z = 0
abstract member function1 : int -> int
default u.function1(a : int) = z <- z + a; z
type MyClassDerived1() =
inherit MyClassBase1()
override u.function1(a: int) = a + 1
In the case of multiple constructors, the following code can be used. The first line of the derived class
constructors is the inherit clause, and the fields appear as explicit fields that are declared with the val
keyword. For more information, see Explicit Fields: The val Keyword.
type BaseClass =
val string1 : string
new (str) = { string1 = str }
new () = { string1 = "" }
type DerivedClass =
inherit BaseClass
Alternatives to Inheritance
In cases where a minor modification of a type is required, consider using an object expression as an alternative
to inheritance. The following example illustrates the use of an object expression as an alternative to creating a
new derived type:
open System
See also
Object Expressions
F# Language Reference
Interfaces
4/13/2021 • 4 minutes to read • Edit Online
Syntax
// Interface declaration:
[ attributes ]
type [accessibility-modifier] interface-name =
[ interface ] [ inherit base-interface-name ...]
abstract member1 : [ argument-types1 -> ] return-type1
abstract member2 : [ argument-types2 -> ] return-type2
...
[ end ]
Remarks
Interface declarations resemble class declarations except that no members are implemented. Instead, all the
members are abstract, as indicated by the keyword abstract . You do not provide a method body for abstract
methods. However, you can provide a default implementation by also including a separate definition of the
member as a method together with the default keyword. Doing so is equivalent to creating a virtual method in
a base class in other .NET languages. Such a virtual method can be overridden in classes that implement the
interface.
The default accessibility for interfaces is public .
You can optionally give each method parameter a name using normal F# syntax:
type ISprintable =
abstract member Print : format:string -> unit
In the above ISprintable example, the Print method has a single parameter of the type string with the
name format .
There are two ways to implement interfaces: by using object expressions, and by using class types. In either case,
the class type or object expression provides method bodies for abstract methods of the interface.
Implementations are specific to each type that implements the interface. Therefore, interface methods on
different types might be different from each other.
The keywords interface and end , which mark the start and end of the definition, are optional when you use
lightweight syntax. If you do not use these keywords, the compiler attempts to infer whether the type is a class
or an interface by analyzing the constructs that you use. If you define a member or use other class syntax, the
type is interpreted as a class.
The .NET coding style is to begin all interfaces with a capital I .
You can specify multiple parameters in two ways: F#-style and .NET-style. Both will compile the same way for
.NET consumers, but F#-style will force F# callers to use F#-style parameter application and .NET-style will force
F# callers to use tupled argument application.
type INumericFSharp =
abstract Add: x: int -> y: int -> int
type INumericDotNet =
abstract Add: x: int * y: int -> int
type IPrintable =
abstract member Print : unit -> unit
Interface implementations are inherited, so any derived classes do not need to reimplement them.
An alternative is to declare a method on the object that upcasts and calls the interface method, as in the
following example.
Interface Inheritance
Interfaces can inherit from one or more base interfaces.
type Interface1 =
abstract member Method1 : int -> int
type Interface2 =
abstract member Method2 : int -> int
type Interface3 =
inherit Interface1
inherit Interface2
abstract member Method3 : int -> int
type MyClass() =
interface Interface3 with
member this.Method1(n) = 2 * n
member this.Method2(n) = n + 100
member this.Method3(n) = n / 10
using System;
namespace CSharp
{
public interface MyDim
{
public int Z => 0;
}
}
interface MyDim
You can override a default implementation with override , like overriding any virtual member.
Any members in an interface that do not have a default implementation must still be explicitly implemented.
type IA<'T> =
abstract member Get : unit -> 'T
type MyClass() =
interface IA<int> with
member x.Get() = 1
interface IA<string> with
member x.Get() = "hello"
let mc = MyClass()
let iaInt = mc :> IA<int>
let iaString = mc :> IA<string>
iaInt.Get() // 1
iaString.Get() // "hello"
See also
F# Language Reference
Object Expressions
Classes
Abstract Classes
5/20/2021 • 4 minutes to read • Edit Online
Abstract classes are classes that leave some or all members unimplemented, so that implementations can be
provided by derived classes.
Syntax
// Abstract class syntax.
[<AbstractClass>]
type [ accessibility-modifier ] abstract-class-name =
[ inherit base-class-or-interface-name ]
[ abstract-member-declarations-and-member-definitions ]
Remarks
In object-oriented programming, an abstract class is used as a base class of a hierarchy, and represents common
functionality of a diverse set of object types. As the name "abstract" implies, abstract classes often do not
correspond directly onto concrete entities in the problem domain. However, they do represent what many
different concrete entities have in common.
Abstract classes must have the AbstractClass attribute. They can have implemented and unimplemented
members. The use of the term abstract when applied to a class is the same as in other .NET languages; however,
the use of the term abstract when applied to methods (and properties) is a little different in F# from its use in
other .NET languages. In F#, when a method is marked with the abstract keyword, this indicates that a member
has an entry, known as a virtual dispatch slot, in the internal table of virtual functions for that type. In other
words, the method is virtual, although the virtual keyword is not used in the F# language. The keyword
abstract is used on virtual methods regardless of whether the method is implemented. The declaration of a
virtual dispatch slot is separate from the definition of a method for that dispatch slot. Therefore, the F#
equivalent of a virtual method declaration and definition in another .NET language is a combination of both an
abstract method declaration and a separate definition, with either the default keyword or the override
keyword. For more information and examples, see Methods.
A class is considered abstract only if there are abstract methods that are declared but not defined. Therefore,
classes that have abstract methods are not necessarily abstract classes. Unless a class has undefined abstract
methods, do not use the AbstractClass attribute.
In the previous syntax, accessibility-modifier can be public , private or internal . For more information, see
Access Control.
As with other types, abstract classes can have a base class and one or more base interfaces. Each base class or
interface appears on a separate line together with the inherit keyword.
The type definition of an abstract class can contain fully defined members, but it can also contain abstract
members. The syntax for abstract members is shown separately in the previous syntax. In this syntax, the type
signature of a member is a list that contains the parameter types in order and the return types, separated by ->
tokens and/or * tokens as appropriate for curried and tupled parameters. The syntax for abstract member type
signatures is the same as that used in signature files and that shown by IntelliSense in the Visual Studio Code
Editor.
The following code illustrates an abstract class Shape, which has two non-abstract derived classes, Square and
Circle. The example shows how to use abstract classes, methods, and properties. In the example, the abstract
class Shape represents the common elements of the concrete entities circle and square. The common features of
all shapes (in a two-dimensional coordinate system) are abstracted out into the Shape class: the position on the
grid, an angle of rotation, and the area and perimeter properties. These can be overridden, except for position,
the behavior of which individual shapes cannot change.
The rotation method can be overridden, as in the Circle class, which is rotation invariant because of its
symmetry. So in the Circle class, the rotation method is replaced by a method that does nothing.
Output:
See also
Classes
Members
Methods
Properties
Members
5/20/2021 • 2 minutes to read • Edit Online
Remarks
Members are features that are part of a type definition and are declared with the member keyword. F# object
types such as records, classes, discriminated unions, interfaces, and structures support members. For more
information, see Records, Classes, Discriminated Unions, Interfaces, and Structures.
Members typically make up the public interface for a type, which is why they are public unless otherwise
specified. Members can also be declared private or internal. For more information, see Access Control.
Signatures for types can also be used to expose or not expose certain members of a type. For more information,
see Signatures.
Private fields and do bindings, which are used only with classes, are not true members, because they are never
part of the public interface of a type and are not declared with the member keyword, but they are described in
this section also.
Related Topics
TO P IC DESC RIP T IO N
let Bindings in Classes Describes the definition of private fields and functions in
classes.
Explicit Fields: The val Keyword Describes the definition of uninitialized fields in a type.
let Bindings in Classes
9/24/2019 • 2 minutes to read • Edit Online
You can define private fields and private functions for F# classes by using let bindings in the class definition.
Syntax
// Field.
[static] let [ mutable ] binding1 [ and ... binding-n ]
// Function.
[static] let [ rec ] binding1 [ and ... binding-n ]
Remarks
The previous syntax appears after the class heading and inheritance declarations but before any member
definitions. The syntax is like that of let bindings outside of classes, but the names defined in a class have a
scope that is limited to the class. A let binding creates a private field or function; to expose data or functions
publicly, declare a property or a member method.
A let binding that is not static is called an instance let binding. Instance let bindings execute when objects
are created. Static let bindings are part of the static initializer for the class, which is guaranteed to execute
before the type is first used.
The code within instance let bindings can use the primary constructor's parameters.
Attributes and accessibility modifiers are not permitted on let bindings in classes.
The following code examples illustrate several types of let bindings in classes.
// A do binding.
do
count <- count + 1
member this.Prop1 = x
member this.Prop2 = y
member this.CreatedCount = count
member this.FunctionValue = privateFunction x y
10 52 1 204
See also
Members
do Bindings in Classes
let Bindings
do Bindings in Classes
5/20/2021 • 2 minutes to read • Edit Online
A do binding in a class definition performs actions when the object is constructed or, for a static do binding,
when the type is first used.
Syntax
[static] do expression
Remarks
A do binding appears together with or after let bindings but before member definitions in a class definition.
Although the do keyword is optional for do bindings at the module level, it is not optional for do bindings in
a class definition.
For the construction of every object of any given type, non-static do bindings and non-static let bindings are
executed in the order in which they appear in the class definition. Multiple do bindings can occur in one type.
The non-static let bindings and the non-static do bindings become the body of the primary constructor. The
code in the non-static do bindings section can reference the primary constructor parameters and any values or
functions that are defined in the let bindings section.
Non-static do bindings can access members of the class as long as the class has a self identifier that is defined
by an as keyword in the class heading, and as long as all uses of those members are qualified with the self
identifier for the class.
Because let bindings initialize the private fields of a class, which is often necessary to guarantee that members
behave as expected, do bindings are usually put after let bindings so that code in the do binding can
execute with a fully initialized object. If your code attempts to use a member before the initialization is complete,
an InvalidOperationException is raised.
Static do bindings can reference static members or fields of the enclosing class but not instance members or
fields. Static do bindings become part of the static initializer for the class, which is guaranteed to execute before
the class is first used.
Attributes are ignored for do bindings in types. If an attribute is required for code that executes in a do
binding, it must be applied to the primary constructor.
In the following code, a class has a static do binding and a non-static do binding. The object has a constructor
that has two parameters, a and b , and two private fields are defined in the let bindings for the class. Two
properties are also defined. All of these are in scope in the non-static do bindings section, as is illustrated by
the line that prints all those values.
open System
Initializing MyType.
Initializing object 1 2 2 4 8 16
See also
Members
Classes
Constructors
let Bindings in Classes
do Bindings
Properties
3/6/2021 • 6 minutes to read • Edit Online
Syntax
// Property that has both get and set defined.
[ attributes ]
[ static ] member [accessibility-modifier] [self-identifier.]PropertyName
with [accessibility-modifier] get() =
get-function-body
and [accessibility-modifier] set parameter =
set-function-body
Remarks
Properties represent the "has a" relationship in object-oriented programming, representing data that is
associated with object instances or, for static properties, with the type.
You can declare properties in two ways, depending on whether you want to explicitly specify the underlying
value (also called the backing store) for the property, or if you want to allow the compiler to automatically
generate the backing store for you. Generally, you should use the more explicit way if the property has a non-
trivial implementation and the automatic way when the property is just a simple wrapper for a value or variable.
To declare a property explicitly, use the member keyword. This declarative syntax is followed by the syntax that
specifies the get and set methods, also named accessors. The various forms of the explicit syntax shown in
the syntax section are used for read/write, read-only, and write-only properties. For read-only properties, you
define only a get method; for write-only properties, define only a set method. Note that when a property has
both get and set accessors, the alternative syntax enables you to specify attributes and accessibility modifiers
that are different for each accessor, as is shown in the following code.
// A read-only property.
member this.MyReadOnlyProperty = myInternalValue
// A write-only property.
member this.MyWriteOnlyProperty with set (value) = myInternalValue <- value
// A read-write property.
member this.MyReadWriteProperty
with get () = myInternalValue
and set (value) = myInternalValue <- value
For read/write properties, which have both a get and set method, the order of get and set can be
reversed. Alternatively, you can provide the syntax shown for get only and the syntax shown for set only
instead of using the combined syntax. Doing this makes it easier to comment out the individual get or set
method, if that is something you might need to do. This alternative to using the combined syntax is shown in the
following code.
Private values that hold the data for properties are called backing stores. To have the compiler create the
backing store automatically, use the keywords member val , omit the self-identifier, then provide an expression to
initialize the property. If the property is to be mutable, include with get, set . For example, the following class
type includes two automatically implemented properties. Property1 is read-only and is initialized to the
argument provided to the primary constructor, and Property2 is a settable property initialized to an empty
string:
Automatically implemented properties are part of the initialization of a type, so they must be included before
any other member definitions, just like let bindings and do bindings in a type definition. Note that the
expression that initializes an automatically implemented property is only evaluated upon initialization, and not
every time the property is accessed. This behavior is in contrast to the behavior of an explicitly implemented
property. What this effectively means is that the code to initialize these properties is added to the constructor of
a class. Consider the following code that shows this difference:
type MyClass() =
let random = new System.Random()
member val AutoProperty = random.Next() with get, set
member this.ExplicitProperty = random.Next()
Output
class1.AutoProperty = 1853799794
class1.AutoProperty = 1853799794
class1.ExplicitProperty = 978922705
class1.ExplicitProperty = 1131210765
The output of the preceding code shows that the value of AutoProperty is unchanged when called repeatedly,
whereas the ExplicitProperty changes each time it is called. This demonstrates that the expression for an
automatically implemented property is not evaluated each time, as is the getter method for the explicit property.
WARNING
There are some libraries, such as the Entity Framework ( System.Data.Entity ) that perform custom operations in base
class constructors that don't work well with the initialization of automatically implemented properties. In those cases, try
using explicit properties.
Properties can be members of classes, structures, discriminated unions, records, interfaces, and type extensions
and can also be defined in object expressions.
Attributes can be applied to properties. To apply an attribute to a property, write the attribute on a separate line
before the property. For more information, see Attributes.
By default, properties are public. Accessibility modifiers can also be applied to properties. To apply an
accessibility modifier, add it immediately before the name of the property if it is meant to apply to both the get
and set methods; add it before the get and set keywords if different accessibility is required for each
accessor. The accessibility-modifier can be one of the following: public , private , internal . For more
information, see Access Control.
Property implementations are executed each time a property is accessed.
Properties can also be array-like, in which case they are called indexed properties. For more information, see
Indexed Properties.
// Assume that the constructor argument sets the initial value of the
// internal backing store.
let mutable myObject = new MyType(10)
myObject.MyProperty <- 20
printfn "%d" (myObject.MyProperty)
The output is 20 .
Abstract Properties
Properties can be abstract. As with methods, abstract just means that there is a virtual dispatch associated
with the property. Abstract properties can be truly abstract, that is, without a definition in the same class. The
class that contains such a property is therefore an abstract class. Alternatively, abstract can just mean that a
property is virtual, and in that case, a definition must be present in the same class. Note that abstract properties
must not be private, and if one accessor is abstract, the other must also be abstract. For more information about
abstract classes, see Abstract Classes.
See also
Members
Methods
Indexed Properties
11/12/2019 • 3 minutes to read • Edit Online
When defining a class that abstracts over ordered data, it can sometimes be helpful to provide indexed access to
that data without exposing the underlying implementation. This is done with the Item member.
Syntax
// Indexed property that can be read and written to
member self-identifier.Item
with get(index-values) =
get-member-body
and set index-values values-to-set =
set-member-body
Remarks
The forms of the previous syntax show how to define indexed properties that have both a get and a set
method, have a get method only, or have a set method only. You can also combine both the syntax shown for
get only and the syntax shown for set only, and produce a property that has both get and set. This latter form
allows you to put different accessibility modifiers and attributes on the get and set methods.
By using the name Item , the compiler treats the property as a default indexed property. A default indexed
property is a property that you can access by using array-like syntax on the object instance. For example, if o is
an object of the type that defines this property, the syntax o.[index] is used to access the property.
The syntax for accessing a non-default indexed property is to provide the name of the property and the index in
parentheses, just like a regular member. For example, if the property on o is called Ordinal , you write
o.Ordinal(index) to access it.
Regardless of which form you use, you should always use the curried form for the set method on an indexed
property. For information about curried functions, see Functions.
Example
The following code example illustrates the definition and use of default and non-default indexed properties that
have get and set methods.
type NumberStrings() =
let mutable ordinals = [| "one"; "two"; "three"; "four"; "five";
"six"; "seven"; "eight"; "nine"; "ten" |]
let mutable cardinals = [| "first"; "second"; "third"; "fourth";
"fifth"; "sixth"; "seventh"; "eighth";
"ninth"; "tenth" |]
member this.Item
with get(index) = ordinals.[index]
and set index value = ordinals.[index] <- value
member this.Ordinal
with get(index) = ordinals.[index]
and set index value = ordinals.[index] <- value
member this.Cardinal
with get(index) = cardinals.[index]
and set index value = cardinals.[index] <- value
for i in 0 .. 9 do
printf "%s " (nstrs.Ordinal(i))
printf "%s " (nstrs.Cardinal(i))
printfn ""
Output
ONE two three four five six seven eight nine ten
ONE first two second three third four fourth five fifth six 6th
seven seventh eight eighth nine ninth ten tenth
open System.Collections.Generic
// 'set' has two index values and a new value to place in the key's position
and set (key1, key2) value = table.[(key1, key2)] <- value
A method is a function that is associated with a type. In object-oriented programming, methods are used to
expose and implement the functionality and behavior of objects and types.
Syntax
// Instance method definition.
[ attributes ]
member [inline] self-identifier.method-name parameter-list [ : return-type ] =
method-body
Remarks
In the previous syntax, you can see the various forms of method declarations and definitions. In longer method
bodies, a line break follows the equal sign (=), and the whole method body is indented.
Attributes can be applied to any method declaration. They precede the syntax for a method definition and are
usually listed on a separate line. For more information, see Attributes.
Methods can be marked inline . For information about inline , see Inline Functions.
Non-inline methods can be used recursively within the type; there is no need to explicitly use the rec keyword.
Instance Methods
Instance methods are declared with the member keyword and a self-identifier, followed by a period (.) and the
method name and parameters. As is the case for let bindings, the parameter-list can be a pattern. Typically,
you enclose method parameters in parentheses in a tuple form, which is the way methods appear in F# when
they are created in other .NET Framework languages. However, the curried form (parameters separated by
spaces) is also common, and other patterns are supported also.
The following example illustrates the definition and use of a non-abstract instance method.
member this.SomeOtherMethod(a, b, c) =
this.SomeMethod(a, b, c) * factor
Within instance methods, do not use the self identifier to access fields defined by using let bindings. Use the self
identifier when accessing other members and properties.
Static Methods
The keyword static is used to specify that a method can be called without an instance and is not associated
with an object instance. Otherwise, methods are instance methods.
The example in the next section shows fields declared with the let keyword, property members declared with
the member keyword, and a static method declared with the static keyword.
The following example illustrates the definition and use of static methods. Assume that these method definitions
are in the SomeType class in the previous section.
The following example illustrates a derived class that overrides a base class method. In this case, the override
changes the behavior so that the method does nothing.
Overloaded Methods
Overloaded methods are methods that have identical names in a given type but that have different arguments.
In F#, optional arguments are usually used instead of overloaded methods. However, overloaded methods are
permitted in the language, provided that the arguments are in tuple form, not curried form.
Optional Arguments
Starting with F# 4.1, you can also have optional arguments with a default parameter value in methods. This is to
help facilitate interoperation with C# code. The following example demonstrates the syntax:
Note that the value passed in for DefaultParameterValue must match the input type. In the above sample, it is an
int . Attempting to pass a non-integer value into DefaultParameterValue would result in a compile error.
// Test code.
let testIntersection =
let r1 = RectangleXY(10.0, 10.0, 20.0, 20.0)
let r2 = RectangleXY(15.0, 15.0, 25.0, 25.0)
let r3 : RectangleXY option = RectangleXY.intersection(r1, r2)
match r3 with
| Some(r3) -> printfn "Intersection rectangle: %f %f %f %f" r3.X1 r3.Y1 r3.X2 r3.Y2
| None -> printfn "No intersection found."
testIntersection
See also
Members
Constructors
11/2/2020 • 7 minutes to read • Edit Online
This article describes how to define and use constructors to create and initialize class and structure objects.
type MyStruct =
struct
val X : int
val Y : int
val Z : int
new(x, y, z) = { X = x; Y = y; Z = z }
end
The side effects of the primary constructor still execute. Therefore, the output is as follows:
The reason why then is required instead of another do is that the do keyword has its standard meaning of
delimiting a unit -returning expression when present in the body of an additional constructor. It only has
special meaning in the context of primary constructors.
Self identifiers in constructors
In other members, you provide a name for the current object in the definition of each member. You can also put
the self identifier on the first line of the class definition by using the as keyword immediately following the
constructor parameters. The following example illustrates this syntax.
In additional constructors, you can also define a self identifier by putting the as clause right after the
constructor parameters. The following example illustrates this syntax:
Problems can occur when you try to use an object before it is fully defined. Therefore, uses of the self identifier
can cause the compiler to emit a warning and insert additional checks to ensure the members of an object are
not accessed before the object is initialized. You should only use the self identifier in the do bindings of the
primary constructor, or after the then keyword in additional constructors.
The name of the self identifier does not have to be this . It can be any valid identifier.
type Account() =
let mutable balance = 0.0
let mutable number = 0
let mutable firstName = ""
let mutable lastName = ""
member this.AccountNumber
with get() = number
and set(value) = number <- value
member this.FirstName
with get() = firstName
and set(value) = firstName <- value
member this.LastName
with get() = lastName
and set(value) = lastName <- value
member this.Balance
with get() = balance
and set(value) = balance <- value
member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount
See also
Members
Events
3/6/2021 • 6 minutes to read • Edit Online
Events enable you to associate function calls with user actions and are important in GUI programming. Events
can also be triggered by your applications or by the operating system.
Handling Events
When you use a GUI library like Windows Forms or Windows Presentation Foundation (WPF), much of the code
in your application runs in response to events that are predefined by the library. These predefined events are
members of GUI classes such as forms and controls. You can add custom behavior to a preexisting event, such
as a button click, by referencing the specific named event of interest (for example, the Click event of the Form
class) and invoking the Add method, as shown in the following code. If you run this from F# Interactive, omit
the call to System.Windows.Forms.Application.Run(System.Windows.Forms.Form) .
open System.Windows.Forms
The type of the Add method is ('a -> unit) -> unit . Therefore, the event handler method takes one
parameter, typically the event arguments, and returns unit . The previous example shows the event handler as a
lambda expression. The event handler can also be a function value, as in the following code example. The
following code example also shows the use of the event handler parameters, which provide information specific
to the type of event. For a MouseMove event, the system passes a System.Windows.Forms.MouseEventArgs object,
which contains the X and Y position of the pointer.
open System.Windows.Forms
form.Click.Add(Beep)
form.MouseMove.Add(MouseMoveEventHandler)
Application.Run(form)
open System.Collections.Generic
type MyClassWithCLIEvent() =
[<CLIEvent>]
member this.Event1 = event1.Publish
member this.TestEvent(arg) =
event1.Trigger(this, arg)
classWithEvent.TestEvent("Hello World!")
The additional functionality provided by the Event module is illustrated here. The following code example
illustrates the basic use of Event.create to create an event and a trigger method, add two event handlers in the
form of lambda expressions, and then trigger the event to execute both lambda expressions.
type MyType() =
let myEvent = new Event<_>()
member this.AddHandlers() =
Event.add (fun string1 -> printfn "%s" string1) myEvent.Publish
Event.add (fun string1 -> printfn "Given a value: %s" string1) myEvent.Publish
member this.Trigger(message) =
myEvent.Trigger(message)
The Observable module contains similar functions that operate on observable objects. Observable objects are
similar to events but only actively subscribe to events if they themselves are being subscribed to.
open System.Windows.Forms
open System.ComponentModel
// Create a form, hook up the event handler, and start the application.
let appForm = new AppForm()
let inpc = appForm :> INotifyPropertyChanged
inpc.PropertyChanged.Add(appForm.OnPropertyChanged)
Application.Run(appForm)
If you want to hook up the event in the constructor, the code is a bit more complicated because the event
hookup must be in a then block in an additional constructor, as in the following example:
module CustomForm
open System.Windows.Forms
open System.ComponentModel
// This property does not have the property changed event set.
member val Property1 : string = "text" with get, set
[<CLIEvent>]
member this.PropertyChanged = propertyChanged.Publish
new() as this =
new AppForm(0)
then
let inpc = this :> INotifyPropertyChanged
inpc.PropertyChanged.Add(this.OnPropertyChanged)
// Create a form, hook up the event handler, and start the application.
let appForm = new AppForm()
Application.Run(appForm)
See also
Members
Handling and Raising Events
Lambda Expressions: The fun Keyword
Explicit Fields: The val Keyword
11/2/2020 • 4 minutes to read • Edit Online
The val keyword is used to declare a location to store a value in a class or structure type, without initializing it.
Storage locations declared in this manner are called explicit fields. Another use of the val keyword is in
conjunction with the member keyword to declare an auto-implemented property. For more information on auto-
implemented properties, see Properties.
Syntax
val [ mutable ] [ access-modifier ] field-name : type-name
Remarks
The usual way to define fields in a class or structure type is to use a let binding. However, let bindings must
be initialized as part of the class constructor, which is not always possible, necessary, or desirable. You can use
the val keyword when you want a field that is uninitialized.
Explicit fields can be static or non-static. The access-modifier can be public , private , or internal . By default,
explicit fields are public. This differs from let bindings in classes, which are always private.
The DefaultValue attribute is required on explicit fields in class types that have a primary constructor. This
attribute specifies that the field is initialized to zero. The type of the field must support zero-initialization. A type
supports zero-initialization if it is one of the following:
A primitive type that has a zero value.
A type that supports a null value, either as a normal value, as an abnormal value, or as a representation of a
value. This includes classes, tuples, records, functions, interfaces, .NET reference types, the unit type, and
discriminated union types.
A .NET value type.
A structure whose fields all support a default zero value.
For example, an immutable field called someField has a backing field in the .NET compiled representation with
the name someField@ , and you access the stored value using a property named someField .
For a mutable field, the .NET compiled representation is a .NET field.
WARNING
The .NET Framework namespace System.ComponentModel contains an attribute that has the same name. For information
about this attribute, see DefaultValueAttribute.
The following code shows the use of explicit fields and, for comparison, a let binding in a class that has a
primary constructor. Note that the let -bound field myInt1 is private. When the let -bound field myInt1 is
referenced from a member method, the self identifier this is not required. But when you are referencing the
explicit fields myInt2 and myString , the self identifier is required.
type MyType() =
let mutable myInt1 = 10
[<DefaultValue>] val mutable myInt2 : int
[<DefaultValue>] val mutable myString : string
member this.SetValsAndPrint( i: int, str: string) =
myInt1 <- i
this.myInt2 <- i + 1
this.myString <- str
printfn "%d %d %s" myInt1 (this.myInt2) (this.myString)
11 12 abc
30 def
The following code shows the use of explicit fields in a class that does not have a primary constructor. In this
case, the DefaultValue attribute is not required, but all the fields must be initialized in the constructors that are
defined for the type.
type MyClass =
val a : int
val b : int
// The following version of the constructor is an error
// because b is not initialized.
// new (a0, b0) = { a = a0; }
// The following version is acceptable because all fields are initialized.
new(a0, b0) = { a = a0; b = b0; }
The output is 35 22 .
The following code shows the use of explicit fields in a structure. Because a structure is a value type, it
automatically has a parameterless constructor that sets the values of its fields to zero. Therefore, the
DefaultValue attribute is not required.
type MyStruct =
struct
val mutable myInt : int
val mutable myString : string
end
[<Struct>]
type Foo =
val mutable bar: string
member self.ChangeBar bar = self.bar <- bar
new (bar) = {bar = bar}
Explicit fields are not intended for routine use. In general, when possible you should use a let binding in a
class instead of an explicit field. Explicit fields are useful in certain interoperability scenarios, such as when you
need to define a structure that will be used in a platform invoke call to a native API, or in COM interop scenarios.
For more information, see External Functions. Another situation in which an explicit field might be necessary is
when you are working with an F# code generator which emits classes without a primary constructor. Explicit
fields are also useful for thread-static variables or similar constructs. For more information, see
System.ThreadStaticAttribute .
When the keywords member val appear together in a type definition, it is a definition of an automatically
implemented property. For more information, see Properties.
See also
Properties
Members
let Bindings in Classes
Type extensions
3/6/2021 • 5 minutes to read • Edit Online
Type extensions (also called augmentations) are a family of features that let you add new members to a
previously defined object type. The three features are:
Intrinsic type extensions
Optional type extensions
Extension methods
Each can be used in different scenarios and has different tradeoffs.
Syntax
// Intrinsic and optional extensions
type typename with
member self-identifier.member-name =
body
...
// Extension methods
open System.Runtime.CompilerServices
[<Extension>]
type Extensions() =
[<Extension>]
static member extension-name (ty: typename, [args]) =
body
...
namespace Example
type Variant =
| Num of int
| Str of string
module Variant =
let print v =
match v with
| Num n -> printf "Num %d" n
| Str s -> printf "Str %s" s
module Extensions
You can now access RepeatElements as if it's a member of IEnumerable<T> as long as the Extensions module is
opened in the scope that you are working in.
Optional extensions do not appear on the extended type when examined by reflection. Optional extensions must
be in modules, and they're only in scope when the module that contains the extension is open or is otherwise in
scope.
Optional extension members are compiled to static members for which the object instance is passed implicitly
as the first parameter. However, they act as if they're instance members or static members according to how
they're declared.
Optional extension members are also not visible to C# or Visual Basic consumers. They can only be consumed in
other F# code.
There is no way to get this code to work with an optional type extension:
As is, the Sum member has a different constraint on 'T ( static member get_Zero and static member (+) )
than what the type extension defines.
Modifying the type extension to have the same constraint as Sum will no longer match the defined constraint
on IEnumerable<'T> .
Changing member this.Sum to member inline this.Sum will give an error that type constraints are
mismatched.
What is desired are static methods that "float in space" and can be presented as if they're extending a type. This
is where extension methods become necessary.
Extension methods
Finally, extension methods (sometimes called "C# style extension members") can be declared in F# as a static
member method on a class.
Extension methods are useful for when you wish to define extensions on a generic type that will constrain the
type variable. For example:
namespace Extensions
open System.Collections.Generic
open System.Runtime.CompilerServices
[<Extension>]
type IEnumerableExtensions =
[<Extension>]
static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs
When used, this code will make it appear as if Sum is defined on IEnumerable<T>, so long as Extensions has
been opened or is in scope.
For the extension to be available to VB.NET code, an extra ExtensionAttribute is required at the assembly level:
module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()
Other remarks
Type extensions also have the following attributes:
Any type that can be accessed can be extended.
Intrinsic and optional type extensions can define any member type, not just methods. So extension properties
are also possible, for example.
The self-identifier token in the syntax represents the instance of the type being invoked, just like ordinary
members.
Extended members can be static or instance members.
Type variables on a type extension must match the constraints of the declared type.
The following limitations also exist for type extensions:
Type extensions do not support virtual or abstract methods.
Type extensions do not support override methods as augmentations.
Type extensions do not support Statically Resolved Type Parameters.
Optional Type extensions do not support constructors as augmentations.
Type extensions cannot be defined on type abbreviations.
Type extensions are not valid for byref<'T> (though they can be declared).
Type extensions are not valid for attributes (though they can be declared).
You can define extensions that overload other methods of the same name, but the F# compiler gives
preference to non-extension methods if there is an ambiguous call.
Finally, if multiple intrinsic type extensions exist for one type, all members must be unique. For optional type
extensions, members in different type extensions to the same type can have the same names. Ambiguity errors
occur only if client code opens two different scopes that define the same member names.
See also
F# Language Reference
Members
Parameters and Arguments
3/6/2021 • 10 minutes to read • Edit Online
This topic describes language support for defining parameters and passing arguments to functions, methods,
and properties. It includes information about how to pass by reference, and how to define and use methods that
can take a variable number of arguments.
Parameter Patterns
Parameters supplied to functions and methods are, in general, patterns separated by spaces. This means that, in
principle, any of the patterns described in Match Expressions can be used in a parameter list for a function or
member.
Methods usually use the tuple form of passing arguments. This achieves a clearer result from the perspective of
other .NET languages because the tuple form matches the way arguments are passed in .NET methods.
The curried form is most often used with functions created by using let bindings.
The following pseudocode shows examples of tuple and curried arguments.
// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...
Combined forms are possible when some arguments are in tuples and some are not.
Other patterns can also be used in parameter lists, but if the parameter pattern does not match all possible
inputs, there might be an incomplete match at run time. The exception MatchFailureException is generated
when the value of an argument does not match the patterns specified in the parameter list. The compiler issues
a warning when a parameter pattern allows for incomplete matches. At least one other pattern is commonly
useful for parameter lists, and that is the wildcard pattern. You use the wildcard pattern in a parameter list when
you simply want to ignore any arguments that are supplied. The following code illustrates the use of the
wildcard pattern in an argument list.
[<EntryPoint>]
let main _ =
printfn "Entry point!"
0
Other patterns that are sometimes used in arguments are the as pattern, and identifier patterns associated
with discriminated unions and active patterns. You can use the single-case discriminated union pattern as
follows.
Active patterns can be useful as parameters, for example, when transforming an argument into a desired format,
as in the following example:
let (| Polar |) { x = x; y = y} =
( sqrt (x*x + y*y), System.Math.Atan (y/ x) )
You can use the as pattern to store a matched value as a local value, as is shown in the following line of code.
Another pattern that is used occasionally is a function that leaves the last argument unnamed by providing, as
the body of the function, a lambda expression that immediately performs a pattern match on the implicit
argument. An example of this is the following line of code.
This code defines a function that takes a generic list and returns true if the list is empty, and false otherwise.
The use of such techniques can make code more difficult to read.
Occasionally, patterns that involve incomplete matches are useful, for example, if you know that the lists in your
program have only three elements, you might use a pattern like the following in a parameter list.
let sum [a; b; c;] = a + b + c
The use of patterns that have incomplete matches is best reserved for quick prototyping and other temporary
uses. The compiler will issue a warning for such code. Such patterns cannot cover the general case of all possible
inputs and therefore are not suitable for component APIs.
Named Arguments
Arguments for methods can be specified by position in a comma-separated argument list, or they can be passed
to a method explicitly by providing the name, followed by an equal sign and the value to be passed in. If
specified by providing the name, they can appear in a different order from that used in the declaration.
Named arguments can make code more readable and more adaptable to certain types of changes in the API,
such as a reordering of method parameters.
Named arguments are allowed only for methods, not for let -bound functions, function values, or lambda
expressions.
The following code example demonstrates the use of named arguments.
type SpeedingTicket() =
member this.GetMPHOver(speed: int, limit: int) = speed - limit
In a call to a class constructor, you can set the values of properties of the class by using a syntax similar to that of
named arguments. The following example shows this syntax.
type Account() =
let mutable balance = 0.0
let mutable number = 0
let mutable firstName = ""
let mutable lastName = ""
member this.AccountNumber
with get() = number
and set(value) = number <- value
member this.FirstName
with get() = firstName
and set(value) = firstName <- value
member this.LastName
with get() = lastName
and set(value) = lastName <- value
member this.Balance
with get() = balance
and set(value) = balance <- value
member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount
You can also use a function defaultArg , which sets a default value of an optional argument. The defaultArg
function takes the optional parameter as the first argument and the default value as the second.
The following example illustrates the use of optional parameters.
type DuplexType =
| Full
| Half
For the purposes of C# and Visual Basic interop you can use the attributes
[<Optional; DefaultParameterValue<(...)>] in F#, so that callers will see an argument as optional. This is
equivalent to defining the argument as optional in C# as in MyMethod(int i = 3) .
open System
open System.Runtime.InteropServices
type C =
static member Foo([<Optional; DefaultParameterValue("Hello world")>] message) =
printfn $"{message}"
You can also specify a new object as a default parameter value. For example, the Foo member could have an
optional CancellationToken as input instead:
open System.Threading
open System.Runtime.InteropServices
type C =
static member Foo([<Optional; DefaultParameterValue(CancellationToken())>] ct: CancellationToken) =
printfn $"{ct}"
The value given as argument to DefaultParameterValue must match the type of the parameter. For example, the
following is not allowed:
type C =
static member Wrong([<Optional; DefaultParameterValue("string")>] i:int) = ()
In this case, the compiler generates a warning and will ignore both attributes altogether. Note that the default
value null needs to be type-annotated, as otherwise the compiler infers the wrong type, i.e.
[<Optional; DefaultParameterValue(null:obj)>] o:obj .
Passing by Reference
Passing an F# value by reference involves byrefs, which are managed pointer types. Guidance for which type to
use is as follows:
Use inref<'T> if you only need to read the pointer.
Use outref<'T> if you only need to write to the pointer.
Use byref<'T> if you need to both read from and write to the pointer.
let test () =
// No need to make it mutable, since it's read-only
let x = 1
example1 &x
Because the parameter is a pointer and the value is mutable, any changes to the value are retained after the
execution of the function.
You can use a tuple as a return value to store any out parameters in .NET library methods. Alternatively, you
can treat the out parameter as a byref parameter. The following code example illustrates both ways.
// TryParse has a second parameter that is an out parameter
// of type System.DateTime.
let (b, dt) = System.DateTime.TryParse("12-20-04 12:21:00")
Parameter Arrays
Occasionally it is necessary to define a function that takes an arbitrary number of parameters of heterogeneous
type. It would not be practical to create all the possible overloaded methods to account for all the types that
could be used. The .NET implementations provide support for such methods through the parameter array
feature. A method that takes a parameter array in its signature can be provided with an arbitrary number of
parameters. The parameters are put into an array. The type of the array elements determines the parameter
types that can be passed to the function. If you define the parameter array with System.Object as the element
type, then client code can pass values of any type.
In F#, parameter arrays can only be defined in methods. They cannot be used in standalone functions or
functions that are defined in modules.
You define a parameter array by using the ParamArray attribute. The ParamArray attribute can only be applied to
the last parameter.
The following code illustrates both calling a .NET method that takes a parameter array and the definition of a
type in F# that has a method that takes a parameter array.
open System
type X() =
member this.F([<ParamArray>] args: Object[]) =
for arg in args do
printfn "%A" arg
[<EntryPoint>]
let main _ =
// call a .NET method that takes a parameter array, passing values of various types
Console.WriteLine("a {0} {1} {2} {3} {4}", 1, 10.0, "Hello world", 1u, true)
This topic describes how to overload arithmetic operators in a class or record type, and at the global level.
Syntax
// Overloading an operator as a class or record member.
static member (operator-symbols) (parameter-list) =
method-body
// Overloading an operator at the global level
let [inline] (operator-symbols) parameter-list = function-body
Remarks
In the previous syntax, the operator-symbol is one of + , - , * , / , = , and so on. The parameter-list specifies
the operands in the order they appear in the usual syntax for that operator. The method-body constructs the
resulting value.
Operator overloads for operators must be static. Operator overloads for unary operators, such as + and - ,
must use a tilde ( ~ ) in the operator-symbol to indicate that the operator is a unary operator and not a binary
operator, as shown in the following declaration.
The following code illustrates a vector class that has just two operators, one for unary minus and one for
multiplication by a scalar. In the example, two overloads for scalar multiplication are needed because the
operator must work regardless of the order in which the vector and scalar appear.
let v2 = v1 * 2.0
let v3 = 2.0 * v1
let v4 = - v2
[] op_Nil
:: op_Cons
+ op_Addition
- op_Subtraction
* op_Multiply
/ op_Division
@ op_Append
^ op_Concatenate
% op_Modulus
&&& op_BitwiseAnd
||| op_BitwiseOr
^^^ op_ExclusiveOr
O P ERATO R GEN ERAT ED N A M E
<<< op_LeftShift
~~~ op_LogicalNot
>>> op_RightShift
~+ op_UnaryPlus
~- op_UnaryNegation
= op_Equality
<= op_LessThanOrEqual
>= op_GreaterThanOrEqual
< op_LessThan
> op_GreaterThan
? op_Dynamic
?<- op_DynamicAssignment
|> op_PipeRight
<| op_PipeLeft
! op_Dereference
>> op_ComposeRight
<< op_ComposeLeft
+= op_AdditionAssignment
-= op_SubtractionAssignment
*= op_MultiplyAssignment
/= op_DivisionAssignment
.. op_Range
O P ERATO R GEN ERAT ED N A M E
.. .. op_RangeStep
Note that the not operator in F# does not emit op_Inequality because it is not a symbolic operator. It is a
function that emits IL that negates a boolean expression.
Other combinations of operator characters that are not listed here can be used as operators and have names
that are made up by concatenating names for the individual characters from the following table. For example, +!
becomes op_PlusBang .
O P ERATO R C H A RA C T ER NAME
> Greater
< Less
+ Plus
- Minus
* Multiply
/ Divide
= Equals
~ Twiddle
$ Dollar
% Percent
. Dot
& Amp
| Bar
@ At
^ Hat
! Bang
? Qmark
( LParen
, Comma
O P ERATO R C H A RA C T ER NAME
) RParen
[ LBrack
] RBrack
Example
The following code illustrates the use of operator overloading to implement a fraction type. A fraction is
represented by a numerator and a denominator. The function hcf is used to determine the highest common
factor, which is used to reduce fractions.
Output:
See also
Members
Flexible Types
11/2/2020 • 2 minutes to read • Edit Online
A flexible type annotation indicates that a parameter, variable, or value has a type that is compatible with a
specified type, where compatibility is determined by position in an object-oriented hierarchy of classes or
interfaces. Flexible types are useful specifically when the automatic conversion to types higher in the type
hierarchy does not occur but you still want to enable your functionality to work with any type in the hierarchy or
any type that implements an interface.
Syntax
#type
Remarks
In the previous syntax, type represents a base type or an interface.
A flexible type is equivalent to a generic type that has a constraint that limits the allowed types to types that are
compatible with the base or interface type. That is, the following two lines of code are equivalent.
#SomeType
Flexible types are useful in several types of situations. For example, when you have a higher order function (a
function that takes a function as an argument), it is often useful to have the function return a flexible type. In the
following example, the use of a flexible type with a sequence argument in iterate2 enables the higher order
function to work with functions that generate sequences, arrays, lists, and any other enumerable type.
Consider the following two functions, one of which returns a sequence, the other of which returns a flexible type.
You can pass any of the following enumerable sequences to this function:
A list of lists
A list of arrays
An array of lists
An array of sequences
Any other combination of enumerable sequences
The following code uses Seq.concat to demonstrate the scenarios that you can support by using flexible types.
let seq1 = { 1 .. 3 }
let seq2 = { 4 .. 6 }
let seq3 = { 7 .. 9 }
In F#, as in other object-oriented languages, there are contexts in which derived types or types that implement
interfaces are automatically converted to a base type or interface type. These automatic conversions occur in
direct arguments, but not when the type is in a subordinate position, as part of a more complex type such as a
return type of a function type, or as a type argument. Thus, the flexible type notation is primarily useful when the
type you are applying it to is part of a more complex type.
See also
F# Language Reference
Generics
Delegates
7/30/2019 • 3 minutes to read • Edit Online
A delegate represents a function call as an object. In F#, you ordinarily should use function values to represent
functions as first-class values; however, delegates are used in the .NET Framework and so are needed when you
interoperate with APIs that expect them. They may also be used when authoring libraries designed for use from
other .NET Framework languages.
Syntax
type delegate-typename = delegate of type1 -> type2
Remarks
In the previous syntax, type1 represents the argument type or types and type2 represents the return type. The
argument types that are represented by type1 are automatically curried. This suggests that for this type you
use a tuple form if the arguments of the target function are curried, and a parenthesized tuple for arguments
that are already in the tuple form. The automatic currying removes a set of parentheses, leaving a tuple
argument that matches the target method. Refer to the code example for the syntax you should use in each case.
Delegates can be attached to F# function values, and static or instance methods. F# function values can be
passed directly as arguments to delegate constructors. For a static method, you construct the delegate by using
the name of the class and the method. For an instance method, you provide the object instance and method in
one argument. In both cases, the member access operator ( . ) is used.
The Invoke method on the delegate type calls the encapsulated function. Also, delegates can be passed as
function values by referencing the Invoke method name without the parentheses.
The following code shows the syntax for creating delegates that represent various methods in a class.
Depending on whether the method is a static method or an instance method, and whether it has arguments in
the tuple form or the curried form, the syntax for declaring and assigning the delegate is a little different.
type Test1() =
static member add(a : int, b : int) =
a + b
static member add2 (a : int) (b : int) =
a + b
// For static methods, use the class name, the dot operator, and the
// name of the static method.
let del1 = Delegate1(Test1.add)
let del2 = Delegate2(Test1.add2)
// For instance methods, use the instance value name, the dot operator, and the instance method name.
let del3 = Delegate1(testObject.Add)
let del4 = Delegate2(testObject.Add2)
The following code shows some of the different ways you can work with delegates.
type Delegate1 = delegate of int * char -> string
// You can pass a lambda expression as an argument to a function expecting a compatible delegate type
// System.Array.ConvertAll takes an array and a converter delegate that transforms an element from
// one type to another according to a specified function.
let stringArray = System.Array.ConvertAll([|'a';'b'|], fun c -> replicate' 3 c)
printfn "%A" stringArray
aaaaa
bbbbb
ccccc
[|"aaa"; "bbb"|]
See also
F# Language Reference
Parameters and Arguments
Events
Object Expressions
3/6/2021 • 2 minutes to read • Edit Online
An object expression is an expression that creates a new instance of a dynamically created, anonymous object
type that is based on an existing base type, interface, or set of interfaces.
Syntax
// When typename is a class:
{ new typename [type-params]arguments with
member-definitions
[ additional-interface-definitions ]
}
// When typename is not a class:
{ new typename [generic-type-args] with
member-definitions
[ additional-interface-definitions ]
}
Remarks
In the previous syntax, the typename represents an existing class type or interface type. type-params describes
the optional generic type parameters. The arguments are used only for class types, which require constructor
parameters. The member-definitions are overrides of base class methods, or implementations of abstract
methods from either a base class or an interface.
The following example illustrates several different types of object expressions.
// This object expression specifies a System.Object but overrides the
// ToString method.
let obj1 = { new System.Object() with member x.ToString() = "F#" }
printfn $"{obj1}"
type ISecond =
inherit IFirst
abstract H : unit -> unit
abstract J : unit -> unit
See also
F# Language Reference
Casting and conversions (F#)
6/19/2021 • 6 minutes to read • Edit Online
Arithmetic Types
F# provides conversion operators for arithmetic conversions between various primitive types, such as between
integer and floating point types. The integral and char conversion operators have checked and unchecked forms;
the floating point operators and the enum conversion operator do not. The unchecked forms are defined in
Microsoft.FSharp.Core.Operators and the checked forms are defined in
Microsoft.FSharp.Core.Operators.Checked . The checked forms check for overflow and generate a runtime
exception if the resulting value exceeds the limits of the target type.
Each of these operators has the same name as the name of the destination type. For example, in the following
code, in which the types are explicitly annotated, byte appears with two different meanings. The first
occurrence is the type and the second is the conversion operator.
let x : int = 5
In addition to built-in primitive types, you can use these operators with types that implement op_Explicit or
op_Implicit methods with appropriate signatures. For example, the int conversion operator works with any
type that provides a static method op_Explicit that takes the type as a parameter and returns int . As a special
exception to the general rule that methods cannot be overloaded by return type, you can do this for
op_Explicit and op_Implicit .
Enumerated Types
The enum operator is a generic operator that takes one type parameter that represents the type of the enum to
convert to. When it converts to an enumerated type, type inference attempts to determine the type of the enum
that you want to convert to. In the following example, the variable col1 is not explicitly annotated, but its type
is inferred from the later equality test. Therefore, the compiler can deduce that you are converting to a Color
enumeration. Alternatively, you can supply a type annotation, as with col2 in the following example.
type Color =
| Red = 1
| Green = 2
| Blue = 3
// The target type of the conversion cannot be determined by type inference, so the type parameter must be
explicit.
let col1 = enum<Color> 1
You can also specify the target enumeration type explicitly as a type parameter, as in the following code:
Note that the enumeration casts work only if the underlying type of the enumeration is compatible with the type
being converted. In the following code, the conversion fails to compile because of the mismatch between int32
and uint32 .
upcast expression
When you use the upcast operator, the compiler attempts to infer the type you are converting to from the
context. If the compiler is unable to determine the target type, the compiler reports an error. A type annotation
may be required.
Downcasting
The :?> operator performs a dynamic cast, which means that the success of the cast is determined at run time.
A cast that uses the :?> operator is not checked at compile time; but at run time, an attempt is made to cast to
the specified type. If the object is compatible with the target type, the cast succeeds. If the object is not
compatible with the target type, the runtime raises an InvalidCastException .
You can also use the downcast operator to perform a dynamic type conversion. The following expression
specifies a conversion down the hierarchy to a type that is inferred from program context:
downcast expression
As for the upcast operator, if the compiler cannot infer a specific target type from the context, it reports an
error. A type annotation may be required.
The following code illustrates the use of the :> and :?> operators. The code illustrates that the :?> operator
is best used when you know that conversion will succeed, because it throws InvalidCastException if the
conversion fails. If you do not know that a conversion will succeed, a type test that uses a match expression is
better because it avoids the overhead of generating an exception.
type Base1() =
abstract member F : unit -> unit
default u.F() =
printfn "F Base1"
type Derived1() =
inherit Base1()
override u.F() =
printfn "F Derived1"
// Upcast to Base1.
let base1 = d1 :> Base1
downcastBase1 base1
Because the generic operators downcast and upcast rely on type inference to determine the argument and
return type, you can replace let base1 = d1 :> Base1 in the previous code example with
let base1: Base1 = upcast d1 .
A type annotation is required, because upcast by itself could not determine the base class.
See also
F# Language Reference
Access Control
11/1/2019 • 3 minutes to read • Edit Online
Access control refers to declaring which clients can use certain program elements, such as types, methods, and
functions.
NOTE
The access specifier protected is not used in F#, although it is acceptable if you are using types authored in languages
that do support protected access. Therefore, if you override a protected method, your method remains accessible only
within the class and its descendents.
In general, the specifier is put in front of the name of the entity, except when a mutable or inline specifier is
used, which appear after the access control specifier.
If no access specifier is used, the default is public , except for let bindings in a type, which are always
private to the type.
Signatures in F# provide another mechanism for controlling access to F# program elements. Signatures are not
required for access control. For more information, see Signatures.
Example
The following code illustrates the use of access control specifiers. There are two files in the project, Module1.fs
and Module2.fs . Each file is implicitly a module. Therefore, there are two modules, Module1 and Module2 . A
private type and an internal type are defined in Module1 . The private type cannot be accessed from Module2 ,
but the internal type can.
// Module1.fs
module Module1
The following code tests the accessibility of the types created in Module1.fs .
// Module2.fs
module Module2
open Module1
See also
F# Language Reference
Signatures
Conditional Expressions: if...then...else
9/18/2019 • 2 minutes to read • Edit Online
The if...then...else expression runs different branches of code and also evaluates to a different value
depending on the Boolean expression given.
Syntax
if boolean-expression then expression1 [ else expression2 ]
Remarks
In the previous syntax, expression1 runs when the Boolean expression evaluates to true ; otherwise,
expression2 runs.
Unlike in other languages, the if...then...else construct is an expression, not a statement. That means that it
produces a value, which is the value of the last expression in the branch that executes. The types of the values
produced in each branch must match. If there is no explicit else branch, its type is unit . Therefore, if the type
of the then branch is any type other than unit , there must be an else branch with the same return type.
When chaining if...then...else expressions together, you can use the keyword elif instead of else if ; they
are equivalent.
Example
The following example illustrates how to use the if...then...else expression.
let test x y =
if x = y then "equals"
elif x < y then "is less than"
else "is greater than"
if age < 10
then printfn "You are only %d years old and already learning F#? Wow!" age
10 is less than 20
What is your name? John
How old are you? 9
You are only 9 years old and already learning F#? Wow!
See also
F# Language Reference
Match expressions
7/30/2019 • 3 minutes to read • Edit Online
The match expression provides branching control that is based on the comparison of an expression with a set of
patterns.
Syntax
// Match expression.
match test-expression with
| pattern1 [ when condition ] -> result-expression1
| pattern2 [ when condition ] -> result-expression2
| ...
Remarks
The pattern matching expressions allow for complex branching based on the comparison of a test expression
with a set of patterns. In the match expression, the test-expression is compared with each pattern in turn, and
when a match is found, the corresponding result-expression is evaluated and the resulting value is returned as
the value of the match expression.
The pattern matching function shown in the previous syntax is a lambda expression in which pattern matching is
performed immediately on the argument. The pattern matching function shown in the previous syntax is
equivalent to the following.
For more information about lambda expressions, see Lambda Expressions: The fun Keyword.
The whole set of patterns should cover all the possible matches of the input variable. Frequently, you use the
wildcard pattern ( _ ) as the last pattern to match any previously unmatched input values.
The following code illustrates some of the ways in which the match expression is used. For a reference and
examples of all the possible patterns that can be used, see Pattern Matching.
let list1 = [ 1; 5; 100; 450; 788 ]
printList list1
Guards on patterns
You can use a when clause to specify an additional condition that the variable must satisfy to match a pattern.
Such a clause is referred to as a guard. The expression following the when keyword is not evaluated unless a
match is made to the pattern associated with that guard.
The following example illustrates the use of a guard to specify a numeric range for a variable pattern. Note that
multiple conditions are combined by using Boolean operators.
rangeTest 10 20 5
rangeTest 10 20 10
rangeTest 10 20 40
Note that because values other than literals cannot be used in the pattern, you must use a when clause if you
have to compare some part of the input against a value. This is shown in the following code:
Note that when a union pattern is covered by a guard, the guard applies to all of the patterns, not just the last
one. For example, given the following code, the guard when a > 12 applies to both A a and B a :
type Union =
| A of int
| B of int
let foo() =
let test = A 42
match test with
| A a
| B a when a > 41 -> a // the guard applies to both patterns
| _ -> 1
foo() // returns 42
See also
F# Language Reference
Active Patterns
Pattern Matching
Pattern Matching
3/6/2021 • 13 minutes to read • Edit Online
Patterns are rules for transforming input data. They are used throughout the F# language to compare data with
a logical structure or structures, decompose data into constituent parts, or extract information from data in
various ways.
Remarks
Patterns are used in many language constructs, such as the match expression. They are used when you are
processing arguments for functions in let bindings, lambda expressions, and in the exception handlers
associated with the try...with expression. For more information, see Match Expressions, let Bindings, Lambda
Expressions: The fun Keyword, and Exceptions: The try...with Expression.
For example, in the match expression, the pattern is what follows the pipe symbol.
Each pattern acts as a rule for transforming input in some way. In the match expression, each pattern is
examined in turn to see if the input data is compatible with the pattern. If a match is found, the result expression
is executed. If a match is not found, the next pattern rule is tested. The optional when condition part is explained
in Match Expressions.
Supported patterns are shown in the following table. At run time, the input is tested against each of the
following patterns in the order listed in the table, and patterns are applied recursively, from first to last as they
appear in your code, and from left to right for the patterns on each line.
Constant pattern Any numeric, character, or string literal, 1.0 , "test" , 30 , Color.Red
an enumeration constant, or a defined
literal identifier
Wildcard pattern _ _
Constant Patterns
Constant patterns are numeric, character, and string literals, enumeration constants (with the enumeration type
name included). A match expression that has only constant patterns can be compared to a case statement in
other languages. The input is compared with the literal value and the pattern matches if the values are equal.
The type of the literal must be compatible with the type of the input.
The following example demonstrates the use of literal patterns, and also uses a variable pattern and an OR
pattern.
[<Literal>]
let Three = 3
let filter123 x =
match x with
// The following line contains literal patterns combined with an OR pattern.
| 1 | 2 | Three -> printfn "Found 1, 2, or 3!"
// The following line contains a variable pattern.
| var1 -> printfn "%d" var1
Another example of a literal pattern is a pattern based on enumeration constants. You must specify the
enumeration type name when you use enumeration constants.
type Color =
| Red = 0
| Green = 1
| Blue = 2
printColorName Color.Red
printColorName Color.Green
printColorName Color.Blue
Identifier Patterns
If the pattern is a string of characters that forms a valid identifier, the form of the identifier determines how the
pattern is matched. If the identifier is longer than a single character and starts with an uppercase character, the
compiler tries to make a match to the identifier pattern. The identifier for this pattern could be a value marked
with the Literal attribute, a discriminated union case, an exception identifier, or an active pattern case. If no
matching identifier is found, the match fails and the next pattern rule, the variable pattern, is compared to the
input.
Discriminated union patterns can be simple named cases or they can have a value, or a tuple containing multiple
values. If there is a value, you must specify an identifier for the value. In the case of a tuple, you must supply a
tuple pattern with an identifier for each element of the tuple or an identifier with a field name for one or more
named union fields. See the code examples in this section for examples.
The option type is a discriminated union that has two cases, Some and None . One case ( Some ) has a value, but
the other ( None ) is just a named case. Therefore, Some needs to have a variable for the value associated with
the Some case, but None must appear by itself. In the following code, the variable var1 is given the value that
is obtained by matching to the Some case.
In the following example, the PersonName discriminated union contains a mixture of strings and characters that
represent possible forms of names. The cases of the discriminated union are FirstOnly , LastOnly , and
FirstLast .
type PersonName =
| FirstOnly of string
| LastOnly of string
| FirstLast of string * string
For discriminated unions that have named fields, you use the equals sign (=) to extract the value of a named
field. For example, consider a discriminated union with a declaration like the following.
type Shape =
| Rectangle of height : float * width : float
| Circle of radius : float
You can use the named fields in a pattern matching expression as follows.
The use of the named field is optional, so in the previous example, both Circle(r) and Circle(radius = r)
have the same effect.
When you specify multiple fields, use the semicolon (;) as a separator.
Active patterns enable you to define more complex custom pattern matching. For more information about active
patterns, see Active Patterns.
The case in which the identifier is an exception is used in pattern matching in the context of exception handlers.
For information about pattern matching in exception handling, see Exceptions: The try...with Expression.
Variable Patterns
The variable pattern assigns the value being matched to a variable name, which is then available for use in the
execution expression to the right of the -> symbol. A variable pattern alone matches any input, but variable
patterns often appear within other patterns, therefore enabling more complex structures such as tuples and
arrays to be decomposed into variables.
The following example demonstrates a variable pattern within a tuple pattern.
let function1 x =
match x with
| (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
| (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
| (var1, var2) -> printfn "%d equals %d" var1 var2
function1 (1,2)
function1 (2, 1)
function1 (0, 0)
as Pattern
The as pattern is a pattern that has an as clause appended to it. The as clause binds the matched value to a
name that can be used in the execution expression of a match expression, or, in the case where this pattern is
used in a let binding, the name is added as a binding to the local scope.
The following example uses an as pattern.
let (var1, var2) as tuple1 = (1, 2)
printfn "%d %d %A" var1 var2 tuple1
OR Pattern
The OR pattern is used when input data can match multiple patterns, and you want to execute the same code as
a result. The types of both sides of the OR pattern must be compatible.
The following example demonstrates the OR pattern.
AND Pattern
The AND pattern requires that the input match two patterns. The types of both sides of the AND pattern must be
compatible.
The following example is like detectZeroTuple shown in the Tuple Pattern section later in this topic, but here
both var1 and var2 are obtained as values by using the AND pattern.
Cons Pattern
The cons pattern is used to decompose a list into the first element, the head, and a list that contains the
remaining elements, the tail.
let list1 = [ 1; 2; 3; 4 ]
printList list1
List Pattern
The list pattern enables lists to be decomposed into a number of elements. The list pattern itself can match only
lists of a specific number of elements.
Array Pattern
The array pattern resembles the list pattern and can be used to decompose arrays of a specific length.
Parenthesized Pattern
Parentheses can be grouped around patterns to achieve the desired associativity. In the following example,
parentheses are used to control associativity between an AND pattern and a cons pattern.
Tuple Pattern
The tuple pattern matches input in tuple form and enables the tuple to be decomposed into its constituent
elements by using pattern matching variables for each position in the tuple.
The following example demonstrates the tuple pattern and also uses literal patterns, variable patterns, and the
wildcard pattern.
let detectZeroTuple point =
match point with
| (0, 0) -> printfn "Both values zero."
| (0, var2) -> printfn "First value is 0 in (0, %d)" var2
| (var1, 0) -> printfn "Second value is 0 in (%d, 0)" var1
| _ -> printfn "Both nonzero."
detectZeroTuple (0, 0)
detectZeroTuple (1, 0)
detectZeroTuple (0, 10)
detectZeroTuple (10, 15)
Record Pattern
The record pattern is used to decompose records to extract the values of fields. The pattern does not have to
reference all fields of the record; any omitted fields just do not participate in matching and are not extracted.
Wildcard Pattern
The wildcard pattern is represented by the underscore ( _ ) character and matches any input, just like the
variable pattern, except that the input is discarded instead of assigned to a variable. The wildcard pattern is often
used within other patterns as a placeholder for values that are not needed in the expression to the right of the
-> symbol. The wildcard pattern is also frequently used at the end of a list of patterns to match any unmatched
input. The wildcard pattern is demonstrated in many code examples in this topic. See the preceding code for one
example.
let detect1 x =
match x with
| 1 -> printfn "Found a 1!"
| (var1 : int) -> printfn "%d" var1
detect1 0
detect1 1
open System.Windows.Forms
let RegisterControl(control:Control) =
match control with
| :? Button as button -> button.Text <- "Registered."
| :? CheckBox as checkbox -> checkbox.Text <- "Registered."
| _ -> ()
If you're only checking if an identifier is of a particular derived type, you don't need the as identifier part of
the pattern, as shown in the following example:
let m (a: A) =
match a with
| :? B -> printfn "It's a B"
| :? C -> printfn "It's a C"
| _ -> ()
Null Pattern
The null pattern matches the null value that can appear when you are working with types that allow a null value.
Null patterns are frequently used when interoperating with .NET Framework code. For example, the return value
of a .NET API might be the input to a match expression. You can control program flow based on whether the
return value is null, and also on other characteristics of the returned value. You can use the null pattern to
prevent null values from propagating to the rest of your program.
The following example uses the null pattern and the variable pattern.
Nameof pattern
The nameof pattern matches against a string when its value is equal to the expression that follows the nameof
keyword. for example:
f "str" // matches
f "asdf" // does not match
See the nameof operator for information on what you can take a name of.
See also
Match Expressions
Active Patterns
F# Language Reference
Active Patterns
11/2/2020 • 7 minutes to read • Edit Online
Active patterns enable you to define named partitions that subdivide input data, so that you can use these
names in a pattern matching expression just as you would for a discriminated union. You can use active patterns
to decompose data in a customized manner for each partition.
Syntax
// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression
Remarks
In the previous syntax, the identifiers are names for partitions of the input data that is represented by
arguments, or, in other words, names for subsets of the set of all values of the arguments. There can be up to
seven partitions in an active pattern definition. The expression describes the form into which to decompose the
data. You can use an active pattern definition to define the rules for determining which of the named partitions
the values given as arguments belong to. The (| and |) symbols are referred to as banana clips and the function
created by this type of let binding is called an active recognizer.
As an example, consider the following active pattern with an argument.
You can use the active pattern in a pattern matching expression, as in the following example.
TestNumber 7
TestNumber 11
TestNumber 32
7 is odd
11 is odd
32 is even
Another use of active patterns is to decompose data types in multiple ways, such as when the same underlying
data has various possible representations. For example, a Color object could be decomposed into an RGB
representation or an HSB representation.
open System.Drawing
Red
Red: 255 Green: 0 Blue: 0
Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
Red: 0 Green: 0 Blue: 0
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
Red: 255 Green: 255 Blue: 255
Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
Red: 128 Green: 128 Blue: 128
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
Red: 255 Green: 235 Blue: 205
Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961
In combination, these two ways of using active patterns enable you to partition and decompose data into just
the appropriate form and perform the appropriate computations on the appropriate data in the form most
convenient for the computation.
The resulting pattern matching expressions enable data to be written in a convenient way that is very readable,
greatly simplifying potentially complex branching and data analysis code.
parseNumeric "1.1"
parseNumeric "0"
parseNumeric "0.0"
parseNumeric "10"
parseNumeric "Something else"
When using partial active patterns, sometimes the individual choices can be disjoint or mutually exclusive, but
they need not be. In the following example, the pattern Square and the pattern Cube are not disjoint, because
some numbers are both squares and cubes, such as 64. The following program uses the AND pattern to
combine the Square and Cube patterns. It prints out all integers up to 1000 that are both squares and cubes, as
well as those which are only cubes.
let findSquareCubes x =
match x with
| Cube x & Square _ -> printfn "%d is a cube and a square" x
| Cube x -> printfn "%d is a cube" x
| _ -> ()
open System.Text.RegularExpressions
// ParseRegex parses a regular expression and returns a list of the strings that match each group in
// the regular expression.
// List.tail is called to eliminate the first element in the list, which is the full matched expression,
// since only the matches for each group are wanted.
let (|ParseRegex|_|) regex str =
let m = Regex(regex).Match(str)
if m.Success
then Some (List.tail [ for x in m.Groups -> x.Value ])
else None
// Three different date formats are demonstrated here. The first matches two-
// digit dates and the second matches full dates. This code assumes that if a two-digit
// date is provided, it is an abbreviation, not a year in the first century.
let parseDate str =
match str with
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
-> new System.DateTime(y + 2000, m, d)
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
-> new System.DateTime(y, m, d)
| ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
-> new System.DateTime(y, m, d)
| _ -> new System.DateTime()
Active patterns are not restricted only to pattern matching expressions, you can also use them on let-bindings.
let (|Default|) onNone value =
match value with
| None -> onNone
| Some e -> e
greet None
greet (Some "George")
See also
F# Language Reference
Match Expressions
Loops: for...to Expression
11/21/2019 • 2 minutes to read • Edit Online
The for...to expression is used to iterate in a loop over a range of values of a loop variable.
Syntax
for identifier = start [ to | downto ] finish do
body-expression
Remarks
The type of the identifier is inferred from the type of the start and finish expressions. Types for these expressions
must be 32-bit integers.
Although technically an expression, for...to is more like a traditional statement in an imperative
programming language. The return type for the body-expression must be unit . The following examples show
various uses of the for...to expression.
function1()
function2()
// A for...to loop that uses functions as the start and finish expressions.
let beginning x y = x - 2*y
let ending x y = x + 2*y
let function3 x y =
for i = (beginning x y) to (ending x y) do
printf "%d " i
printfn ""
function3 10 4
1 2 3 4 5 6 7 8 9 10
10 9 8 7 6 5 4 3 2 1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
See also
F# Language Reference
Loops: for...in Expression
Loops: while...do Expression
Loops: for...in Expression
9/24/2019 • 3 minutes to read • Edit Online
This looping construct is used to iterate over the matches of a pattern in an enumerable collection such as a
range expression, sequence, list, array, or other construct that supports enumeration.
Syntax
for pattern in enumerable-expression do
body-expression
Remarks
The for...in expression can be compared to the for each statement in other .NET languages because it is
used to loop over the values in an enumerable collection. However, for...in also supports pattern matching
over the collection instead of just iteration over the whole collection.
The enumerable expression can be specified as an enumerable collection or, by using the .. operator, as a
range on an integral type. Enumerable collections include lists, sequences, arrays, sets, maps, and so on. Any
type that implements System.Collections.IEnumerable can be used.
When you express a range by using the .. operator, you can use the following syntax.
start .. finish
You can also use a version that includes an increment called the skip, as in the following code.
start .. skip .. finish
When you use integral ranges and a simple counter variable as a pattern, the typical behavior is to increment
the counter variable by 1 on each iteration, but if the range includes a skip value, the counter is incremented by
the skip value instead.
Values matched in the pattern can also be used in the body expression.
The following code examples illustrate the use of the for...in expression.
1
5
100
450
788
The following example shows how to loop over a sequence, and how to use a tuple pattern instead of a simple
variable.
let seq1 = seq { for i in 1 .. 10 -> (i, i*i) }
for (a, asqr) in seq1 do
printfn "%d squared is %d" a asqr
1 squared is 1
2 squared is 4
3 squared is 9
4 squared is 16
5 squared is 25
6 squared is 36
7 squared is 49
8 squared is 64
9 squared is 81
10 squared is 100
The following example shows how to loop over a simple integer range.
let function1() =
for i in 1 .. 10 do
printf "%d " i
printfn ""
function1()
1 2 3 4 5 6 7 8 9 10
The following example shows how to loop over a range with a skip of 2, which includes every other element of
the range.
let function2() =
for i in 1 .. 2 .. 10 do
printf "%d " i
printfn ""
function2()
1 3 5 7 9
let function3() =
for c in 'a' .. 'z' do
printf "%c " c
printfn ""
function3()
a b c d e f g h i j k l m n o p q r s t u v w x y z
The following example shows how to use a negative skip value for a reverse iteration.
let function4() =
for i in 10 .. -1 .. 1 do
printf "%d " i
printfn " ... Lift off!"
function4()
The beginning and ending of the range can also be expressions, such as functions, as in the following code.
let function5 x y =
for i in (beginning x y) .. (ending x y) do
printf "%d " i
printfn ""
function5 10 4
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
The next example shows the use of a wildcard character (_) when the element is not needed in the loop.
Note You can use for...in in sequence expressions and other computation expressions, in which case a
customized version of the for...in expression is used. For more information, see Sequences, Asynchronous
Workflows, and Computation Expressions.
See also
F# Language Reference
Loops: for...to Expression
Loops: while...do Expression
Loops: while...do Expression
9/24/2019 • 2 minutes to read • Edit Online
The while...do expression is used to perform iterative execution (looping) while a specified test condition is
true.
Syntax
while test-expression do
body-expression
Remarks
The test-expression is evaluated; if it is true , the body-expression is executed and the test expression is
evaluated again. The body-expression must have type unit . If the test expression is false , the iteration ends.
The following example illustrates the use of the while...do expression.
open System
lookForValue 10 20
The output of the previous code is a stream of random numbers between 1 and 20, the last of which is 10.
13 19 8 18 16 2 10
Found a 10!
NOTE
You can use while...do in sequence expressions and other computation expressions, in which case a customized
version of the while...do expression is used. For more information, see Sequences, Asynchronous Workflows, and
Computation Expressions.
See also
F# Language Reference
Loops: for...in Expression
Loops: for...to Expression
Assertions
10/23/2019 • 2 minutes to read • Edit Online
The assert expression is a debugging feature that you can use to test an expression. Upon failure in Debug
mode, an assertion generates a system error dialog box.
Syntax
assert condition
Remarks
The assert expression has type bool -> unit .
The assert function resolves to Debug.Assert. This means its behavior is identical to having called
Debug.Assert directly.
Assertion checking is enabled only when you compile in Debug mode; that is, if the constant DEBUG is defined.
In the project system, by default, the DEBUG constant is defined in the Debug configuration but not in the
Release configuration.
The assertion failure error cannot be caught by using F# exception handling.
Example
The following code example illustrates the use of the assert expression.
See also
F# Language Reference
Exception Handling
5/20/2021 • 2 minutes to read • Edit Online
This section contains information about exception handling support in the F# language.
Related Topics
T IT L E DESC RIP T IO N
Exceptions: The try...with Expression Describes the language construct that supports exception
handling.
Exceptions: The try...finally Expression Describes the language construct that enables you to
execute clean-up code as the stack unwinds when an
exception is thrown.
Exceptions: The invalidArg Function Describes how to generate an invalid argument exception.
Exception Types
11/2/2020 • 2 minutes to read • Edit Online
There are two categories of exceptions in F#: .NET exception types and F# exception types. This topic describes
how to define and use F# exception types.
Syntax
exception exception-type of argument-type
Remarks
In the previous syntax, exception-type is the name of a new F# exception type, and argument-type represents the
type of an argument that can be supplied when you raise an exception of this type. You can specify multiple
arguments by using a tuple type for argument-type.
A typical definition for an F# exception resembles the following.
You can generate an exception of this type by using the raise function, as follows.
You can use an F# exception type directly in the filters in a try...with expression, as shown in the following
example.
let function1 x y =
try
if x = y then raise (Error1("x"))
else raise (Error2("x", 10))
with
| Error1(str) -> printfn "Error1 %s" str
| Error2(str, i) -> printfn "Error2 %s %d" str i
function1 10 10
function1 9 2
The exception type that you define with the exception keyword in F# is a new type that inherits from
System.Exception .
See also
Exception Handling
Exceptions: the raise Function
Exception Hierarchy
Exceptions: The try...with Expression
7/30/2019 • 3 minutes to read • Edit Online
This topic describes the try...with expression, the expression that is used for exception handling in the F#
language.
Syntax
try
expression1
with
| pattern1 -> expression2
| pattern2 -> expression3
...
Remarks
The try...with expression is used to handle exceptions in F#. It is similar to the try...catch statement in C#.
In the preceding syntax, the code in expression1 might generate an exception. The try...with expression
returns a value. If no exception is thrown, the whole expression returns the value of expression1. If an exception
is thrown, each pattern is compared in turn with the exception, and for the first matching pattern, the
corresponding expression, known as the exception handler, for that branch is executed, and the overall
expression returns the value of the expression in that exception handler. If no pattern matches, the exception
propagates up the call stack until a matching handler is found. The types of the values returned from each
expression in the exception handlers must match the type returned from the expression in the try block.
Frequently, the fact that an error occurred also means that there is no valid value that can be returned from the
expressions in each exception handler. A frequent pattern is to have the type of the expression be an option type.
The following code example illustrates this pattern.
let divide1 x y =
try
Some (x / y)
with
| :? System.DivideByZeroException -> printfn "Division by zero!"; None
Exceptions can be .NET exceptions, or they can be F# exceptions. You can define F# exceptions by using the
exception keyword.
You can use a variety of patterns to filter on the exception type and other conditions; the options are
summarized in the following table.
:? exception-type as identifier Matches the specified .NET exception type, but gives the
exception a named value.
PAT T ERN DESC RIP T IO N
identifier Matches any exception and binds the name to the exception
object. Equivalent to :? System.Exception as identifier
Examples
The following code examples illustrate the use of the various exception handler patterns.
let function1 x y =
try
if x = y then raise (Error1("x"))
else raise (Error2("x", 10))
with
| Error1(str) -> printfn "Error1 %s" str
| Error2(str, i) -> printfn "Error2 %s %d" str i
function1 10 10
function1 9 2
NOTE
The try...with construct is a separate expression from the try...finally expression. Therefore, if your code
requires both a with block and a finally block, you will have to nest the two expressions.
NOTE
You can use try...with in asynchronous workflows and other computation expressions, in which case a customized
version of the try...with expression is used. For more information, see Asynchronous Workflows, and Computation
Expressions.
See also
Exception Handling
Exception Types
Exceptions: The try...finally Expression
Exceptions: The try...finally Expression
9/18/2019 • 2 minutes to read • Edit Online
The try...finally expression enables you to execute clean-up code even if a block of code throws an
exception.
Syntax
try
expression1
finally
expression2
Remarks
The try...finally expression can be used to execute the code in expression2 in the preceding syntax
regardless of whether an exception is generated during the execution of expression1.
The type of expression2 does not contribute to the value of the whole expression; the type returned when an
exception does not occur is the last value in expression1. When an exception does occur, no value is returned
and the flow of control transfers to the next matching exception handler up the call stack. If no exception handler
is found, the program terminates. Before the code in a matching handler is executed or the program terminates,
the code in the finally branch is executed.
The following code demonstrates the use of the try...finally expression.
let divide x y =
let stream : System.IO.FileStream = System.IO.File.Create("test.txt")
let writer : System.IO.StreamWriter = new System.IO.StreamWriter(stream)
try
writer.WriteLine("test1");
Some( x / y )
finally
writer.Flush()
printfn "Closing stream"
stream.Close()
let result =
try
divide 100 0
with
| :? System.DivideByZeroException -> printfn "Exception handled."; None
Closing stream
Exception handled.
As you can see from the output, the stream was closed before the outer exception was handled, and the file
test.txt contains the text test1 , which indicates that the buffers were flushed and written to disk even
though the exception transferred control to the outer exception handler.
Note that the try...with construct is a separate construct from the try...finally construct. Therefore, if your
code requires both a with block and a finally block, you have to nest the two constructs, as in the following
code example.
let function1 x y =
try
try
if x = y then raise (InnerError("inner"))
else raise (OuterError("outer"))
with
| InnerError(str) -> printfn "Error1 %s" str
finally
printfn "Always print this."
let function2 x y =
try
function1 x y
with
| OuterError(str) -> printfn "Error2 %s" str
In the context of computation expressions, including sequence expressions and asynchronous workflows,
tr y...finally expressions can have a custom implementation. For more information, see Computation
Expressions.
See also
Exception Handling
Exceptions: The try...with Expression
Exceptions: the raise Function
7/30/2019 • 2 minutes to read • Edit Online
The raise function is used to indicate that an error or exceptional condition has occurred. Information about
the error is captured in an exception object.
Syntax
raise (expression)
Remarks
The raise function generates an exception object and initiates a stack unwinding process. The stack unwinding
process is managed by the common language runtime (CLR), so the behavior of this process is the same as it is
in any other .NET language. The stack unwinding process is a search for an exception handler that matches the
generated exception. The search starts in the current try...with expression, if there is one. Each pattern in the
with block is checked, in order. When a matching exception handler is found, the exception is considered
handled; otherwise, the stack is unwound and with blocks up the call chain are checked until a matching
handler is found. Any finally blocks that are encountered in the call chain are also executed in sequence as the
stack unwinds.
The raise function is the equivalent of throw in C# or C++. Use reraise in a catch handler to propagate the
same exception up the call chain.
The following code examples illustrate the use of the raise function to generate an exception.
let function1 x y =
try
try
if x = y then raise (InnerError("inner"))
else raise (OuterError("outer"))
with
| InnerError(str) -> printfn "Error1 %s" str
finally
printfn "Always print this."
let function2 x y =
try
function1 x y
with
| OuterError(str) -> printfn "Error2 %s" str
The raise function can also be used to raise .NET exceptions, as shown in the following example.
let divide x y =
if (y = 0) then raise (System.ArgumentException("Divisor cannot be zero!"))
else
x / y
See also
Exception Handling
Exception Types
Exceptions: The try...with Expression
Exceptions: The try...finally Expression
Exceptions: The failwith Function
Exceptions: The invalidArg Function
Exceptions: The failwith Function
7/30/2019 • 2 minutes to read • Edit Online
Syntax
failwith error-message-string
Remarks
The error-message-string in the previous syntax is a literal string or a value of type string . It becomes the
Message property of the exception.
The exception that is generated by failwith is a System.Exception exception, which is a reference that has the
name Failure in F# code. The following code illustrates the use of failwith to throw an exception.
let divideFailwith x y =
if (y = 0) then failwith "Divisor cannot be zero."
else
x / y
let testDivideFailwith x y =
try
divideFailwith x y
with
| Failure(msg) -> printfn "%s" msg; 0
See also
Exception Handling
Exception Types
Exceptions: The try...with Expression
Exceptions: The try...finally Expression
Exceptions: the raise Function
Exceptions: The invalidArg Function
9/18/2019 • 2 minutes to read • Edit Online
Syntax
invalidArg parameter-name error-message-string
Remarks
The parameter-name in the previous syntax is a string with the name of the parameter whose argument was
invalid. The error-message-string is a literal string or a value of type string . It becomes the Message property
of the exception object.
The exception generated by invalidArg is a System.ArgumentException exception. The following code illustrates
the use of invalidArg to throw an exception.
December
January
System.ArgumentException: Month parameter out of range.
See also
Exception Handling
Exception Types
Exceptions: The try...with Expression
Exceptions: The try...finally Expression
Exceptions: the raise Function
Exceptions: The failwith Function
Attributes
3/31/2021 • 3 minutes to read • Edit Online
Syntax
[<target:attribute-name(arguments)>]
Remarks
In the previous syntax, the target is optional and, if present, specifies the kind of program entity that the attribute
applies to. Valid values for target are shown in the table that appears later in this document.
The attribute-name refers to the name (possibly qualified with namespaces) of a valid attribute type, with or
without the suffix Attribute that is usually used in attribute type names. For example, the type
ObsoleteAttribute can be shortened to just Obsolete in this context.
The arguments are the arguments to the constructor for the attribute type. If an attribute has a parameterless
constructor, the argument list and parentheses can be omitted. Attributes support both positional arguments
and named arguments. Positional arguments are arguments that are used in the order in which they appear.
Named arguments can be used if the attribute has public properties. You can set these by using the following
syntax in the argument list.
property-name = property-value
Such property initializations can be in any order, but they must follow any positional arguments. The following is
an example of an attribute that uses positional arguments and property initializations:
open System.Runtime.InteropServices
[<DllImport("kernel32", SetLastError=true)>]
extern bool CloseHandle(nativeint handle)
In this example, the attribute is DllImportAttribute , here used in shortened form. The first argument is a
positional parameter and the second is a property.
Attributes are a .NET programming construct that enables an object known as an attribute to be associated with
a type or other program element. The program element to which an attribute is applied is known as the attribute
target. The attribute usually contains metadata about its target. In this context, metadata could be any data about
the type other than its fields and members.
Attributes in F# can be applied to the following programming constructs: functions, methods, assemblies,
modules, types (classes, records, structures, interfaces, delegates, enumerations, unions, and so on),
constructors, properties, fields, parameters, type parameters, and return values. Attributes are not allowed on
let bindings inside classes, expressions, or workflow expressions.
Typically, the attribute declaration appears directly before the declaration of the attribute target. Multiple
attribute declarations can be used together, as follows:
[<Owner("Jason Carlson")>]
[<Company("Microsoft")>]
type SomeType1 =
Typically encountered attributes include the Obsolete attribute, attributes for security considerations, attributes
for COM support, attributes that relate to ownership of code, and attributes indicating whether a type can be
serialized. The following example demonstrates the use of the Obsolete attribute.
open System
let newFunction x y =
x + 2 * y
For the attribute targets assembly and module , you apply the attributes to a top-level do binding in your
assembly. You can include the word assembly or ``module`` in the attribute declaration, as follows:
open System.Reflection
[<assembly:AssemblyVersionAttribute("1.0.0.0")>]
[<``module``:MyCustomModuleAttribute>]
do
printfn "Executing..."
If you omit the attribute target for an attribute applied to a do binding, the F# compiler attempts to determine
the attribute target that makes sense for that attribute. Many attribute classes have an attribute of type
System.AttributeUsageAttribute that includes information about the possible targets supported for that
attribute. If the System.AttributeUsageAttribute indicates that the attribute supports functions as targets, the
attribute is taken to apply to the main entry point of the program. If the System.AttributeUsageAttribute
indicates that the attribute supports assemblies as targets, the compiler takes the attribute to apply to the
assembly. Most attributes do not apply to both functions and assemblies, but in cases where they do, the
attribute is taken to apply to the program's main function. If the attribute target is specified explicitly, the
attribute is applied to the specified target.
Although you do not usually need to specify the attribute target explicitly, valid values for target in an attribute
along with examples of usage are shown in the following table:
module
[<``module``:
MyCustomAttributeThatWorksOnModules>]
return
let function1 x : [<return:
MyCustomAttributeThatWorksOnReturns>] int = x +
1
field
[<DefaultValue>] val mutable x: int
property
[<Obsolete>] this.MyProperty = x
param
member this.MyMethod([<Out>] x : ref<int>) = x
:= 10
type
[<type: StructLayout(LayoutKind.Sequential)>]
type MyStruct =
struct
val x : byte
val y : int
end
See also
F# Language Reference
Resource Management: The use Keyword
7/30/2019 • 3 minutes to read • Edit Online
This topic describes the keyword use and the using function, which can control the initialization and release of
resources.
Resources
The term resource is used in more than one way. Yes, resources can be data that an application uses, such as
strings, graphics, and the like, but in this context, resources refers to software or operating system resources,
such as graphics device contexts, file handles, network and database connections, concurrency objects such as
wait handles, and so on. The use of these resources by applications involves the acquisition of the resource from
the operating system or other resource provider, followed by the later release of the resource to the pool so that
it can be provided to another application. Problems occur when applications do not release resources back to
the common pool.
Managing Resources
To efficiently and responsibly manage resources in an application, you must release resources promptly and in a
predictable manner. The .NET Framework helps you do this by providing the System.IDisposable interface. A
type that implements System.IDisposable has the System.IDisposable.Dispose method, which correctly frees
resources. Well-written applications guarantee that System.IDisposable.Dispose is called promptly when any
object that holds a limited resource is no longer needed. Fortunately, most .NET languages provide support to
make this easier, and F# is no exception. There are two useful language constructs that support the dispose
pattern: the use binding and the using function.
use Binding
The use keyword has a form that resembles that of the let binding:
use value = expression
It provides the same functionality as a let binding but adds a call to Dispose on the value when the value
goes out of scope. Note that the compiler inserts a null check on the value, so that if the value is null , the call
to Dispose is not attempted.
The following example shows how to close a file automatically by using the use keyword.
open System.IO
using Function
The using function has the following form:
using (expression1) function-or-lambda
In a using expression, expression1 creates the object that must be disposed. The result of expression1 (the
object that must be disposed) becomes an argument, value, to function-or-lambda, which is either a function
that expects a single remaining argument of a type that matches the value produced by expression1, or a
lambda expression that expects an argument of that type. At the end of the execution of the function, the
runtime calls Dispose and frees the resources (unless the value is null , in which case the call to Dispose is not
attempted).
The following example demonstrates the using expression with a lambda expression.
open System.IO
writetofile2 "abc2.txt" "The quick sly fox jumps over the lazy brown dog."
Note that the function could be a function that has some arguments applied already. The following code
example demonstrates this. It creates a file that contains the string XYZ .
The using function and the use binding are nearly equivalent ways to accomplish the same thing. The using
keyword provides more control over when Dispose is called. When you use using , Dispose is called at the
end of the function or lambda expression; when you use the use keyword, Dispose is called at the end of the
containing code block. In general, you should prefer to use use instead of the using function.
See also
F# Language Reference
Namespaces
5/5/2020 • 4 minutes to read • Edit Online
A namespace lets you organize code into areas of related functionality by enabling you to attach a name to a
grouping of F# program elements. Namespaces are typically top-level elements in F# files.
Syntax
namespace [rec] [parent-namespaces.]identifier
Remarks
If you want to put code in a namespace, the first declaration in the file must declare the namespace. The contents
of the entire file then become part of the namespace, provided no other namespaces declaration exists further
in the file. If that is the case, then all code up until the next namespace declaration is considered to be within the
first namespace.
Namespaces cannot directly contain values and functions. Instead, values and functions must be included in
modules, and modules are included in namespaces. Namespaces can contain types, modules.
XML doc comments can be declared above a namespace, but they're ignored. Compiler directives can also be
declared above a namespace.
Namespaces can be declared explicitly with the namespace keyword, or implicitly when declaring a module. To
declare a namespace explicitly, use the namespace keyword followed by the namespace name. The following
example shows a code file that declares a namespace Widgets with a type and a module included in that
namespace.
namespace Widgets
type MyWidget1 =
member this.WidgetName = "Widget1"
module WidgetsModule =
let widgetName = "Widget2"
If the entire contents of the file are in one module, you can also declare namespaces implicitly by using the
module keyword and providing the new namespace name in the fully qualified module name. The following
example shows a code file that declares a namespace Widgets and a module WidgetsModule , which contains a
function.
module Widgets.WidgetModule
let widgetFunction x y =
printfn "%A %A" x y
The following code is equivalent to the preceding code, but the module is a local module declaration. In that
case, the namespace must appear on its own line.
namespace Widgets
module WidgetModule =
let widgetFunction x y =
printfn "%A %A" x y
If more than one module is required in the same file in one or more namespaces, you must use local module
declarations. When you use local module declarations, you cannot use the qualified namespace in the module
declarations. The following code shows a file that has a namespace declaration and two local module
declarations. In this case, the modules are contained directly in the namespace; there is no implicitly created
module that has the same name as the file. Any other code in the file, such as a do binding, is in the namespace
but not in the inner modules, so you need to qualify the module member widgetFunction by using the module
name.
namespace Widgets
module WidgetModule1 =
let widgetFunction x y =
printfn "Module1 %A %A" x y
module WidgetModule2 =
let widgetFunction x y =
printfn "Module2 %A %A" x y
module useWidgets =
do
WidgetModule1.widgetFunction 10 20
WidgetModule2.widgetFunction 5 6
Module1 10 20
Module2 5 6
Nested Namespaces
When you create a nested namespace, you must fully qualify it. Otherwise, you create a new top-level
namespace. Indentation is ignored in namespace declarations.
The following example shows how to declare a nested namespace.
namespace Outer
Global Namespace
You use the predefined namespace global to put names in the .NET top-level namespace.
namespace global
type SomeType() =
member this.SomeMember = 0
You can also use global to reference the top-level .NET namespace, for example, to resolve name conflicts with
other namespaces.
global.System.Console.WriteLine("Hello World!")
Recursive namespaces
Namespaces can also be declared as recursive to allow for all contained code to be mutually recursive. This is
done via namespace rec . Use of namespace rec can alleviate some pains in not being able to write mutually
referential code between types and modules. The following is an example of this:
namespace rec MutualReferences
member self.Peel() = BananaHelpers.peel self // Note the dependency on the BananaHelpers module.
member self.SqueezeJuiceOut() = raise (DontSqueezeTheBananaException self) // This member depends on the
exception above.
module BananaHelpers =
let peel (b: Banana) =
let flip (banana: Banana) =
match banana.Orientation with
| Up ->
banana.Orientation <- Down
banana
| Down -> banana
Note that the exception DontSqueezeTheBananaException and the class Banana both refer to each other.
Additionally, the module BananaHelpers and the class Banana also refer to each other. This wouldn't be possible
to express in F# if you removed the rec keyword from the MutualReferences namespace.
This feature is also available for top-level Modules.
See also
F# Language Reference
Modules
F# RFC FS-1009 - Allow mutually referential types and modules over larger scopes within files
Modules
5/5/2020 • 6 minutes to read • Edit Online
In the context of the F# language, a module is a grouping of F# code, such as values, types, and function values,
in an F# program. Grouping code in modules helps keep related code together and helps avoid name conflicts in
your program.
Syntax
// Top-level module declaration.
module [accessibility-modifier] [qualified-namespace.]module-name
declarations
// Local module declaration.
module [accessibility-modifier] module-name =
declarations
Remarks
An F# module is a grouping of F# code constructs such as types, values, function values, and code in do
bindings. It is implemented as a common language runtime (CLR) class that has only static members. There are
two types of module declarations, depending on whether the whole file is included in the module: a top-level
module declaration and a local module declaration. A top-level module declaration includes the whole file in the
module. A top-level module declaration can appear only as the first declaration in a file.
In the syntax for the top-level module declaration, the optional qualified-namespace is the sequence of nested
namespace names that contains the module. The qualified namespace does not have to be previously declared.
You do not have to indent declarations in a top-level module. You do have to indent all declarations in local
modules. In a local module declaration, only the declarations that are indented under that module declaration
are part of the module.
If a code file does not begin with a top-level module declaration or a namespace declaration, the whole contents
of the file, including any local modules, becomes part of an implicitly created top-level module that has the
same name as the file, without the extension, with the first letter converted to uppercase. For example, consider
the following file.
module Program
let x = 40
If you have multiple modules in a file, you must use a local module declaration for each module. If an enclosing
namespace is declared, these modules are part of the enclosing namespace. If an enclosing namespace is not
declared, the modules become part of the implicitly created top-level module. The following code example
shows a code file that contains multiple modules. The compiler implicitly creates a top-level module named
Multiplemodules , and MyModule1 and MyModule2 are nested in that top-level module.
// In the file multiplemodules.fs.
// MyModule1
module MyModule1 =
// Indent all program elements within modules that are declared with an equal sign.
let module1Value = 100
let module1Function x =
x + 10
// MyModule2
module MyModule2 =
If you have multiple files in a project or in a single compilation, or if you are building a library, you must include
a namespace declaration or module declaration at the top of the file. The F# compiler only determines a module
name implicitly when there is only one file in a project or compilation command line, and you are creating an
application.
The accessibility-modifier can be one of the following: public , private , internal . For more information, see
Access Control. The default is public.
You can open the module or one or more of the namespaces to simplify the code. For more information about
opening namespaces and modules, see Import Declarations: The open Keyword.
The following code example shows a top-level module that contains all the code up to the end of the file.
module Arithmetic
let add x y =
x + y
let sub x y =
x - y
To use this code from another file in the same project, you either use qualified names or you open the module
before you use the functions, as shown in the following examples.
Nested Modules
Modules can be nested. Inner modules must be indented as far as outer module declarations to indicate that
they are inner modules, not new modules. For example, compare the following two examples. Module Z is an
inner module in the following code.
module Y =
let x = 1
module Z =
let z = 5
module Y =
let x = 1
module Z =
let z = 5
Module Z is also a sibling module in the following code, because it is not indented as far as other declarations
in module Y .
module Y =
let x = 1
module Z =
let z = 5
Finally, if the outer module has no declarations and is followed immediately by another module declaration, the
new module declaration is assumed to be an inner module, but the compiler will warn you if the second module
definition is not indented farther than the first.
module Y =
module Z =
let z = 5
If you want all the code in a file to be in a single outer module and you want inner modules, the outer module
does not require the equal sign, and the declarations, including any inner module declarations, that will go in the
outer module do not have to be indented. Declarations inside the inner module declarations do have to be
indented. The following code shows this case.
// The top-level module declaration can be omitted if the file is named
// TopLevel.fs or topLevel.fs, and the file is the only file in an
// application.
module TopLevel
let topLevelX = 5
module Inner1 =
let inner1X = 1
module Inner2 =
let inner2X = 5
Recursive modules
F# 4.1 introduces the notion of modules which allow for all contained code to be mutually recursive. This is done
via module rec . Use of module rec can alleviate some pains in not being able to write mutually referential code
between types and modules. The following is an example of this:
member self.Peel() = BananaHelpers.peel self // Note the dependency on the BananaHelpers module.
member self.SqueezeJuiceOut() = raise (DontSqueezeTheBananaException self) // This member depends on
the exception above.
module BananaHelpers =
let peel (b: Banana) =
let flip (banana: Banana) =
match banana.Orientation with
| Up ->
banana.Orientation <- Down
banana
| Down -> banana
Note that the exception DontSqueezeTheBananaException and the class Banana both refer to each other.
Additionally, the module BananaHelpers and the class Banana also refer to each other. This would not be
possible to express in F# if you removed the rec keyword from the RecursiveModule module.
This capability is also possible in Namespaces with F# 4.1.
See also
F# Language Reference
Namespaces
F# RFC FS-1009 - Allow mutually referential types and modules over larger scopes within files
Import declarations: The open keyword
3/6/2021 • 3 minutes to read • Edit Online
An import declaration specifies a module or namespace whose elements you can reference without using a fully
qualified name.
Syntax
open module-or-namespace-name
open type type-name
Remarks
Referencing code by using the fully qualified namespace or module path every time can create code that is hard
to write, read, and maintain. Instead, you can use the open keyword for frequently used modules and
namespaces so that when you reference a member of that module or namespace, you can use the short form of
the name instead of the fully qualified name. This keyword is similar to the using keyword in C#,
using namespace in Visual C++, and Imports in Visual Basic.
The module or namespace provided must be in the same project or in a referenced project or assembly. If it is
not, you can add a reference to the project, or use the -reference command-line option (or its abbreviation, -r
). For more information, see Compiler Options.
The import declaration makes the names available in the code that follows the declaration, up to the end of the
enclosing namespace, module, or file.
When you use multiple import declarations, they should appear on separate lines.
The following code shows the use of the open keyword to simplify code.
The F# compiler does not emit an error or warning when ambiguities occur when the same name occurs in
more than one open module or namespace. When ambiguities occur, F# gives preference to the more recently
opened module or namespace. For example, in the following code, empty means Seq.empty , even though
empty is located in both the List and Seq modules.
open List
open Seq
printfn %"{empty}"
Therefore, be careful when you open modules or namespaces such as List or Seq that contain members that
have identical names; instead, consider using the qualified names. You should avoid any situation in which the
code is dependent upon the order of the import declarations.
This will expose all accessible static fields and members on the type.
You can also open F#-defined record and discriminated union types to expose static members. In the case of
discriminated unions, you can also expose the union cases. This can be helpful for accessing union cases in a
type declared inside of a module that you may not want to open, like so:
module M =
type DU = A | B | C
let someOtherFunction x = x + 1
printfn "%A" A
AutoOpen Attribute
You can apply the AutoOpen attribute to an assembly if you want to automatically open a namespace or module
when the assembly is referenced. You can also apply the AutoOpen attribute to a module to automatically open
that module when the parent module or namespace is opened. For more information, see AutoOpenAttribute.
RequireQualifiedAccess Attribute
Some modules, records, or union types may specify the RequireQualifiedAccess attribute. When you reference
elements of those modules, records, or unions, you must use a qualified name regardless of whether you
include an import declaration. If you use this attribute strategically on types that define commonly used names,
you help avoid name collisions and thereby make code more resilient to changes in libraries. For more
information, see RequireQualifiedAccessAttribute.
See also
F# Language Reference
Namespaces
Modules
Signatures
7/30/2019 • 4 minutes to read • Edit Online
A signature file contains information about the public signatures of a set of F# program elements, such as types,
namespaces, and modules. It can be used to specify the accessibility of these program elements.
Remarks
For each F# code file, you can have a signature file, which is a file that has the same name as the code file but
with the extension .fsi instead of .fs. Signature files can also be added to the compilation command-line if you
are using the command line directly. To distinguish between code files and signature files, code files are
sometimes referred to as implementation files. In a project, the signature file should precede the associated code
file.
A signature file describes the namespaces, modules, types, and members in the corresponding implementation
file. You use the information in a signature file to specify what parts of the code in the corresponding
implementation file can be accessed from code outside the implementation file, and what parts are internal to
the implementation file. The namespaces, modules, and types that are included in the signature file must be a
subset of the namespaces, modules, and types that are included in the implementation file. With some
exceptions noted later in this topic, those language elements that are not listed in the signature file are
considered private to the implementation file. If no signature file is found in the project or command line, the
default accessibility is used.
For more information about the default accessibility, see Access Control.
In a signature file, you do not repeat the definition of the types and the implementations of each method or
function. Instead, you use the signature for each method and function, which acts as a complete specification of
the functionality that is implemented by a module or namespace fragment. The syntax for a type signature is the
same as that used in abstract method declarations in interfaces and abstract classes, and is also shown by
IntelliSense and by the F# interpreter fsi.exe when it displays correctly compiled input.
If there is not enough information in the type signature to indicate whether a type is sealed, or whether it is an
interface type, you must add an attribute that indicates the nature of the type to the compiler. Attributes that you
use for this purpose are described in the following table.
[<Sealed>] For a type that has no abstract members, or that should not
be extended.
The compiler produces an error if the attributes are not consistent between the signature and the declaration in
the implementation file.
Use the keyword val to create a signature for a value or function value. The keyword type introduces a type
signature.
You can generate a signature file by using the --sig compiler option. Generally, you do not write .fsi files
manually. Instead, you generate .fsi files by using the compiler, add them to your project, if you have one, and
edit them by removing methods and functions that you do not want to be accessible.
There are several rules for type signatures:
Type abbreviations in an implementation file must not match a type without an abbreviation in a
signature file.
Records and discriminated unions must expose either all or none of their fields and constructors, and the
order in the signature must match the order in the implementation file. Classes can reveal some, all, or
none of their fields and methods in the signature.
Classes and structures that have constructors must expose the declarations of their base classes (the
inherits declaration). Also, classes and structures that have constructors must expose all of their
abstract methods and interface declarations.
Interface types must reveal all their methods and interfaces.
The rules for value signatures are as follows:
Modifiers for accessibility ( public , internal , and so on) and the inline and mutable modifiers in the
signature must match those in the implementation.
The number of generic type parameters (either implicitly inferred or explicitly declared) must match, and
the types and type constraints in generic type parameters must match.
If the Literal attribute is used, it must appear in both the signature and the implementation, and the
same literal value must be used for both.
The pattern of parameters (also known as the arity) of signatures and implementations must be
consistent.
If parameter names in a signature file differ from the corresponding implementation file, the name in the
signature file will be used instead, which may cause issues when debugging or profiling. If you wish to be
notified of such mismatches, enable warning 3218 in your project file or when invoking the compiler (see
--warnon under Compiler Options).
The following code example shows an example of a signature file that has namespace, module, function value,
and type signatures together with the appropriate attributes. It also shows the corresponding implementation
file.
// Module1.fsi
namespace Library1
module Module1 =
val function1 : int -> int
type Type1 =
new : unit -> Type1
member method1 : unit -> unit
member method2 : unit -> unit
[<Sealed>]
type Type2 =
new : unit -> Type2
member method1 : unit -> unit
member method2 : unit -> unit
[<Interface>]
type InterfaceType1 =
abstract member method1 : int -> int
abstract member method2 : string -> unit
module Module1 =
let function1 x = x + 1
type Type1() =
member type1.method1() =
printfn "type1.method1"
member type1.method2() =
printfn "type1.method2"
[<Sealed>]
type Type2() =
member type2.method1() =
printfn "type2.method1"
member type2.method2() =
printfn "type2.method2"
[<Interface>]
type InterfaceType1 =
abstract member method1 : int -> int
abstract member method2 : string -> unit
See also
F# Language Reference
Access Control
Compiler Options
Units of Measure
11/2/2020 • 7 minutes to read • Edit Online
Floating point and signed integer values in F# can have associated units of measure, which are typically used to
indicate length, volume, mass, and so on. By using quantities with units, you enable the compiler to verify that
arithmetic relationships have the correct units, which helps prevent programming errors.
Syntax
[<Measure>] type unit-name [ = measure ]
Remarks
The previous syntax defines unit-name as a unit of measure. The optional part is used to define a new measure
in terms of previously defined units. For example, the following line defines the measure cm (centimeter).
[<Measure>] type cm
The following line defines the measure ml (milliliter) as a cubic centimeter ( cm^3 ).
In the previous syntax, measure is a formula that involves units. In formulas that involve units, integral powers
are supported (positive and negative), spaces between units indicate a product of the two units, * also
indicates a product of units, and / indicates a quotient of units. For a reciprocal unit, you can either use a
negative integer power or a / that indicates a separation between the numerator and denominator of a unit
formula. Multiple units in the denominator should be surrounded by parentheses. Units separated by spaces
after a / are interpreted as being part of the denominator, but any units following a * are interpreted as being
part of the numerator.
You can use 1 in unit expressions, either alone to indicate a dimensionless quantity, or together with other units,
such as in the numerator. For example, the units for a rate would be written as 1/s , where s indicates seconds.
Parentheses are not used in unit formulas. You do not specify numeric conversion constants in the unit formulas;
however, you can define conversion constants with units separately and use them in unit-checked computations.
Unit formulas that mean the same thing can be written in various equivalent ways. Therefore, the compiler
converts unit formulas into a consistent form, which converts negative powers to reciprocals, groups units into
a single numerator and a denominator, and alphabetizes the units in the numerator and denominator.
For example, the unit formulas kg m s^-2 and m /s s * kg are both converted to kg m/s^2 .
You use units of measure in floating point expressions. Using floating point numbers together with associated
units of measure adds another level of type safety and helps avoid the unit mismatch errors that can occur in
formulas when you use weakly typed floating point numbers. If you write a floating point expression that uses
units, the units in the expression must match.
You can annotate literals with a unit formula in angle brackets, as shown in the following examples.
1.0<cm>
55.0<miles/hour>
You do not put a space between the number and the angle bracket; however, you can include a literal suffix such
as f , as in the following example.
Such an annotation changes the type of the literal from its primitive type (such as float ) to a dimensioned
type, such as float<cm> or, in this case, float<miles/hour> . A unit annotation of <1> indicates a dimensionless
quantity, and its type is equivalent to the primitive type without a unit parameter.
The type of a unit of measure is a floating point or signed integral type together with an extra unit annotation,
indicated in brackets. Thus, when you write the type of a conversion from g (grams) to kg (kilograms), you
describe the types as follows.
Units of measure are used for compile-time unit checking but are not persisted in the run-time environment.
Therefore, they do not affect performance.
Units of measure can be applied to any type, not just floating point types; however, only floating point types,
signed integral types, and decimal types support dimensioned quantities. Therefore, it only makes sense to use
units of measure on the primitive types and on aggregates that contain these primitive types.
The following example illustrates the use of units of measure.
// Mass, grams.
[<Measure>] type g
// Mass, kilograms.
[<Measure>] type kg
// Weight, pounds.
[<Measure>] type lb
// Distance, meters.
[<Measure>] type m
// Distance, cm
[<Measure>] type cm
// Distance, inches.
[<Measure>] type inch
// Distance, feet
[<Measure>] type ft
// Time, seconds.
[<Measure>] type s
// Force, Newtons.
[<Measure>] type N = kg m / s^2
// Pressure, bar.
[<Measure>] type bar
// Pressure, Pascals
[<Measure>] type Pa = N / m^2
// Volume, milliliters.
[<Measure>] type ml
// Volume, liters.
[<Measure>] type L
The following code example illustrates how to convert from a dimensionless floating point number to a
dimensioned floating point value. You just multiply by 1.0, applying the dimensions to the 1.0. You can abstract
this into a function like degreesFahrenheit .
Also, when you pass dimensioned values to functions that expect dimensionless floating point numbers, you
must cancel out the units or cast to float by using the float operator. In this example, you divide by
1.0<degC> for the arguments to printf because printf expects dimensionless quantities.
[<Measure>] type degC // temperature, Celsius/Centigrade
[<Measure>] type degF // temperature, Fahrenheit
The following example session shows the outputs from and inputs to this code.
// Distance, meters.
[<Measure>] type m
// Time, seconds.
[<Measure>] type s
let v1 = 3.1<m/s>
let v2 = 2.7<m/s>
let x1 = 1.2<m>
let t1 = 1.0<s>
Units at Runtime
Units of measure are used for static type checking. When floating point values are compiled, the units of
measure are eliminated, so the units are lost at run time. Therefore, any attempt to implement functionality that
depends on checking the units at run time is not possible. For example, implementing a ToString function to
print out the units is not possible.
Conversions
To convert a type that has units (for example, float<'u> ) to a type that does not have units, you can use the
standard conversion function. For example, you can use float to convert to a float value that does not have
units, as shown in the following code.
[<Measure>]
type cm
let length = 12.0<cm>
let x = float length
To convert a unitless value to a value that has units, you can multiply by a 1 or 1.0 value that is annotated with
the appropriate units. However, for writing interoperability layers, there are also some explicit functions that you
can use to convert unitless values to values with units. These are in the FSharp.Core.LanguagePrimitives module.
For example, to convert from a unitless float to a float<cm> , use FloatWithMeasure, as shown in the
following code.
open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x
See also
F# Language Reference
Plain text formatting
5/28/2021 • 10 minutes to read • Edit Online
F# supports type-checked formatting of plain text using printf , printfn , sprintf , and related functions. For
example,
dotnet fsi
Hello world, 2 + 2 is 4
F# also allows structured values to be formatted as plain text. For example, consider the following example that
formats the output as a matrix-like display of tuples.
dotnet fsi
[[(1, 1); (1, 2); (1, 3); (1, 4); (1, 5)];
[(2, 1); (2, 2); (2, 3); (2, 4); (2, 5)];
[(3, 1); (3, 2); (3, 3); (3, 4); (3, 5)];
[(4, 1); (4, 2); (4, 3); (4, 4); (4, 5)];
[(5, 1); (5, 2); (5, 3); (5, 4); (5, 5)]]
Structured plain text formatting is activated when you use the %A format in printf formatting strings. It's also
activated when formatting the output of values in F# interactive, where the output includes extra information
and is additionally customizable. Plain text formatting is also observable through any calls to x.ToString() on
F# union and record values, including those that occur implicitly in debugging, logging, and other tooling.
stdin(3,25): error FS0001: The type 'string' does not match the type 'int'
Technically speaking, when using printf and other related functions, a special rule in the F# compiler checks
the string literal passed as the format string, ensuring the subsequent arguments applied are of the correct type
to match the format specifiers used.
Format specifiers for printf
Format specifications for printf formats are strings with % markers that indicate format. Format placeholders
consist of %[flags][width][.precision][type] where the type is interpreted as follows:
F O RM AT SP EC IF IER T Y P E( S) REM A RK S
Basic integer types are byte ( System.Byte ), sbyte ( System.SByte ), int16 ( System.Int16 ), uint16 (
System.UInt16 ), int32 ( System.Int32 ), uint32 ( System.UInt32 ), int64 ( System.Int64 ), uint64 (
System.UInt64 ), nativeint ( System.IntPtr ), and unativeint ( System.UIntPtr ). Basic floating point types are
float ( System.Double ), float32 ( System.Single ), and decimal ( System.Decimal ).
The optional width is an integer indicating the minimal width of the result. For instance, %6d prints an integer,
prefixing it with spaces to fill at least six characters. If width is * , then an extra integer argument is taken to
specify the corresponding width.
Valid flags are:
FLAG EF F EC T REM A RK S
The printf # flag is invalid and a compile-time error will be reported if it is used.
Values are formatted using invariant culture. Culture settings are irrelevant to printf formatting except when
they affect the results of %O and %A formatting. For more information, see structured plain text formatting.
%A formatting
The %A format specifier is used to format values in a human-readable way, and can also be useful for reporting
diagnostic information.
Primitive values
When formatting plain text using the %A specifier, F# numeric values are formatted with their suffix and
invariant culture. Floating point values are formatted using 10 places of floating point precision. For example,
produces
When using the %A specifier, strings are formatted using quotes. Escape codes are not added and instead the
raw characters are printed. For example,
produces
("abc", "a b
c"d")
.NET values
When formatting plain text using the %A specifier, non-F# .NET objects are formatted by using x.ToString()
using the default settings of .NET given by System.Globalization.CultureInfo.CurrentCulture and
System.Globalization.CultureInfo.CurrentUICulture . For example,
open System.Globalization
produces
Structured values
When formatting plain text using the %A specifier, block indentation is used for F# lists and tuples. This shown
in the previous example. The structure of arrays is also used, including multi-dimensional arrays. Single-
dimensional arrays are shown with [| ... |] syntax. For example,
[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25); (6, 36); (7, 49); (8, 64); (9, 81);
(10, 100); (11, 121); (12, 144); (13, 169); (14, 196); (15, 225); (16, 256);
(17, 289); (18, 324); (19, 361); (20, 400)|]
The default print width is 80. This width can be customized by using a print width in the format specifier. For
example,
produces
[|(1, 1);
(2, 4);
(3, 9);
(4, 16);
(5, 25)|]
[|(1, 1); (2, 4);
(3, 9); (4, 16);
(5, 25)|]
[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25)|]
Specifying a print width of 0 results in no print width being used. A single line of text will result, except where
embedded strings in the output contain line breaks. For example
produces
[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25)|]
[|"abc
def"; "abc
def"; "abc
def"; "abc
def"; "abc
def"|]
A depth limit of 4 is used for sequence ( IEnumerable ) values, which are shown as seq { ...} . A depth limit of
100 is used for list and array values. For example,
produces
seq [(1, 1); (2, 4); (3, 9); (4, 16); ...]
Block indentation is also used for the structure of public record and union values. For example,
type R = { X : int list; Y : string list }
produces
{ X = [1; 2; 3]
Y = ["one"; "two"; "three"] }
If %+A is used, then the private structure of records and unions is also revealed by using reflection. For example
type internal R =
{ X : int list; Y : string list }
override _.ToString() = "R"
produces
external view:
R
internal view:
{ X = [1; 2; 3]
Y = ["one"; "two"; "three"] }
type Tree =
| Tip
| Node of Tree * Tree
Cycles are detected in the object graphs and ... is used at places where cycles are detected. For example
type R = { mutable Links: R list }
let r = { Links = [] }
r.Links <- [r]
printfn "%A" r
produces
{ Links = [...] }
When using the %A specifier, the presence of the StructuredFormatDisplay attribute on type declarations is
respected. This can be used to specify surrogate text and property to display a value. For example:
[<StructuredFormatDisplay("Counts({Clicks})")>]
type Counts = { Clicks:int list}
produces
Counts([0; 1; 2; 3;
4; 5; 6; 7;
8; 9; 10; 11;
12; 13; 14;
15; 16; 17;
18; 19; 20])
The default implementation of ToString is observable in F# programming. Often, the default results aren't
suitable for use in either programmer-facing information display or user output, and as a result it is common to
override the default implementation.
By default, F# record and union types override the implementation of ToString with an implementation that
uses sprintf "%+A" . For example,
produces
For class types, no default implementation of ToString is provided and the .NET default is used, which reports
the name of the type. For example,
type MyClassType(clicks: int list) =
member _.Clicks = clicks
produces
produces
See also
Strings
Records
Discriminated Unions
F# Interactive
Document your code with XML comments
3/6/2021 • 6 minutes to read • Edit Online
You can produce documentation from triple-slash (///) code comments in F#. XML comments can precede
declarations in code files (.fs) or signature (.fsi) files.
XML documentation comments are a special kind of comment, added above the definition of any user-defined
type or member. They are special because they can be processed by the compiler to generate an XML
documentation file at compile time. The compiler-generated XML file can be distributed alongside your .NET
assembly so that IDEs can use tooltips to show quick information about types or members. Additionally, the XML
file can be run through tools like fsdocs to generate API reference websites.
XML documentation comments, like all other comments, are ignored by the compiler, unless the options
described below are enabled to check the validity and completeness of comments at compile time.
You can generate the XML file at compile time by doing one of the following:
You can add a GenerateDocumentationFile element to the <PropertyGroup> section of your .fsproj
project file, which generates an XML file in the project directory with the same root filename as the
assembly. For example:
<GenerateDocumentationFile>true</GenerateDocumentationFile>
If you are developing an application using Visual Studio, right-click on the project and select Proper ties .
In the properties dialog, select the Build tab, and check XML documentation file . You can also change
the location to which the compiler writes the file.
There are two ways to write XML documentation comments: with and without XML tags. Both use triple-slash
comments.
/// Creates a new string whose characters are the result of applying
/// the function mapping to each of the characters of the input string
/// and concatenating the resulting strings.
val collect : (char -> string) -> string -> string
/// <summary>Builds a new string whose characters are the results of applying the function <c>mapping</c>
/// to each of the characters of the input string and concatenating the resulting
/// strings.</summary>
/// <param name="mapping">The function to produce a string from each character of the input string.</param>
///<param name="str">The input string.</param>
///<returns>The concatenated string.</returns>
///<exception cref="System.ArgumentNullException">Thrown when the input string is null.</exception>
val collect : (char -> string) -> string -> string
Recommended Tags
If you are using XML tags, the following table describes the outer tags recognized in F# XML code comments.
TA G SY N TA X DESC RIP T IO N
<summary> text </summary> Specifies that text is a brief description of the program
element. The description is usually one or two sentences.
<param name=" name "> description </param> Specifies the name and description for a function or method
parameter.
<typeparam name=" name "> description </typeparam> Specifies the name and description for a type parameter.
<returns> text </returns> Specifies that text describes the return value of a function or
method.
<exception cref=" type "> description </exception> Specifies the type of exception that can be generated and
the circumstances under which it is thrown.
<seealso cref=" reference "/> Specifies a See Also link to the documentation for another
type. The reference is the name as it appears in the XML
documentation file. See Also links usually appear at the
bottom of a documentation page.
The following table describes the tags for use inside description sections:
TA G SY N TA X DESC RIP T IO N
<para> text </para> Specifies a paragraph of text. This is used to separate text
inside the remarks tag.
<code> text </code> Specifies that text is multiple lines of code. This tag can be
used by documentation generators to display text in a font
that is appropriate for code.
<typeparamref name=" name "/> Specifies a reference to a type parameter in the same
documentation comment.
<c> text </c> Specifies that text is inline code. This tag can be used by
documentation generators to display text in a font that is
appropriate for code.
<see cref=" reference "> text </see> Specifies an inline link to another program element. The
reference is the name as it appears in the XML
documentation file. The text is the text shown in the link.
User-defined tags
The previous tags represent those that are recognized by the F# compiler and typical F# editor tooling. However,
a user is free to define their own tags. Tools like fsdocs bring support for extra tags like <namespacedoc>.
Custom or in-house documentation generation tools can also be used with the standard tags and multiple
output formats from HTML to PDF can be supported.
Compile-time checking
When --warnon:3390is enabled, the compiler verifies the syntax of the XML and the parameters referred to in
<param> and <paramref> tags.
Documenting F# Constructs
F# constructs such as modules, members, union cases, and record fields are documented by a /// comment
immediately prior to their declaration. If needed, implicit constructors of classes are documented by giving a
/// comment prior to the argument list. For example:
Limitations
Some features of XML documentation in C# and other .NET languages are not supported in F#.
In F#, cross-references must use the full XML signature of the corresponding symbol, for example
cref="T:System.Console" . Simple C#-style cross-references such as cref="Console" are not elaborated to
full XML signatures and these elements are not checked by the F# compiler. Some documentation tooling
may allow the use of these cross-references by subsequent processing, but the full signatures should be
used.
The tags <include> , <inheritdoc> are not supported by the F# compiler. No error is given if they are
used, but they are simply copied to the generated documentation file without otherwise affecting the
documentation generated.
Cross-references are not checked by the F# compiler, even when -warnon:3390 is used.
The names used in the tags <typeparam> and <typeparamref> are not checked by the F# compiler, even
when --warnon:3390 is used.
No warnings are given if documentation is missing, even when --warnon:3390 is used.
Recommendations
Documenting code is recommended for many reasons. What follows are some best practices, general use case
scenarios, and things that you should know when using XML documentation tags in your F# code.
Enable the option --warnon:3390 in your code to help ensure your XML documentation is valid XML.
Consider adding signature files to separate long XML documentation comments from your
implementation.
For the sake of consistency, all publicly visible types and their members should be documented. If you
must do it, do it all.
At a bare minimum, modules, types, and their members should have a plain /// comment or <summary>
tag. This will show in an autocompletion tooltip window in F# editing tools.
Documentation text should be written using complete sentences ending with full stops.
See also
C# XML Documentation Comments (C# Programming Guide).
F# Language Reference
Compiler Options
Lazy Expressions
3/6/2021 • 2 minutes to read • Edit Online
Lazy expressions are expressions that are not evaluated immediately, but are instead evaluated when the result
is needed. This can help to improve the performance of your code.
Syntax
let identifier = lazy ( expression )
Remarks
In the previous syntax, expression is code that is evaluated only when a result is required, and identifier is a
value that stores the result. The value is of type Lazy<'T> , where the actual type that is used for 'T is
determined from the result of the expression.
Lazy expressions enable you to improve performance by restricting the execution of an expressions to only
those situations in which a result is needed.
To force the expressions to be performed, you call the method Force . Force causes the execution to be
performed only one time. Subsequent calls to Force return the same result, but do not execute any code.
The following code illustrates the use of lazy expressions and the use of Force . In this code, the type of result
is Lazy<int> , and the Force method returns an int .
let x = 10
let result = lazy (x + 10)
printfn "%d" (result.Force())
Lazy evaluation, but not the Lazy type, is also used for sequences. For more information, see Sequences.
See also
F# Language Reference
LazyExtensions module
Computation Expressions
7/13/2021 • 15 minutes to read • Edit Online
Computation expressions in F# provide a convenient syntax for writing computations that can be sequenced and
combined using control flow constructs and bindings. Depending on the kind of computation expression, they
can be thought of as a way to express monads, monoids, monad transformers, and applicative functors.
However, unlike other languages (such as do-notation in Haskell), they are not tied to a single abstraction, and
do not rely on macros or other forms of metaprogramming to accomplish a convenient and context-sensitive
syntax.
Overview
Computations can take many forms. The most common form of computation is single-threaded execution,
which is easy to understand and modify. However, not all forms of computation are as straightforward as single-
threaded execution. Some examples include:
Non-deterministic computations
Asynchronous computations
Effectful computations
Generative computations
More generally, there are context-sensitive computations that you must perform in certain parts of an
application. Writing context-sensitive code can be challenging, as it is easy to "leak" computations outside of a
given context without abstractions to prevent you from doing so. These abstractions are often challenging to
write by yourself, which is why F# has a generalized way to do so called computation expressions .
Computation expressions offer a uniform syntax and abstraction model for encoding context-sensitive
computations.
Every computation expression is backed by a builder type. The builder type defines the operations that are
available for the computation expression. See Creating a New Type of Computation Expression, which shows
how to create a custom computation expression.
Syntax overview
All computation expressions have the following form:
builder-expr { cexper }
where builder-expr is the name of a builder type that defines the computation expression, and cexper is the
expression body of the computation expression. For example, async computation expression code can look like
this:
return processedData
}
There is a special, additional syntax available within a computation expression, as shown in the previous
example. The following expression forms are possible with computation expressions:
Each of these keywords, and other standard F# keywords are only available in a computation expression if they
have been defined in the backing builder type. The only exception to this is match! , which is itself syntactic
sugar for the use of let! followed by a pattern match on the result.
The builder type is an object that defines special methods that govern the way the fragments of the computation
expression are combined; that is, its methods control how the computation expression behaves. Another way to
describe a builder class is to say that it enables you to customize the operation of many F# constructs, such as
loops and bindings.
let!
The let! keyword binds the result of a call to another computation expression to a name:
If you bind the call to a computation expression with let , you will not get the result of the computation
expression. Instead, you will have bound the value of the unrealized call to that computation expression. Use
let! to bind to the result.
The do! keyword is for calling a computation expression that returns a unit -like type (defined by the Zero
member on the builder):
For the async workflow, this type is Async<unit> . For other computation expressions, the type is likely to be
CExpType<unit> .
do! is defined by the Bind(x, f) member on the builder type, where f produces a unit .
yield
The yield keyword is for returning a value from the computation expression so that it can be consumed as an
IEnumerable<T>:
let squares =
seq {
for i in 1..10 do
yield i * i
}
for sq in squares do
printfn $"%d{sq}"
In most cases, it can be omitted by callers. The most common way to omit yield is with the -> operator:
let squares =
seq {
for i in 1..10 -> i * i
}
for sq in squares do
printfn $"%d{sq}"
For more complex expressions that might yield many different values, and perhaps conditionally, simply
omitting the keyword can do:
As with the yield keyword in C#, each element in the computation expression is yielded back as it is iterated.
yield is defined by the Yield(x) member on the builder type, where x is the item to yield back.
yield!
The yield! keyword is for flattening a collection of values from a computation expression:
let squares =
seq {
for i in 1..3 -> i * i
}
let cubes =
seq {
for i in 1..3 -> i * i * i
}
let squaresAndCubes =
seq {
yield! squares
yield! cubes
}
When evaluated, the computation expression called by yield! will have its items yielded back one-by-one,
flattening the result.
yield! is defined by the YieldFrom(x) member on the builder type, where x is a collection of values.
Unlike yield , yield! must be explicitly specified. Its behavior isn't implicit in computation expressions.
return
The return keyword wraps a value in the type corresponding to the computation expression. Aside from
computation expressions using yield , it is used to "complete" a computation expression:
return is defined by the Return(x) member on the builder type, where x is the item to wrap.
return!
The return! keyword realizes the value of a computation expression and wraps that result in the type
corresponding to the computation expression:
return! is defined by the ReturnFrom(x) member on the builder type, where x is another computation
expression.
match!
The match! keyword allows you to inline a call to another computation expression and pattern match on its
result:
When calling a computation expression with match! , it will realize the result of the call like let! . This is often
used when calling a computation expression where the result is an optional.
Bind M<'T> * ('T -> M<'U>) -> M<'U> Called for let! and do! in
computation expressions.
For seq<'T> * ('T -> M<'U>) -> M<'U> Called for for...do expressions in
or computation expressions.
TryFinally Delayed<'T> * (unit -> unit) -> Called for try...finally expressions
M<'T> in computation expressions.
TryWith Delayed<'T> * (exn -> M<'T>) -> Called for try...with expressions in
M<'T>
computation expressions.
Using 'T * ('T -> M<'U>) -> M<'U> when Called for use bindings in
'T :> IDisposable computation expressions.
While (unit -> bool) * Delayed<'T> -> Called for while...do expressions in
M<'T> computation expressions.
or
Many of the methods in a builder class use and return an M<'T> construct, which is typically a separately
defined type that characterizes the kind of computations being combined, for example, Async<'T> for
asynchronous workflows and Seq<'T> for sequence workflows. The signatures of these methods enable them
to be combined and nested with each other, so that the workflow object returned from one construct can be
passed to the next.
Many functions use the result of Delay as an argument: Run , While , TryWith , TryFinally and Combine . The
Delayed<'T> type is the return type of Delay and consequently the parameter to these functions. Delayed<'T>
can be an arbitrary type that does not need to be related to M<'T> ; commonly M<'T> or (unit -> M<'T>) are
used. The default implementation is M<'T> . See here for a more in-depth look.
The compiler, when it parses a computation expression, converts the expression into a series of nested function
calls by using the methods in the preceding table and the code in the computation expression. The nested
expression is of the following form:
In the above code, the calls to Run and Delay are omitted if they are not defined in the computation expression
builder class. The body of the computation expression, here denoted as {| cexpr |} , is translated into calls
involving the methods of the builder class by the translations described in the following table. The computation
expression {| cexpr |} is defined recursively according to these translations where expr is an F# expression
and cexpr is a computation expression.
EXP RESSIO N T RA N SL AT IO N
{ let! pattern = expr in cexpr } builder.Bind(expr, (fun pattern -> {| cexpr |}))
{ use pattern = expr in cexpr } builder.Using(expr, (fun pattern -> {| cexpr |}))
{ if expr then cexpr0 else cexpr1 |} if expr then { cexpr0 } else { cexpr1 }
{ match expr with | pattern_i -> cexpr_i } match expr with | pattern_i -> { cexpr_i }
{ for identifier = expr1 to expr2 do cexpr } builder.For(enumeration, (fun identifier -> { cexpr
}))
{ try cexpr with | pattern_i -> expr_i } builder.TryWith(builder.Delay({ cexpr }), (fun
value -> match value with | pattern_i -> expr_i |
exn -> reraise exn)))
In the previous table, other-expr describes an expression that is not otherwise listed in the table. A builder class
does not need to implement all of the methods and support all of the translations listed in the previous table.
Those constructs that are not implemented are not available in computation expressions of that type. For
example, if you do not want to support the use keyword in your computation expressions, you can omit the
definition of Use in your builder class.
The following code example shows a computation expression that encapsulates a computation as a series of
steps that can be evaluated one step at a time. A discriminated union type, OkOrException , encodes the error
state of the expression as evaluated so far. This code demonstrates several typical patterns that you can use in
your computation expressions, such as boilerplate implementations of some of the builder methods.
module Eventually =
// The bind for the computations. Append 'func' to the
// computation.
let rec bind func expr =
let rec bind func expr =
match expr with
| Done value -> func value
| NotYetDone work -> NotYetDone (fun () -> bind func (work()))
type OkOrException<'T> =
| Ok of 'T
| Exception of System.Exception
A computation expression has an underlying type, which the expression returns. The underlying type may
represent a computed result or a delayed computation that can be performed, or it may provide a way to iterate
through some type of collection. In the previous example, the underlying type was Eventually . For a sequence
expression, the underlying type is System.Collections.Generic.IEnumerable<T>. For a query expression, the
underlying type is System.Linq.IQueryable. For an asynchronous workflow, the underlying type is Async . The
Async object represents the work to be performed to compute the result. For example, you call
Async.RunSynchronously to execute a computation and return the result.
Custom Operations
You can define a custom operation on a computation expression and use a custom operation as an operator in a
computation expression. For example, you can include a query operator in a query expression. When you define
a custom operation, you must define the Yield and For methods in the computation expression. To define a
custom operation, put it in a builder class for the computation expression, and then apply the
CustomOperationAttribute . This attribute takes a string as an argument, which is the name to be used in a
custom operation. This name comes into scope at the start of the opening curly brace of the computation
expression. Therefore, you shouldn't use identifiers that have the same name as a custom operation in this
block. For example, avoid the use of identifiers such as all or last in query expressions.
Extending existing Builders with new Custom Operations
If you already have a builder class, its custom operations can be extended from outside of this builder class.
Extensions must be declared in modules. Namespaces cannot contain extension members except in the same file
and the same namespace declaration group where the type is defined.
The following example shows the extension of the existing FSharp.Linq.QueryBuilder class.
[<CustomOperation("existsNot")>]
member _.ExistsNot (source: QuerySource<'T, 'Q>, predicate) =
Enumerable.Any (source.Source, Func<_,_>(predicate)) |> not
See also
F# Language Reference
Asynchronous Workflows
Sequences
Query Expressions
Series on Computation Expressions from F# for Fun and Profit
Asynchronous workflows
11/2/2020 • 4 minutes to read • Edit Online
This article describes support in F# for performing computations asynchronously, that is, without blocking
execution of other work. For example, asynchronous computations can be used to write applications that have
UIs that remain responsive to users as the application performs other work.
Syntax
async { expression }
Remarks
In the previous syntax, the computation represented by expression is set up to run asynchronously, that is,
without blocking the current computation thread when asynchronous sleep operations, I/O, and other
asynchronous operations are performed. Asynchronous computations are often started on a background thread
while execution continues on the current thread. The type of the expression is Async<'T> , where 'T is the type
returned by the expression when the return keyword is used. The code in such an expression is referred to as
an asynchronous block, or async block.
There are a variety of ways of programming asynchronously, and the Async class provides methods that
support several scenarios. The general approach is to create Async objects that represent the computation or
computations that you want to run asynchronously, and then start these computations by using one of the
triggering functions. The various triggering functions provide different ways of running asynchronous
computations, and which one you use depends on whether you want to use the current thread, a background
thread, or a .NET Framework task object, and whether there are continuation functions that should run when the
computation finishes. For example, to start an asynchronous computation on the current thread, you can use
Async.StartImmediate . When you start an asynchronous computation from the UI thread, you do not block the
main event loop that processes user actions such as keystrokes and mouse activity, so your application remains
responsive.
Asynchronous Primitives
A method that performs a single asynchronous task and returns the result is called an asynchronous primitive,
and these are designed specifically for use with let! . Several asynchronous primitives are defined in the F#
core library. Two such methods for Web applications are defined in the module FSharp.Control.WebExtensions :
WebRequest.AsyncGetResponse and WebClient.AsyncDownloadString . Both primitives download data from a Web
page, given a URL. AsyncGetResponse produces a System.Net.WebResponse object, and AsyncDownloadString
produces a string that represents the HTML for a Web page.
Several primitives for asynchronous I/O operations are included in the FSharp.Control.CommonExtensions
module. These extension methods of the System.IO.Stream class are Stream.AsyncRead and Stream.AsyncWrite .
You can also write your own asynchronous primitives by defining a function whose complete body is enclosed
in an async block.
To use asynchronous methods in the .NET Framework that are designed for other asynchronous models with the
F# asynchronous programming model, you create a function that returns an F# Async object. The F# library has
functions that make this easy to do.
One example of using asynchronous workflows is included here; there are many others in the documentation
for the methods of the Async class.
This example shows how to use asynchronous workflows to perform computations in parallel.
In the following code example, a function fetchAsync gets the HTML text returned from a Web request. The
fetchAsync function contains an asynchronous block of code. When a binding is made to the result of an
asynchronous primitive, in this case AsyncDownloadString , let! is used instead of let .
You use the function Async.RunSynchronously to execute an asynchronous operation and wait for its result. As an
example, you can execute multiple asynchronous operations in parallel by using the Async.Parallel function
together with the Async.RunSynchronously function. The Async.Parallel function takes a list of the Async
objects, sets up the code for each Async task object to run in parallel, and returns an Async object that
represents the parallel computation. Just as for a single operation, you call Async.RunSynchronously to start the
execution.
The runAll function launches three asynchronous workflows in parallel and waits until they have all completed.
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
runAll()
See also
F# Language Reference
Computation Expressions
Control.Async Class
Query expressions
5/20/2021 • 41 minutes to read • Edit Online
Query expressions enable you to query a data source and put the data in a desired form. Query expressions
provide support for LINQ in F#.
Syntax
query { expression }
Remarks
Query expressions are a type of computation expression similar to sequence expressions. Just as you specify a
sequence by providing code in a sequence expression, you specify a set of data by providing code in a query
expression. In a sequence expression, the yield keyword identifies data to be returned as part of the resulting
sequence. In query expressions, the select keyword performs the same function. In addition to the select
keyword, F# also supports a number of query operators that are much like the parts of a SQL SELECT statement.
Here is an example of a simple query expression, along with code that connects to the Northwind OData source.
// Use the OData type provider to create types that can be used to access the Northwind database.
// Add References to FSharp.Data.TypeProviders and System.Data.Services.Client
open Microsoft.FSharp.Data.TypeProviders
// A query expression.
let query1 =
query {
for customer in db.Customers do
select customer
}
// Print results
query1
|> Seq.iter (fun customer -> printfn "Company: %s Contact: %s" customer.CompanyName customer.ContactName)
In the previous code example, the query expression is in curly braces. The meaning of the code in the expression
is, return every customer in the Customers table in the database in the query results. Query expressions return a
type that implements IQueryable<T> and IEnumerable<T>, and so they can be iterated using the Seq module
as the example shows.
Every computation expression type is built from a builder class. The builder class for the query computation
expression is QueryBuilder . For more information, see Computation Expressions and QueryBuilder Class.
Query Operators
Query operators enable you to specify the details of the query, such as to put criteria on records to be returned,
or specify the sorting order of results. The query source must support the query operator. If you attempt to use
an unsupported query operator, System.NotSupportedException will be thrown.
Only expressions that can be translated to SQL are allowed in query expressions. For example, no function calls
are allowed in the expressions when you use the where query operator.
Table 1 shows available query operators. In addition, see Table2, which compares SQL queries and the
equivalent F# query expressions later in this topic. Some query operators aren't supported by some type
providers. In particular, the OData type provider is limited in the query operators that it supports due to
limitations in OData.
This table assumes a database in the following form:
The code in the tables that follow also assumes the following database connection code. Projects should add
references to System.Data, System.Data.Linq, and FSharp.Data.TypeProviders assemblies. The code that creates
this database is included at the end of this topic.
open System
open Microsoft.FSharp.Data.TypeProviders
open System.Data.Linq.SqlClient
open System.Linq
open Microsoft.FSharp.Linq
let db = schema.GetDataContext()
query {
for student in db.Student do
select student.Age.Value
contains 11
}
count Returns the number of selected elements.
query {
for student in db.Student do
select student
count
}
query {
for number in data do
last
}
query {
for number in data do
where (number < 0)
lastOrDefault
}
query {
for student in db.Student do
where (student.StudentID = 1)
select student
exactlyOne
}
query {
for student in db.Student do
where (student.StudentID = 1)
select student
exactlyOneOrDefault
}
headOrDefault Selects the first element of those selected so far, or a default
value if the sequence contains no elements.
query {
for student in db.Student do
select student
headOrDefault
}
query {
for student in db.Student do
select student
}
query {
for student in db.Student do
where (student.StudentID > 4)
select student
}
minBy Selects a value for each element selected so far and returns
the minimum resulting value.
query {
for student in db.Student do
minBy student.StudentID
}
maxBy Selects a value for each element selected so far and returns
the maximum resulting value.
query {
for student in db.Student do
maxBy student.StudentID
}
query {
for student in db.Student do
groupBy student.Age into g
select (g.Key, g.Count())
}
sortBy Sorts the elements selected so far in ascending order by the
given sorting key.
query {
for student in db.Student do
sortBy student.Name
select student
}
query {
for student in db.Student do
sortByDescending student.Name
select student
}
query {
for student in db.Student do
where student.Age.HasValue
sortBy student.Age.Value
thenBy student.Name
select student
}
query {
for student in db.Student do
where student.Age.HasValue
sortBy student.Age.Value
thenByDescending student.Name
select student
}
groupValBy Selects a value for each element selected so far and groups
the elements by the given key.
query {
for student in db.Student do
groupValBy student.Name student.Age into g
select (g, g.Key, g.Count())
}
query {
for student in db.Student do
join selection in db.CourseSelection
on (student.StudentID =
selection.StudentID)
select (student, selection)
}
query {
for student in db.Student do
groupJoin courseSelection in
db.CourseSelection
on (student.StudentID =
courseSelection.StudentID) into g
for courseSelection in g do
join course in db.Course
on (courseSelection.CourseID =
course.CourseID)
select (student.Name, course.CourseName)
}
leftOuterJoin Correlates two sets of selected values based on matching
keys and groups the results. If any group is empty, a group
with a single default value is used instead. Note that the
order of the keys around the = sign in a join expression is
significant.
query {
for student in db.Student do
leftOuterJoin selection in
db.CourseSelection
on (student.StudentID =
selection.StudentID) into result
for selection in result.DefaultIfEmpty() do
select (student, selection)
}
sumByNullable Selects a nullable value for each element selected so far and
returns the sum of these values. If any nullable does not
have a value, it is ignored.
query {
for student in db.Student do
sumByNullable student.Age
}
minByNullable Selects a nullable value for each element selected so far and
returns the minimum of these values. If any nullable does
not have a value, it is ignored.
query {
for student in db.Student do
minByNullable student.Age
}
maxByNullable Selects a nullable value for each element selected so far and
returns the maximum of these values. If any nullable does
not have a value, it is ignored.
query {
for student in db.Student do
maxByNullable student.Age
}
averageByNullable Selects a nullable value for each element selected so far and
returns the average of these values. If any nullable does not
have a value, it is ignored.
query {
for student in db.Student do
averageByNullable (Nullable.float
student.Age)
}
averageBy Selects a value for each element selected so far and returns
the average of these values.
query {
for student in db.Student do
averageBy (float student.StudentID)
}
query {
for student in db.Student do
join selection in db.CourseSelection
on (student.StudentID =
selection.StudentID)
distinct
}
query {
for student in db.Student do
where
(query {
for courseSelection in
db.CourseSelection do
exists (courseSelection.StudentID =
student.StudentID) })
select student
}
query {
for student in db.Student do
find (student.Name = "Abercrombie, Kim")
}
all Determines whether all elements selected so far satisfy a
condition.
query {
for student in db.Student do
all (SqlMethods.Like(student.Name, "%,%"))
}
query {
for student in db.Student do
head
}
query {
for numbers in data do
nth 3
}
query {
for student in db.Student do
skip 1
}
query {
for number in data do
skipWhile (number < 3)
select student
}
sumBy Selects a value for each element selected so far and returns
the sum of these values.
query {
for student in db.Student do
sumBy student.StudentID
}
take Selects a specified number of contiguous elements from
those selected so far.
query {
for student in db.Student do
select student
take 2
}
query {
for number in data do
takeWhile (number < 10)
}
query {
for student in db.Student do
sortByNullable student.Age
select student
}
query {
for student in db.Student do
sortByNullableDescending student.Age
select student
}
thenByNullable Performs a subsequent ordering of the elements selected so
far in ascending order by the given nullable sorting key. This
operator may only be used immediately after a sortBy ,
sortByDescending , thenBy , or thenByDescending , or
their nullable variants.
query {
for student in db.Student do
sortBy student.Name
thenByNullable student.Age
select student
}
query {
for student in db.Student do
sortBy student.Name
thenByNullableDescending student.Age
select student
}
Grouping
// Group by age and count.
SELECT Student.Age, COUNT( * ) FROM Student query {
GROUP BY Student.Age for n in db.Student do
groupBy n.Age into g
select (g.Key, g.Count())
}
// OR
query {
for n in db.Student do
groupValBy n.Age n.Age into g
select (g.Key, g.Count())
}
Distinct count.
// Join with distinct and count.
SELECT DISTINCT COUNT(StudentID) FROM query {
CourseSelection for n in db.Student do
join e in db.CourseSelection
on (n.StudentID = e.StudentID)
distinct
count
}
BETWEEN
// Selecting students with ages between 10 and
SELECT * FROM Student 15.
WHERE Student.Age BETWEEN 10 AND 15 query {
for student in db.Student do
where (student.Age ?>= 10 && student.Age ?<
15)
select student
}
OR
// Selecting students with age that's either 11
SELECT * FROM Student or 12.
WHERE Student.Age = 11 OR Student.Age = 12 query {
for student in db.Student do
where (student.Age.Value = 11 ||
student.Age.Value = 12)
select student
}
OR with ordering
// Selecting students in a certain age range
SELECT * FROM Student and sorting.
WHERE Student.Age = 12 OR Student.Age = 13 query {
ORDER BY Student.Age DESC for n in db.Student do
where (n.Age.Value = 12 || n.Age.Value =
13)
sortByNullableDescending n.Age
select n
}
TOP , OR , and ordering.
// Selecting students with certain ages,
SELECT TOP 2 student.Name FROM Student // taking account of the possibility of nulls.
WHERE Student.Age = 11 OR Student.Age = 12 query {
ORDER BY Student.Name DESC for student in db.Student do
where
((student.Age.HasValue &&
student.Age.Value = 11) ||
(student.Age.HasValue &&
student.Age.Value = 12))
sortByDescending student.Name
select student.Name
take 2
}
let query2 =
query {
for n in db.LastStudent do
select (n.Name, n.Age)
}
query2.Union (query1)
let query2 =
query {
for n in db.LastStudent do
select (n.Name, n.Age)
}
query1.Intersect(query2)
CASE condition.
// Using if statement to alter results for
SELECT student.StudentID, special value.
CASE Student.Age query {
WHEN -1 THEN 100 for student in db.Student do
ELSE Student.Age select
END, (if student.Age.HasValue &&
Student.Age student.Age.Value = -1 then
FROM Student (student.StudentID,
System.Nullable<int>(100), student.Age)
else (student.StudentID, student.Age,
student.Age))
}
Multiple cases.
// Using if statement to alter results for
SELECT Student.StudentID, special values.
CASE Student.Age query {
WHEN -1 THEN 100 for student in db.Student do
WHEN 0 THEN 1000 select
ELSE Student.Age (if student.Age.HasValue &&
END, student.Age.Value = -1 then
Student.Age (student.StudentID,
FROM Student System.Nullable<int>(100), student.Age)
elif student.Age.HasValue &&
student.Age.Value = 0 then
(student.StudentID,
System.Nullable<int>(1000), student.Age)
else (student.StudentID, student.Age,
student.Age))
}
Multiple tables.
// Multiple table select.
SELECT * FROM Student, Course query {
for student in db.Student do
for course in db.Course do
select (student, course)
}
Multiple joins.
// Multiple joins.
SELECT Student.Name, Course.CourseName query {
FROM Student for student in db.Student do
JOIN CourseSelection join courseSelection in db.CourseSelection
ON CourseSelection.StudentID = on (student.StudentID =
Student.StudentID courseSelection.StudentID)
JOIN Course join course in db.Course
ON Course.CourseID = CourseSelection.CourseID on (courseSelection.CourseID =
course.CourseID)
select (student.Name, course.CourseName)
}
Multiple left outer joins.
// Using leftOuterJoin with multiple joins.
SELECT Student.Name, Course.CourseName query {
FROM Student for student in db.Student do
LEFT OUTER JOIN CourseSelection leftOuterJoin courseSelection in
ON CourseSelection.StudentID = db.CourseSelection
Student.StudentID on (student.StudentID =
LEFT OUTER JOIN Course courseSelection.StudentID) into g1
ON Course.CourseID = CourseSelection.CourseID for courseSelection in g1.DefaultIfEmpty()
do
leftOuterJoin course in db.Course
on (courseSelection.CourseID =
course.CourseID) into g2
for course in g2.DefaultIfEmpty() do
select (student.Name, course.CourseName)
}
The following code can be used to create the sample database for these examples.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
USE [master];
GO
USE MyDatabase;
GO
The following code contains the sample code that appears in this topic.
#if INTERACTIVE
#r "FSharp.Data.TypeProviders.dll"
#r "System.Data.dll"
#r "System.Data.Linq.dll"
#endif
open System
open Microsoft.FSharp.Data.TypeProviders
open System.Data.Linq.SqlClient
open System.Linq
let db = schema.GetDataContext()
type Nullable<'T when 'T : ( new : unit -> 'T) and 'T : struct and 'T :> ValueType > with
member this.Print() =
if this.HasValue then this.Value.ToString()
else "NULL"
open Microsoft.FSharp.Linq
printfn "\nExists."
query {
for student in db.Student do
where
(query {
for courseSelection in db.CourseSelection do
exists (courseSelection.StudentID = student.StudentID) })
select student
}
|> Seq.iter (fun student -> printfn "%A" student.Name)
printfn "\nGroup students by age and print counts of number of students at each age with more than 1
student."
query {
for student in db.Student do
groupBy student.Age into group
where (group.Count() > 1)
select (group.Key, group.Count())
}
|> Seq.iter (fun (age, ageCount) ->
printfn "Age: %s Count: %d" (age.Print()) ageCount)
printfn "\nGroup students by age and count number of students at each age, and display all with count > 1 in
descending order of count."
query {
for student in db.Student do
groupBy student.Age into g
where (g.Count() > 1)
sortByDescending (g.Count())
select (g.Key, g.Count())
}
|> Seq.iter (fun (age, myCount) ->
printfn "Age: %s" (age.Print())
printfn "Count: %d" myCount)
printfn "\nLook for students with Name match _e%% pattern and take first two."
query {
for student in db.Student do
where (SqlMethods.Like( student.Name, "_e%") )
select student
take 2
}
|> Seq.iter (fun student -> printfn "%s" student.Name)
printfn "\nLook for students with name matching [^abc]%% pattern and select ID."
query {
for n in db.Student do
where (SqlMethods.Like( n.Name, "[^abc]%") )
select n.StudentID
}
|> Seq.iter (fun id -> printfn "%d" id)
printfn "\n Selecting students with certain ages, taking account of possibility of nulls."
query {
for student in db.Student do
where
((student.Age.HasValue && student.Age.Value = 11) ||
(student.Age.HasValue && student.Age.Value = 12))
sortByDescending student.Name
select student.Name
take 2
}
|> Seq.iter (fun name -> printfn "%s" name)
query2.Union (query1)
|> Seq.iter (fun (name, age) -> printfn "%s %s" name (age.Print()))
query1.Intersect(query2)
|> Seq.iter (fun (name, age) -> printfn "%s %s" name (age.Print()))
And here is the full output when this code is run in F# Interactive.
minByNullable
Minimum age: 10
maxByNullable
Maximum age: 14
averageBy
Average student ID: 4.500000
averageByNullable
Average age: 12
Count of students:
Student count: 8
Exists.
"Abercrombie, Kim"
"Abolrous, Hazen"
"Hance, Jim"
"Adams, Terry"
"Hansen, Claus"
"Perham, Tom"
Group students by age and print counts of number of students at each age with more than 1 student.
Age: 12 Count: 3
Group students by age and count number of students at each age, and display all with count > 1 in descending
order of count.
Age: 12
Count: 3
Look for students with Name match _e% pattern and take first two.
Penor, Lori
Perham, Tom
Look for students with name matching [^abc]% pattern and select ID.
3
5
6
7
8
Multiple Joins
Abercrombie, Kim Trigonometry
Abercrombie, Kim Algebra II
Abercrombie, Kim English
Abolrous, Hazen Trigonometry
Abolrous, Hazen English
Abolrous, Hazen French
Abolrous, Hazen Algebra II
Hance, Jim Trigonometry
Hance, Jim Algebra I
Adams, Terry Trigonometry
Adams, Terry English
Adams, Terry Trigonometry
Hansen, Claus Algebra II
Hansen, Claus Trigonometry
Perham, Tom Algebra II
type schema
val db : schema.ServiceTypes.SimpleDataContextTypes.MyDatabase1
val student : System.Data.Linq.Table<schema.ServiceTypes.Student>
val data : int list = [1; 5; 7; 11; 18; 21]
type Nullable<'T
when 'T : (new : unit -> 'T) and 'T : struct and
'T :> System.ValueType> with
member Print : unit -> string
val num : int = 21
val student2 : schema.ServiceTypes.Student
val student3 : schema.ServiceTypes.Student
val student4 : schema.ServiceTypes.Student
val student5 : int = 1
val student6 : int = 8
val idList : int list = [1; 2; 5; 10]
val idQuery : seq<int>
val names : string [] = [|"a"; "b"; "c"|]
module Queries = begin
val query1 : System.Linq.IQueryable<string * System.Nullable<int>>
val query2 : System.Linq.IQueryable<string * System.Nullable<int>>
end
module Queries2 = begin
val query1 : System.Linq.IQueryable<string * System.Nullable<int>>
val query2 : System.Linq.IQueryable<string * System.Nullable<int>>
end
See also
F# Language Reference
QueryBuilder Class
Computation Expressions
Code quotations
6/10/2021 • 7 minutes to read • Edit Online
This article describes code quotations, a language feature that enables you to generate and work with F# code
expressions programmatically. This feature lets you generate an abstract syntax tree that represents F# code. The
abstract syntax tree can then be traversed and processed according to the needs of your application. For
example, you can use the tree to generate F# code or generate code in some other language.
Quoted expressions
A quoted expression is an F# expression in your code that is delimited in such a way that it is not compiled as
part of your program, but instead is compiled into an object that represents an F# expression. You can mark a
quoted expression in one of two ways: either with type information or without type information. If you want to
include type information, you use the symbols <@ and @> to delimit the quoted expression. If you do not need
type information, you use the symbols <@@ and @@> . The following code shows typed and untyped quotations.
open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>
Traversing a large expression tree is faster if you do not include type information. The resulting type of an
expression quoted with the typed symbols is Expr<'T> , where the type parameter has the type of the expression
as determined by the F# compiler's type inference algorithm. When you use code quotations without type
information, the type of the quoted expression is the non-generic type Expr. You can call the Raw property on
the typed Expr class to obtain the untyped Expr object.
There are various static methods that allow you to generate F# expression objects programmatically in the
Expr class without using quoted expressions.
A code quotation must include a complete expression. For a let binding, for example, you need both the
definition of the bound name and another expression that uses the binding. In verbose syntax, this is an
expression that follows the in keyword. At the top level in a module, this is just the next expression in the
module, but in a quotation, it is explicitly required.
Therefore, the following expression is not valid.
// Not valid:
// <@ let f x = x + 1 @>
// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
let f x = x + 10
f 20
@>
To evaluate F# quotations, you must use the F# Quotation Evaluator. It provides support for evaluating and
executing F# expression objects.
F# quotations also retain type constraint information. Consider the following example:
open FSharp.Linq.RuntimeHelpers
The constraint generated by the inline function is retained in the code quotation. The negate function's
quoted form can now be evaluated.
Expr type
An instance of the Expr type represents an F# expression. Both the generic and the non-generic Expr types are
documented in the F# library documentation. For more information, see FSharp.Quotations Namespace and
Quotations.Expr Class.
Splicing operators
Splicing enables you to combine literal code quotations with expressions that you have created
programmatically or from another code quotation. The % and %% operators enable you to add an F#
expression object into a code quotation. You use the % operator to insert a typed expression object into a typed
quotation; you use the %% operator to insert an untyped expression object into an untyped quotation. Both
operators are unary prefix operators. Thus if expr is an untyped expression of type Expr , the following code is
valid.
And if expr is a typed quotation of type Expr<int> , the following code is valid.
Example 1
Description
The following example illustrates the use of code quotations to put F# code into an expression object and then
print the F# code that represents the expression. A function println is defined that contains a recursive
function print that displays an F# expression object (of type Expr ) in a friendly format. There are several
active patterns in the FSharp.Quotations.Patterns and FSharp.Quotations.DerivedPatterns modules that can be
used to analyze expression objects. This example does not include all the possible patterns that might appear in
an F# expression. Any unrecognized pattern triggers a match to the wildcard pattern ( _ ) and is rendered by
using the ToString method, which, on the Expr type, lets you know the active pattern to add to your match
expression.
Code
module Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
let a = 2
println exprLambda
println exprCall
println <@@ let f x = x + 10 in f 10 @@>
Output
Example 2
Description
You can also use the three active patterns in the ExprShape module to traverse expression trees with fewer
active patterns. These active patterns can be useful when you want to traverse a tree but you do not need all the
information in most of the nodes. When you use these patterns, any F# expression matches one of the following
three patterns: ShapeVar if the expression is a variable, ShapeLambda if the expression is a lambda expression, or
ShapeCombination if the expression is anything else. If you traverse an expression tree by using the active
patterns as in the previous code example, you have to use many more patterns to handle all possible F#
expression types, and your code will be more complex. For more information, see
ExprShape.ShapeVar|ShapeLambda|ShapeCombination Active Pattern.
The following code example can be used as a basis for more complex traversals. In this code, an expression tree
is created for an expression that involves a function call, add . The SpecificCall active pattern is used to detect
any call to add in the expression tree. This active pattern assigns the arguments of the call to the exprList
value. In this case, there are only two, so these are pulled out and the function is called recursively on the
arguments. The results are inserted into a code quotation that represents a call to mul by using the splice
operator ( %% ). The println function from the previous example is used to display the results.
The code in the other active pattern branches just regenerates the same expression tree, so the only change in
the resulting expression is the change from add to mul .
Code
module Module1
open Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.ExprShape
let add x y = x + y
let mul x y = x * y
Output
1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))
See also
F# Language Reference
The fixed keyword
3/6/2021 • 2 minutes to read • Edit Online
The fixed keyword allows you to "pin" a local onto the stack to prevent it from being collected or moved
during garbage-collection. It is used for low-level programming scenarios.
Syntax
use ptr = fixed expression
Remarks
This extends the syntax of expressions to allow extracting a pointer and binding it to a name which is prevented
from being collected or moved during garbage-collection.
A pointer from an expression is fixed via the fixed keyword is bound to an identifier via the use keyword. The
semantics of this are similar to resource management via the use keyword. The pointer is fixed while it is in
scope, and once it is out of scope, it is no longer fixed. fixed cannot be used outside the context of a use
binding. You must bind the pointer to a name with use .
Use of fixed must occur within an expression in a function or a method. It cannot be used at a script-level or
module-level scope.
Like all pointer code, this is an unsafe feature and will emit a warning when used.
Example
open Microsoft.FSharp.NativeInterop
let pnt = { X = 1; Y = 2 }
printfn $"pnt before - X: %d{pnt.X} Y: %d{pnt.Y}" // prints 1 and 2
doPointerWork()
See also
NativePtr Module
Byrefs
3/6/2021 • 6 minutes to read • Edit Online
F# has two major feature areas that deal in the space of low-level programming:
The byref / inref / outref types, which are managed pointers. They have restrictions on usage so that you
cannot compile a program that is invalid at run time.
A byref -like struct, which is a structure that has similar semantics and the same compile-time restrictions as
byref<'T> . One example is Span<T>.
Syntax
// Byref types as parameters
let f (x: byref<'T>) = ()
let g (x: inref<'T>) = ()
let h (x: outref<'T>) = ()
[<Struct; IsByRefLike>]
type S(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
A byref<'T> can be passed where an inref<'T> is expected. Similarly, a byref<'T> can be passed where an
outref<'T> is expected.
Using byrefs
To use a inref<'T> , you need to get a pointer value with & :
open System
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
To write to the pointer by using an outref<'T> or byref<'T> , you must also make the value you grab a pointer
to mutable .
open System
If you are only writing the pointer instead of reading it, consider using outref<'T> instead of byref<'T> .
Inref semantics
Consider the following code:
However, for F# value types that are immutable, the this pointer is inferred to be an inref .
All of these rules together mean that the holder of an inref pointer may not modify the immediate contents of
the memory being pointed to.
Outref semantics
The purpose of outref<'T> is to indicate that the pointer should only be written to. Unexpectedly, outref<'T>
permits reading the underlying value despite its name. This is for compatibility purposes. Semantically,
outref<'T> is no different than byref<'T> .
Interop with C#
C# supports the in ref and out ref keywords, in addition to ref returns. The following table shows how F#
interprets what C# emits:
C # C O N ST RUC T F # IN F ERS
F # C O N ST RUC T EM IT T ED C O N ST RUC T
type C() =
static member M(x: System.DateTime) = x.AddDays(1.0)
static member M(x: inref<System.DateTime>) = x.AddDays(2.0)
static member M2(x: System.DateTime, y: int) = x.AddDays(1.0)
static member M2(x: inref<System.DateTime>, y: int) = x.AddDays(2.0)
In both cases, the overloads taking System.DateTime are resolved rather than the overloads taking
inref<System.DateTime> .
Byref-like structs
In addition to the byref / inref / outref trio, you can define your own structs that can adhere to byref -like
semantics. This is done with the IsByRefLikeAttribute attribute:
open System
open System.Runtime.CompilerServices
[<IsByRefLike; Struct>]
type S(count1: Span<int>, count2: Span<int>) =
member x.Count1 = count1
member x.Count2 = count2
IsByRefLike does not imply Struct . Both must be present on the type.
A " byref -like" struct in F# is a stack-bound value type. It is never allocated on the managed heap. A byref -like
struct is useful for high-performance programming, as it is enforced with set of strong checks about lifetime and
non-capture. The rules are:
They can be used as function parameters, method parameters, local variables, method returns.
They cannot be static or instance members of a class or normal struct.
They cannot be captured by any closure construct ( async methods or lambda expressions).
They cannot be used as a generic parameter.
This last point is crucial for F# pipeline-style programming, as |> is a generic function that parameterizes its
input types. This restriction may be relaxed for |> in the future, as it is inline and does not make any calls to
non-inlined generic functions in its body.
Although these rules strongly restrict usage, they do so to fulfill the promise of high-performance computing in
a safe manner.
Byref returns
Byref returns from F# functions or members can be produced and consumed. When consuming a byref -
returning method, the value is implicitly dereferenced. For example:
To return a value byref, the variable that contains the value must live longer than the current scope. Also, to
return byref, use &value (where value is a variable that lives longer than the current scope).
To avoid the implicit dereference, such as passing a reference through multiple chained calls, use &x (where x
is the value).
You can also directly assign to a return byref . Consider the following (highly imperative) program:
type C() =
let mutable nums = [| 1; 3; 7; 15; 31; 63; 127; 255; 511; 1023 |]
while ctr > 0 && nums.[ctr] >= target do ctr <- ctr - 1
[<EntryPoint>]
let main argv =
let c = C()
printfn $"Original sequence: %O{c}"
let v = &c.FindLargestSmallerThan 16
let test2 () =
let x = 12
&x // Error: 'x' exceeds its defined scope!
let test () =
let x =
let y = 1
&y // Error: `y` exceeds its defined scope!
()
This prevents you from getting different results depending on if you compile with optimizations or not.
Reference Cells
9/24/2019 • 2 minutes to read • Edit Online
Reference cells are storage locations that enable you to create mutable values with reference semantics.
Syntax
ref expression
Remarks
You use the ref operator before a value to create a new reference cell that encapsulates the value. You can then
change the underlying value because it is mutable.
A reference cell holds an actual value; it is not just an address. When you create a reference cell by using the
ref operator, you create a copy of the underlying value as an encapsulated mutable value.
// Declare a reference.
let refVar = ref 6
The output is 50 .
Reference cells are instances of the Ref generic record type, which is declared as follows.
type Ref<'a> =
{ mutable contents: 'a }
The type 'a ref is a synonym for Ref<'a> . The compiler and IntelliSense in the IDE display the former for this
type, but the underlying definition is the latter.
The ref operator creates a new reference cell. The following code is the declaration of the ref operator.
The following table shows the features that are available on the reference cell.
O P ERATO R, M EM B ER, O R
F IEL D DESC RIP T IO N TYPE DEF IN IT IO N
O P ERATO R, M EM B ER, O R
F IEL D DESC RIP T IO N TYPE DEF IN IT IO N
! (dereference operator) Returns the underlying 'a ref -> 'a let (!) r = r.contents
value.
:= (assignment operator) Changes the underlying 'a ref -> 'a -> unit let (:=) r x =
value. r.contents <- x
ref (operator) Encapsulates a value into a 'a -> 'a ref let ref x = { contents
new reference cell. = x }
Value (property) Gets or sets the underlying unit -> 'a member x.Value =
value. x.contents
contents (record field) Gets or sets the underlying 'a let ref x = { contents
value. = x }
There are several ways to access the underlying value. The value returned by the dereference operator ( ! ) is
not an assignable value. Therefore, if you are modifying the underlying value, you must use the assignment
operator ( := ) instead.
Both the Value property and the contents field are assignable values. Therefore, you can use these to either
access or change the underlying value, as shown in the following code.
xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
10
10
11
12
The field contents is provided for compatibility with other versions of ML and will produce a warning during
compilation. To disable the warning, use the --mlcompatibility compiler option. For more information, see
Compiler Options.
C# programmers should know that ref in C# is not the same thing as ref in F#. The equivalent constructs in
F# are byrefs, which are a different concept from reference cells.
Values marked as mutable may be automatically promoted to 'a ref if captured by a closure; see Values.
See also
F# Language Reference
Parameters and Arguments
Symbol and Operator Reference
Values
Nameof
6/29/2021 • 2 minutes to read • Edit Online
The nameof expression produces a string constant that matches the name in source for nearly any F# construct
in source.
Syntax
nameof symbol
nameof<'TGeneric>
Remarks
nameof works by resolving the symbol passed to it and produces the name of that symbol as it is declared in
your source code. This is useful in various scenarios, such as logging, and protects your logging against changes
in source code.
let months =
[
"January"; "February"; "March"; "April";
"May"; "June"; "July"; "August"; "September";
"October"; "November"; "December"
]
months.[month-1]
The last line will throw an exception and "month" will be shown in the error message.
You can take a name of nearly every F# construct:
module M =
let f x = nameof x
nameof is not a first-class function and cannot be used as such. That means it cannot be partially applied and
values cannot be piped into it via F# pipeline operators.
Nameof on operators
Operators in F# can be used in two ways, as an operator text itself, or a symbol representing the compiled form.
nameof on an operator will produce the name of the operator as it is declared in source. To get the compiled
name, use the compiled name in source:
nameof(+) // "+"
nameof op_Addition // "op_Addition"
Nameof on generics
You can also take a name of a generic type parameter, but the syntax is different:
nameof<'TGeneric> will take the name of the symbol as defined in source, not the name of the type substituted
at a call site.
The reason why the syntax is different is to align with other F# intrinsic operators like typeof<> and
typedefof<> . This makes F# consistent with respect to operators that act on generic types and anything else in
source.
f "str" // matches
f "asdf" // does not match
Preprocessor Directives
A preprocessor directive is prefixed with the # symbol and appears on a line by itself. It is interpreted by the
preprocessor, which runs before the compiler itself.
The following table lists the preprocessor directives that are available in F#.
# [line] int, Indicates the original source code line and file name, for
# [line] int string, debugging. This feature is provided for tools that generate
# [line] int verbatim-string F# source code.
The effect of disabling a warning applies to the entire file, including portions of the file that precede the
directive.|
NOTE
The behavior of the conditional compilation directives is not the same as it is in other languages. For example, you cannot
use Boolean expressions involving symbols, and true and false have no special meaning. Symbols that you use in the
if directive must be defined by the command line or in the project settings; there is no define preprocessor directive.
The following code illustrates the use of the #if , #else , and #endif directives. In this example, the code
contains two versions of the definition of function1 . When VERSION1 is defined by using the -define compiler
option, the code between the #if directive and the #else directive is activated. Otherwise, the code between
#else and #endif is activated.
#if VERSION1
let function1 x y =
printfn "x: %d y: %d" x y
x + 2 * y
#else
let function1 x y =
printfn "x: %d y: %d" x y
x - 2*y
#endif
There is no #define preprocessor directive in F#. You must use the compiler option or project settings to define
the symbols used by the #if directive.
Conditional compilation directives can be nested. Indentation is not significant for preprocessor directives.
You can also negate a symbol with ! . In this example, a string's value is something only when not debugging:
#if !DEBUG
let str = "Not debugging!"
#else
let str = "Debugging!"
#endif
Line Directives
When building, the compiler reports errors in F# code by referencing line numbers on which each error occurs.
These line numbers start at 1 for the first line in a file. However, if you are generating F# source code from
another tool, the line numbers in the generated code are generally not of interest, because the errors in the
generated F# code most likely arise from another source. The #line directive provides a way for authors of
tools that generate F# source code to pass information about the original line numbers and source files to the
generated F# code.
When you use the #line directive, file names must be enclosed in quotation marks. Unless the verbatim token (
@ ) appears in front of the string, you must escape backslash characters by using two backslash characters
instead of one in order to use them in the path. The following are valid line tokens. In these examples, assume
that the original file Script1 results in an automatically generated F# code file when it is run through a tool,
and that the code at the location of these directives is generated from some tokens at line 25 in file Script1 .
# 25
#line 25
#line 25 "C:\\Projects\\MyProject\\MyProject\\Script1"
#line 25 @"C:\Projects\MyProject\MyProject\Script1"
# 25 @"C:\Projects\MyProject\MyProject\Script1"
These tokens indicate that the F# code generated at this location is derived from some constructs at or near line
25 in Script1 .
Compiler Directives
Compiler directives resemble preprocessor directives, because they are prefixed with a # sign, but instead of
being interpreted by the preprocessor, they are left for the compiler to interpret and act on.
The following table lists the compiler directive that is available in F#.
See also
F# Language Reference
Compiler Options
Compiler options
3/15/2021 • 8 minutes to read • Edit Online
This topic describes compiler command-line options for the F# compiler, fsc.exe.
The compilation environment can also be controlled by setting the project properties. For projects targeting
.NET Core, the "Other flags" property, <OtherFlags>...</OtherFlags> in .fsproj , is used for specifying extra
command-line options.
C O M P IL ER O P T IO N DESC RIP T IO N
-g: [full|pdbonly] Equivalent to the C# compiler option of the same name. For
more information, see
-d:symbol
--keyfile:filename Specifies the name of a public key file for signing the
generated assembly.
--mlcompatibility Ignores warnings that appear when you use features that
are designed for compatibility with other versions of ML.
--nologo Doesn't show the banner text when launching the compiler.
--pdb:pdb-filename Names the output debug PDB (program database) file. This
option only applies when --debug is also enabled.
--platform:platform-name Specifies that the generated code will only run on the
specified platform ( x86 , Itanium , or x64 ), or, if the
platform-name anycpu is chosen, specifies that the
generated code can run on any platform.
--staticlink:assembly-name Statically links the given assembly and all referenced DLLs
that depend on this assembly. Use the assembly name, not
the DLL name.
--target:[exe|winexe|library|module] filename Specifies the type and file name of the generated compiled
code.
exe means a console application.
winexe means a Windows application, which differs
from the console application in that it does not have
standard input/output streams (stdin, stdout, and
stderr) defined.
library is an assembly without an entry point.
module is a .NET Framework module (.netmodule),
which can later be combined with other modules into
an assembly.
This compiler option is equivalent to the C# compiler
option of the same name. For more information, see
/target (C# Compiler Options).
Related articles
T IT L E DESC RIP T IO N
Project Properties Reference Describes the UI for projects, including project property
pages that provide build options.
F# Interactive options
11/2/2020 • 5 minutes to read • Edit Online
This article describes the command-line options supported by F# Interactive, fsi.exe . F# Interactive accepts
many of the same command-line options as the F# compiler, but also accepts some additional options.
O P T IO N DESC RIP T IO N
-g [+ |- ]
--load:<filename> Compiles the given source code at startup and loads the
compiled F# constructs into the session.
--use:<filename> Tells the interpreter to use the given file on startup as initial
input.
--warnaserror [+ |- ]:<int-list> Same as the fsc.exe compiler option. For more information,
see Compiler Options.
open System.Globalization
fsi.PrintWidth <- 120 // Control the width used for structured printing
fsi.ShowProperties <- false // Control whether properties of .NET objects are shown by default
fsi.ShowIEnumerable <- false // Control whether sequence values are expanded by default
fsi.ShowDeclarationValues <- false // Control whether values are shown for declaration outputs
open System
type DateAndLabel =
{ Date: DateTime
Label: string }
let newYearsDay1999 =
{ Date = DateTime(1999, 1, 1)
Label = "New Year" }
If you execute the example in F# Interactive, it outputs based on the formatting option set. In this case, it affects
the formatting of date and time:
type DateAndLabel =
{ Date: DateTime
Label: string }
val newYearsDay1999 : DateAndLabel = { Date = 1999-01-01T00:00:00
Label = "New Year" }
let x = MyList([1..10])
This outputs:
If the transformer function passed to fsi.AddPrintTransformer returns null , then the print transformer is
ignored. This can be used to filter any input value by starting with type obj . For example:
let y = "beep"
This outputs:
Related topics
T IT L E DESC RIP T IO N
The identifiers __LINE__ , __SOURCE_DIRECTORY__ and __SOURCE_FILE__ are built-in values that enable you to
access the source line number, directory and file name in your code.
Syntax
__LINE__
__SOURCE_DIRECTORY__
__SOURCE_FILE__
Remarks
Each of these values has type string .
The following table summarizes the source line, file, and path identifiers that are available in F#. These identifiers
are not preprocessor macros; they are built-in values that are recognized by the compiler.
__SOURCE_FILE__ Evaluates to the current source file name, without its path,
considering #line directives.
For more information about the #line directive, see Compiler Directives.
Example
The following code example demonstrates the use of these values.
let printSourceLocation() =
printfn "Line: %s" __LINE__
printfn "Source Directory: %s" __SOURCE_DIRECTORY__
printfn "Source File: %s" __SOURCE_FILE__
printSourceLocation()
Output:
Line: 4
Source Directory: C:\Users\username\Documents\Visual Studio 2017\Projects\SourceInfo\SourceInfo
Source File: Program.fs
See also
Compiler Directives
F# Language Reference
Caller information
3/6/2021 • 2 minutes to read • Edit Online
By using Caller Info attributes, you can obtain information about the caller to a method. You can obtain file path
of the source code, the line number in the source code, and the member name of the caller. This information is
helpful for tracing, debugging, and creating diagnostic tools.
To obtain this information, you use attributes that are applied to optional parameters, each of which has a
default value. The following table lists the Caller Info attributes that are defined in the
System.Runtime.CompilerServices namespace:
Example
The following example shows how you might use these attributes to trace a caller.
open System.Diagnostics
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
type Tracer() =
member _.DoTrace(message: string,
[<CallerMemberName; Optional; DefaultParameterValue("")>] memberName: string,
[<CallerFilePath; Optional; DefaultParameterValue("")>] path: string,
[<CallerLineNumber; Optional; DefaultParameterValue(0)>] line: int) =
Trace.WriteLine(sprintf $"Message: {message}")
Trace.WriteLine(sprintf $"Member name: {memberName}")
Trace.WriteLine(sprintf $"Source file path: {path}")
Trace.WriteLine(sprintf $"Source line number: {line}")
Remarks
Caller Info attributes can only be applied to optional parameters. The Caller Info attributes cause the compiler to
write the proper value for each optional parameter decorated with a Caller Info attribute.
Caller Info values are emitted as literals into the Intermediate Language (IL) at compile time. Unlike the results of
the StackTrace property for exceptions, the results aren't affected by obfuscation.
You can explicitly supply the optional arguments to control the caller information or to hide caller information.
Member names
You can use the CallerMemberName attribute to avoid specifying the member name as a String argument to the
called method. By using this technique, you avoid the problem that Rename Refactoring doesn't change the
String values. This benefit is especially useful for the following tasks:
C A L L S O C C URS W IT H IN M EM B ER N A M E RESULT
Method, property, or event The name of the method, property, or event from which the
call originated.
User-defined operators or conversions The generated name for the member, for example,
"op_Addition".
Attribute constructor The name of the member to which the attribute is applied. If
the attribute is any element within a member (such as a
parameter, a return value, or a generic type parameter), this
result is the name of the member that's associated with that
element.
No containing member (for example, assembly-level or The default value of the optional parameter.
attributes that are applied to types)
See also
Attributes
Named arguments
Optional parameters
Verbose Syntax
3/6/2021 • 2 minutes to read • Edit Online
There are two forms of syntax available for many constructs in the F# language: verbose syntax and lightweight
syntax. The verbose syntax is not as commonly used, but has the advantage of being less sensitive to
indentation. The lightweight syntax is shorter and uses indentation to signal the beginning and end of
constructs, rather than additional keywords like begin , end , in , and so on. The default syntax is the
lightweight syntax. This topic describes the syntax for F# constructs when lightweight syntax is not enabled.
Verbose syntax is always enabled, so even if you enable lightweight syntax, you can still use verbose syntax for
some constructs. You can disable lightweight syntax by using the #light "off" directive.
Table of Constructs
The following table shows the lightweight and verbose syntax for F# language constructs in contexts where
there is a difference between the two forms. In this table, angle brackets (<>) enclose user-supplied syntax
elements. Refer to the documentation for each language construct for more detailed information about the
syntax used within these constructs.
compound expressions
<expression1> <expression1>; <expression2>
<expression2>
code block
( begin
<expression1> <expression1>;
<expression2> <expression2>;
) end
`for...do`
for counter = start to for counter = start to
finish do finish do
... ...
done
`while...do`
while <condition> do while <condition> do
... ...
done
`for...in`
for var in start .. finish for var in start .. finish
do do
... ...
done
`do`
do do
... ...
in
record
type <record-name> = type <record-name> =
{ {
<field-declarations> <field-declarations>
} }
<value-or-member- with
definitions> <value-or-member-
definitions>
end
class
type <class-name>(<params>) type <class-name>(<params>)
= =
... class
...
end
structure
[<StructAttribute>] type <structure-name> =
type <structure-name> = struct
... ...
end
discriminated union
type <union-name> = type <union-name> =
| ... | ...
| ... | ...
... ...
<value-or-member with
definitions> <value-or-member-
definitions>
end
interface
type <interface-name> = type <interface-name> =
... interface
...
end
object expression
{ new <type-name> { new <type-name>
with with
<value-or-member- <value-or-member-
definitions> definitions>
<interface- end
implementations> <interface-
} implementations>
}
interface implementation
interface <interface-name> interface <interface-name>
with with
<value-or-member- <value-or-member-
definitions> definitions>
end
type extension
type <type-name> type <type-name>
with with
<value-or-member- <value-or-member-
definitions> definitions>
end
module
module <module-name> = module <module-name> =
... begin
...
end
See also
F# Language Reference
Compiler Directives
Code Formatting Guidelines
F# compiler messages
3/13/2021 • 2 minutes to read • Edit Online
This section details compiler errors and warnings that the F# compiler will emit for certain constructs. The
default sets of errors can be changed by:
Treating specific warnings as if they were errors by using the -warnaserror+ compiler option,
Ignoring specific warnings by using the -nowarn compiler option
If a particular warning or error is not yet recorded in this section:
Go to the end of this page and send feedback that includes the number or text of the error, or
Add it yourself by following the instructions in create-new-fsharp-compiler-message.fsx and opening a pull
request for this repository.
See also
F# Compiler Options
Interactive programming with F#
3/10/2021 • 8 minutes to read • Edit Online
F# Interactive (dotnet fsi) is used to run F# code interactively at the console, or to execute F# scripts. In other
words, F# interactive executes a REPL (Read, Evaluate, Print Loop) for the F# language.
To run F# Interactive from the console, run dotnet fsi . You will find dotnet fsi in any .NET SDK.
For information about available command-line options, see F# Interactive Options.
>
The code's formatting is preserved, and there is a double semicolon ( ;; ) terminating the input. F# Interactive
then evaluated the code and printed the results!
Scripting with F#
Evaluating code interactively in F# Interactive can be a great learning tool, but you'll quickly find that it's not as
productive as writing code in a normal editor. To support normal code editing, you can write F# scripts.
Scripts use the file extension .fsx . Instead of compiling source code and then later running the compiled
assembly, you can just run dotnet fsi and specify the filename of the script of F# source code, and F# interactive
reads the code and executes it in real time. For example, consider the following script called Script.fsx :
let getOddSquares xs =
xs
|> List.filter (fun x -> x % 2 <> 0)
|> List.map (fun x -> x * x)
When this file is created in your machine, you can run it with dotnet fsi and see the output directly in your
terminal window:
F# scripting is natively supported in Visual Studio, Visual Studio Code, and Visual Studio for Mac.
F# Interactive supports referencing NuGet packages with the #r "nuget:" syntax and an optional version:
#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json
If a version is not specified, the highest available non-preview package is taken. To reference a specific version,
introduce the version via a comma. This can be handy when referencing a preview version of a package. For
example, consider this script using a preview version of DiffSharp:
// A 1D tensor
let t1 = dsharp.tensor [ 0.0 .. 0.2 .. 1.0 ]
// A 2x2 tensor
let t2 = dsharp.tensor [ [ 0; 1 ]; [ 2; 2 ] ]
#i "nuget:https://my-remote-package-source/index.json"
#i @"path-to-my-local-source"
This will tell the resolution engine under the covers to also take into account the remote and/or local sources
added to a script.
You can specify as many package references as you like in a script.
NOTE
There's currently a limitation for scripts that use framework references (e.g. Microsoft.NET.Sdk.Web or
Microsoft.NET.Sdk.WindowsDesktop ). Packages like Saturn, Giraffe, WinForms are not available. This is being tracked in
issue #9417.
For more information, see package management extensibility and other extensions.
// MyAssembly.fs
module MyAssembly
let myFunction x y = x + 2 * y
One compiled, you can reference it in a file called Script.fsx like so:
#r "path/to/MyAssembly.dll"
let square x = x * x
And the consuming file, Script2.fsx :
#load "Script1.fsx"
open Script1
Note that the open Script1 declaration is required. This is because constructs in an F# script are compiled into a
top-level module that is the name of the script file it is in.
You can evaluate Script2.fsx like so:
When evaluated, it prints all arguments. The first argument is always the name of the script that is evaluated:
#load "file-name.fsx" Reads a source file, compiles it, and runs it.
DIREC T IVE DESC RIP T IO N
#time "on" or #time "off" By itself, #time toggles whether to display performance
information. When it is "on" , F# Interactive measures real
time, CPU time, and garbage collection information for each
section of code that is interpreted and executed.
When you specify files or paths in F# Interactive, a string literal is expected. Therefore, files and paths must be in
quotation marks, and the usual escape characters apply. You can use the @ character to cause F# Interactive to
interpret a string that contains a path as a verbatim string. This causes F# Interactive to ignore any escape
characters.
#if INTERACTIVE
// Some code that executes only in FSI
// ...
#endif
Related articles
T IT L E DESC RIP T IO N
The following articles describe guidelines for formatting F# code and topical guidance for features of the
language and how they should be used.
This guidance has been formulated based on the use of F# in large codebases with a diverse group of
programmers. This guidance generally leads to successful use of F# and minimizes frustrations when
requirements for programs change over time.
Next steps
The F# code formatting guidelines provide guidance on how to format code so that it is easy to read.
The F# coding conventions provide guidance for F# programming idioms that will help the long-term
maintenance of larger F# codebases.
The F# component design guidelines provide guidance for authoring F# components, such as libraries.
F# code formatting guidelines
6/17/2021 • 26 minutes to read • Edit Online
This article offers guidelines for how to format your code so that your F# code is:
More legible
In accordance with conventions applied by formatting tools in Visual Studio and other editors
Similar to other code online
These guidelines are based on A comprehensive guide to F# Formatting Conventions by Anh-Dung Phan.
let subtractThenAdd x = x - 1 + 3
Unary - operators should always be immediately followed by the value they are negating:
// OK
let negate x = -x
// Bad
let negateBad x = - x
Adding a white-space character after the - operator can lead to confusion for others.
In summary, it's important to always:
Surround binary operators with white space
Never have trailing white space after a unary operator
The binary arithmetic operator guideline is especially important. Failing to surround a binary - operator, when
combined with certain formatting choices, could lead to interpreting it as a unary - .
Surround a custom operator definition with white space
Always use white space to surround an operator definition:
// OK
let ( !> ) x f = f x
// Bad
let (!>) x f = f x
For any custom operator that starts with * and that has more than one character, you need to add a white
space to the beginning of the definition to avoid a compiler ambiguity. Because of this, we recommend that you
simply surround the definitions of all operators with a single white-space character.
Surround function parameter arrows with white space
When defining the signature of a function, use white space around the -> symbol:
// OK
type MyFun = int -> int -> string
// Bad
type MyFunBad = int->int->string
// OK
let myFun (a: decimal) b c = a + b + c
// Bad
let myFunBad (a:decimal)(b)c = a + b + c
// OK
let myLongValueName =
someExpression
|> anotherExpression
// Bad
let myLongValueName = someExpression
|> anotherExpression
This is sometimes called “vanity alignment” or “vanity indentation”. The primary reasons for avoiding this are:
Important code is moved far to the right
There is less width left for the actual code
Renaming can break the alignment
Do the same for do / do! in order to keep the indentation consistent with let / let! . Here is an example using
do in a class:
// OK
type Foo () =
let foo =
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
do
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
// Bad - notice the "do" expression is indented one space less than the `let` expression
type Foo () =
let foo =
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
do fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
Here is an example with do! using 2 spaces of indentation (because with do! there is coincidentally no
difference between the approaches when using 4 spaces of indentation):
// OK
async {
let! foo =
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
do!
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
}
// Bad - notice the "do!" expression is indented two spaces more than the `let!` expression
async {
let! foo =
fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
do! fooBarBaz
|> loremIpsumDolorSitAmet
|> theQuickBrownFoxJumpedOverTheLazyDog
}
let longFunctionWithLotsOfParametersAndReturnType
(aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
(aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
(aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
: ReturnType =
// ... the body of the method follows
let longFunctionWithLongTupleParameter
(
aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
) =
// ... the body of the method follows
let longFunctionWithLongTupleParameterAndReturnType
(
aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
) : ReturnType =
// ... the body of the method follows
type TM() =
member _.LongMethodWithLotsOfParameters
(
aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
) =
// ... the body of the method
type TC
(
aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse
) =
// ... the body of the class follows
If the parameters are currified, place the = character along with any return type on a new line:
type C() =
member _.LongMethodWithLotsOfCurrifiedParamsAndReturnType
(aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
(aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
(aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
: ReturnType =
// ... the body of the method
member _.LongMethodWithLotsOfCurrifiedParams
(aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
(aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
(aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
=
// ... the body of the method
This is a way to avoid too long lines (in case return type might have long name) and have less line-damage
when adding parameters.
Type annotations
Right-pad value and function argument type annotations
When defining values or arguments with type annotations, use white space after the : symbol, but not before:
// OK
let complexFunction (a: int) (b: int) c = a + b + c
let expensiveToCompute: int = 0 // Type annotation for let-bound value
type C() =
member _.Property: int = 1
// Bad
let complexFunctionBad (a :int) (b :int) (c:int) = a + b + c
let expensiveToComputeBad1:int = 1
let expensiveToComputeBad2 :int = 2
// OK
let myFun (a: decimal) b c : decimal = a + b + c // Type annotation for the return type of a function
let anotherFun (arg: int) : unit = () // Type annotation for return type of a function
type C() =
member _.SomeMethod(x: int) : int = 1 // Type annotation for return type of a member
// Bad
let myFunBad (a: decimal) b c:decimal = a + b + c
let anotherFunBad (arg: int): unit = ()
type C() =
member _.SomeMethod(x: int): int = 1
Formatting bindings
In all cases, the right-hand side of a binding either all goes on one line, or (if it's too long) goes on a new line
indented one scope.
For example, the following are non-compliant:
let a = """
foobar, long string
"""
type File =
member this.SaveAsync(path: string) : Async<unit> = async {
// IO operation
return ()
}
let c = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
let d = while f do
printfn "%A" x
let a =
"""
foobar, long string
"""
type File =
member this.SaveAsync(path: string) : Async<unit> =
async {
// IO operation
return ()
}
let c =
{ Name = "Bilbo"
Age = 111
Region = "The Shire" }
let d =
while f do
printfn "%A" x
Formatting comments
Generally prefer multiple double-slash comments over ML-style block comments.
// Prefer this style of comments when you want
// to express written ideas on multiple lines.
(*
ML-style comments are fine, but not a .NET-ism.
They are useful when needing to modify multi-line comments, though.
*)
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAcco
untKey.Value}"
Multi-line interpolated expressions are strongly discouraged. Instead, bind the expression result to a value and
use that in the interpolated string.
Naming conventions
Use camelCase for class-bound, expression-bound, and pattern-bound values and functions
It is common and accepted F# style to use camelCase for all names bound as local variables or in pattern
matches and function definitions.
// OK
let addIAndJ i j = i + j
// Bad
let addIAndJ I J = I+J
// Bad
let AddIAndJ i j = i + j
type MyClass() =
let doSomething () =
Use camelCase for internal and private module -bound values and functions
Use camelCase for private module-bound values, including the following:
Ad hoc functions in scripts
Values making up the internal implementation of a module or type
let emailMyBossTheLatestResults =
...
module MyModule =
let myFunction paramOne paramTwo = ...
type MyClass() =
member this.MyMethod(paramOne, paramTwo) = ...
module MyTopLevelModule
module Helpers =
module private SuperHelpers =
...
...
type IMyInterface =
abstract Something: int
type MyClass() =
member this.MyMethod(x, y) = x + y
type SchoolPerson =
| Professor
| Student
| Advisor
| Administrator
Use prefix syntax for generics ( Foo<T> ) in preference to postfix syntax ( T Foo )
F# inherits both the postfix ML style of naming generic types (for example, int list ) as well as the prefix .NET
style (for example, list<int> ). Prefer the .NET style, except for five specific types:
1. For F# Lists, use the postfix form: int list rather than list<int> .
2. For F# Options, use the postfix form: int option rather than option<int> .
3. For F# Value Options, use the postfix form: int voption rather than voption<int> .
4. For F# arrays, use the syntactic name int[] rather than int array or array<int> .
5. For Reference Cells, use int ref rather than ref<int> or Ref<int> .
Formatting tuples
A tuple instantiation should be parenthesized, and the delimiting commas within it should be followed by a
single space, for example: (1, 2) , (x, y, z) .
It is commonly accepted to omit parentheses in pattern matching of tuples:
let (x, y) = z // Destructuring
let x, y = z // OK
// OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
It is also commonly accepted to omit parentheses if the tuple is the return value of a function:
// OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
In summary, prefer parenthesized tuple instantiations, but when using tuples for pattern matching or a return
value, it is considered fine to avoid parentheses.
// OK
type Volume =
| Liter of float
| FluidOunce of float
| ImperialPint of float
// Not OK
type Volume =
| Liter of float
| USPint of float
| ImperialPint of float
When there is a single short union, you can omit the leading | .
[<NoEquality; NoComparison>]
type SynBinding =
| SynBinding of
accessibility: SynAccess option *
kind: SynBindingKind *
mustInline: bool *
isMutable: bool *
attributes: SynAttributes *
xmlDoc: PreXmlDoc *
valData: SynValData *
headPat: SynPat *
returnInfo: SynBindingReturnInfo option *
expr: SynExpr *
range: range *
seqPoint: DebugPointAtBinding
You can also use triple-slash /// comments.
type Foobar =
/// Code comment
| Foobar of int
// OK
let opt = Some ("A", 1)
// Not OK
let opt = Some("A", 1)
Instantiated Discriminated Unions that split across multiple lines should give contained data a new scope with
indentation:
let tree1 =
BinaryNode
(BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4))
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
// OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
member x.ZipAndCity = $"{x.Zip} {x.City}"
// Not OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
member x.ZipAndCity = $"{x.Zip} {x.City}"
// Unusual in F#
type PostalAddress =
{
Address: string
City: string
Zip: string
}
Placing the opening token on a new line and the closing token on a new line is preferable if you are declaring
interface implementations or members on the record:
type MyRecord =
{
SomeField: int
}
interface IMyInterface
Formatting records
Short records can be written in one line:
Records that are longer should use new lines for labels:
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Placing the opening token on a new line, the contents tabbed over one scope, and the closing token on a new
line is preferable if you are:
Moving records around in code with different indentation scopes
Piping them into a function
let rainbow =
{
Boss1 = "Jeffrey"
Boss2 = "Jeffrey"
Boss3 = "Jeffrey"
Boss4 = "Jeffrey"
Boss5 = "Jeffrey"
Boss6 = "Jeffrey"
Boss7 = "Jeffrey"
Boss8 = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"]
}
type MyRecord =
{
SomeField: int
}
interface IMyInterface
let foo a =
a
|> Option.map
(fun x ->
{
MyField = x
})
let rainbow2 =
{ rainbow with
Boss = "Jeffrey"
Lackeys = [ "Zippy"; "George"; "Bungle" ] }
And as with the record guidance, you may want to dedicate separate lines for the braces and indent one scope
to the right with the expression. In some special cases, such as wrapping a value with an optional without
parentheses, you may need to keep a brace on one line:
type S = { F1: int; F2: string }
type State = { Foo: S option }
let xs = [ 1; 2; 3 ]
let ys = [| 1; 2; 3; |]
Always use at least one space between two distinct brace-like operators. For example, leave a space between a
[ and a { .
// OK
[ { IngredientName = "Green beans"; Quantity = 250 }
{ IngredientName = "Pine nuts"; Quantity = 250 }
{ IngredientName = "Feta cheese"; Quantity = 250 }
{ IngredientName = "Olive oil"; Quantity = 10 }
{ IngredientName = "Lemon"; Quantity = 1 } ]
// Not OK
[{ IngredientName = "Green beans"; Quantity = 250 }
{ IngredientName = "Pine nuts"; Quantity = 250 }
{ IngredientName = "Feta cheese"; Quantity = 250 }
{ IngredientName = "Olive oil"; Quantity = 10 }
{ IngredientName = "Lemon"; Quantity = 1 }]
let pascalsTriangle =
[|
[| 1 |]
[| 1; 1 |]
[| 1; 2; 1 |]
[| 1; 3; 3; 1 |]
[| 1; 4; 6; 4; 1 |]
[| 1; 5; 10; 10; 5; 1 |]
[| 1; 6; 15; 20; 15; 6; 1 |]
[| 1; 7; 21; 35; 35; 21; 7; 1 |]
[| 1; 8; 28; 56; 70; 56; 28; 8; 1 |]
|]
And as with records, declaring the opening and closing brackets on their own line will make moving code
around and piping into functions easier.
When generating arrays and lists programmatically, prefer -> over do ... yield when a value is always
generated:
// Preferred
let squares = [ for x in 1..10 -> x * x ]
// Not preferred
let squares' = [ for x in 1..10 do yield x * x ]
Older versions of the F# language required specifying yield in situations where data may be generated
conditionally, or there may be consecutive expressions to be evaluated. Prefer omitting these yield keywords
unless you must compile with an older F# language version:
// Preferred
let daysOfWeek includeWeekend =
[
"Monday"
"Tuesday"
"Wednesday"
"Thursday"
"Friday"
if includeWeekend then
"Saturday"
"Sunday"
]
// Not preferred
let daysOfWeek' includeWeekend =
[
yield "Monday"
yield "Tuesday"
yield "Wednesday"
yield "Thursday"
yield "Friday"
if includeWeekend then
yield "Saturday"
yield "Sunday"
]
In some cases, do...yield may aid in readability. These cases, though subjective, should be taken into
consideration.
Formatting if expressions
Indentation of conditionals depends on the size and complexity of the expressions that make them up. Write
them on one line when:
cond , e1 , and e2 are short
e1 and e2 are not if/then/else expressions themselves.
Multiple conditionals with elif and else are indented at the same scope as the if when they follow the
rules of the one line if/then/else expressions.
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
If any of the conditions or expressions is multi-line, the entire if/then/else expression is multi-line:
if cond1 then
e1
elif cond2 then
e2
elif cond3 then
e3
else
e4
If a condition is long, place it on the next line with an extra indent. Align the if and the then keywords.
if
complexExpression a b && env.IsDevelopment()
|| secondLongerExpression
aVeryLongparameterNameOne
aVeryLongparameterNameTwo
aVeryLongparameterNameThree
"""
Multiline
string
"""
then
e1
else
e2
If you have a condition that is this long, first consider refactoring it into a separate function and calling that
function instead
let condition () =
complexExpression a b && env.IsDevelopment()
|| secondLongerExpression
aVeryLongparameterNameOne
aVeryLongparameterNameTwo
aVeryLongparameterNameThree
"""
Multiline
string
"""
if condition () then
e1
else
e2
// OK
match l with
| { him = x; her = "Posh" } :: tail -> x
| _ :: tail -> findDavid tail
| [] -> failwith "Couldn't find David"
// Not OK
match l with
| { him = x; her = "Posh" } :: tail -> x
| _ :: tail -> findDavid tail
| [] -> failwith "Couldn't find David"
If the expression on the right of the pattern matching arrow is too large, move it to the following line, indented
one step from the match / | .
Pattern matching of anonymous functions, starting by function , should generally not indent too far. For
example, indenting one scope as follows is fine:
lambdaList
|> List.map
(function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
Pattern matching in functions defined by let or let rec should be indented four spaces after starting of let ,
even if function keyword is used:
let rec sizeLambda acc =
function
| Abs(x, body) -> sizeLambda (succ acc) body
| App(lam1, lam2) -> sizeLambda (sizeLambda acc lam1) lam2
| Var v -> succ acc
try
if System.DateTime.Now.Second % 3 = 0 then
raise (new System.Exception())
else
raise (new System.ApplicationException())
with
| :? System.ApplicationException ->
printfn "A second that was not a multiple of 3"
| _ ->
printfn "A second that was a multiple of 3"
Always add a | for each clause, even when only having a single clause.
// OK
try
persistState currentState
with
| ex ->
printfn "Something went wrong: %A" ex
// Not OK
try
persistState currentState
with ex ->
printfn "Something went wrong: %A" ex
When pipelines are concerned, the same is typically also true, where a curried function is applied as an
argument on the same line:
However, you may wish to pass arguments to a function on a new line, as a matter of readability or because the
list of arguments or the argument names are too long. In that case, indent with one scope:
// OK
sprintf "\t%s - %i\n\r"
x.IngredientName x.Quantity
// OK
sprintf
"\t%s - %i\n\r"
x.IngredientName x.Quantity
// OK
let printVolumes x =
printf "Volume in liters = %f, in us pints = %f, in imperial = %f"
(convertVolumeToLiter x)
(convertVolumeUSPint x)
(convertVolumeImperialPint x)
For lambda expressions, you may also want to consider placing the body of a lambda expression on a new line,
indented by one scope, if it is long enough:
If the body of a lambda expression is multiple lines long, you should consider refactoring it into a locally-scoped
function.
Parameters should generally be indented relative to the function or fun / function keyword, regardless of the
context in which the function appears:
list1
|> List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{elem}")
list1
|> List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{elem}")
When the function take a single multiline tuple argument, the same rules for Formatting constructors, static
members, and member invocations apply.
myFunction(
478815516,
"A very long string making all of this multi-line",
1515,
false
)
acc +
(sprintf "\t%s - %i\n\r"
x.IngredientName x.Quantity)
// Preferred approach
let methods2 =
System.AppDomain.CurrentDomain.GetAssemblies()
|> List.ofArray
|> List.map (fun assm -> assm.GetTypes())
|> Array.concat
|> List.ofArray
|> List.map (fun t -> t.GetMethods())
|> Array.concat
// Not OK
let methods2 = System.AppDomain.CurrentDomain.GetAssemblies()
|> List.ofArray
|> List.map (fun assm -> assm.GetTypes())
|> Array.concat
|> List.ofArray
|> List.map (fun t -> t.GetMethods())
|> Array.concat
// Not OK either
let methods2 = System.AppDomain.CurrentDomain.GetAssemblies()
|> List.ofArray
|> List.map (fun assm -> assm.GetTypes())
|> Array.concat
|> List.ofArray
|> List.map (fun t -> t.GetMethods())
|> Array.concat
// Not OK
ctx.Response.Headers.[HeaderNames.ContentType] <- Constants.jsonApiMediaType
|> StringValues
ctx.Response.Headers.[HeaderNames.ContentLength] <- bytes.Length
|> string
|> StringValues
Formatting modules
Code in a local module must be indented relative to the module, but code in a top-level module should not be
indented. Namespace elements do not have to be indented.
// A is a top-level module.
module A
let function1 a b = a - b * b
module A2 =
let function2 a b = a * a - b * b
let comparer =
{ new IComparer<string> with
member x.Compare(s1, s2) =
let rev (s: String) =
new String (Array.rev (s.ToCharArray()))
let reversed = rev s1
reversed.CompareTo (rev s2) }
// OK
spam (ham.[1])
// Not OK
spam ( ham.[ 1 ] )
// Not OK
let makeStreamReader x = new System.IO.StreamReader(path = x)
If the expression is long, use newlines and indent one scope, rather than indenting to the bracket.
let person =
new Person(
argument1,
argument2
)
let myRegexMatch =
Regex.Match(
"my longer input string with some interesting content in it",
"myRegexPattern"
)
let untypedRes =
checker.ParseFile(
fileName,
sourceText,
parsingOptionsWithDefines
)
The same rules apply even if there is only a single multiline argument.
Option.traverse(
create
>> Result.setError [ invalidHeader "Content-Checksum" ]
)
If both generic type arguments/constraints and function parameters don’t fit, but the type
parameters/constraints alone do, place the parameters on new lines:
If the type parameters or constraints are too long, break and align them as shown below. Keep the list of type
parameters on the same line as the function, regardless of its length. For constraints, place when on the first
line, and keep each constraint on a single line regardless of its length. Place > at the end of the last line. Indent
the constraints by one level.
If the type parameters/constraints are broken up, but there are no normal function parameters, place the = on
a new line regardless:
Formatting attributes
Attributes are placed above a construct:
[<SomeAttribute>]
type MyClass() = ...
[<RequireQualifiedAccess>]
module M =
let f x = x
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
When applied to a parameter, they must be on the same line and separated by a ; separator.
Formatting literals
F# literals using the Literal attribute should place the attribute on its own line and use PascalCase naming:
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
[<CustomOperation("addOne")>]
member _.AddOne (state: int) =
state + 1
[<CustomOperation("subtractOne")>]
member _.SubtractOne (state: int) =
state - 1
[<CustomOperation("divideBy")>]
member _.DivideBy (state: int, divisor: int) =
state / divisor
[<CustomOperation("multiplyBy")>]
member _.MultiplyBy (state: int, factor: int) =
state * factor
// 10
let myNumber =
math {
addOne
addOne
addOne
subtractOne
divideBy 2
multiplyBy 10
}
The domain that's being modeled should ultimately drive the naming convention. If it is idiomatic to use a
different convention, that convention should be used instead.
F# coding conventions
6/10/2021 • 26 minutes to read • Edit Online
The following conventions are formulated from experience working with large F# codebases. The Five principles
of good F# code are the foundation of each recommendation. They are related to the F# component design
guidelines, but are applicable for any F# code, not just components such as libraries.
Organizing code
F# features two primary ways to organize code: modules and namespaces. These are similar, but do have the
following differences:
Namespaces are compiled as .NET namespaces. Modules are compiled as static classes.
Namespaces are always top level. Modules can be top-level and nested within other modules.
Namespaces can span multiple files. Modules cannot.
Modules can be decorated with [<RequireQualifiedAccess>] and [<AutoOpen>] .
The following guidelines will help you use these to organize your code.
Prefer namespaces at the top level
For any publicly consumable code, namespaces are preferential to modules at the top level. Because they are
compiled as .NET namespaces, they are consumable from C# with no issue.
// Good!
namespace MyCode
type MyClass() =
...
Using a top-level module may not appear different when called only from F#, but for C# consumers, callers may
be surprised by having to qualify MyClass with the MyCode module.
// Bad!
module MyCode
type MyClass() =
...
The [<AutoOpen>] construct can pollute the scope of what is available to callers, and the answer to where
something comes from is "magic". This is not a good thing. An exception to this rule is the F# Core Library itself
(though this fact is also a bit controversial).
However, it is a convenience if you have helper functionality for a public API that you wish to organize separately
from that public API.
module MyAPI =
[<AutoOpen>]
module private Helpers =
let helper1 x y z =
...
let myFunction1 x =
let y = ...
let z = ...
helper1 x y z
This lets you cleanly separate implementation details from the public API of a function without having to fully
qualify a helper each time you call it.
Additionally, exposing extension methods and expression builders at the namespace level can be neatly
expressed with [<AutoOpen>] .
Use [<RequireQualifiedAccess>] whenever names could conflict or you feel it helps with readability
Adding the [<RequireQualifiedAccess>] attribute to a module indicates that the module may not be opened and
that references to the elements of the module require explicit qualified access. For example, the
Microsoft.FSharp.Collections.List module has this attribute.
This is useful when functions and values in the module have names that are likely to conflict with names in other
modules. Requiring qualified access can greatly increase long-term maintainability and the ability of a library to
evolve.
[<RequireQualifiedAccess>]
module StringTokenization =
let parse s = ...
...
let s = getAString()
let parsed = StringTokenization.parse s // Must qualify to use 'parse'
In F#, elements opened into a scope can shadow others already present. This means that reordering open
statements could alter the meaning of code. As a result, any arbitrary sorting of all open statements (for
example, alphanumerically) is not recommended, lest you generate different behavior that you might expect.
Instead, we recommend that you sort them topologically; that is, order your open statements in the order in
which layers of your system are defined. Doing alphanumeric sorting within different topological layers may
also be considered.
As an example, here is the topological sorting for the F# compiler service public API file:
namespace Microsoft.FSharp.Compiler.SourceCodeServices
open System
open System.Collections.Generic
open System.Collections.Concurrent
open System.Diagnostics
open System.IO
open System.Reflection
open System.Text
open FSharp.Compiler
open FSharp.Compiler.AbstractIL
open FSharp.Compiler.AbstractIL.Diagnostics
open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AbstractIL.ILBinaryReader
open FSharp.Compiler.AbstractIL.Internal
open FSharp.Compiler.AbstractIL.Internal.Library
open FSharp.Compiler.AccessibilityLogic
open FSharp.Compiler.Ast
open FSharp.Compiler.CompileOps
open FSharp.Compiler.CompileOptions
open FSharp.Compiler.Driver
open Internal.Utilities
open Internal.Utilities.Collections
A line break separates topological layers, with each layer being sorted alphanumerically afterwards. This cleanly
organizes code without accidentally shadowing values.
// This is bad!
module MyApi =
let dep1 = File.ReadAllText "/Users/<name>/connectionstring.txt"
let dep2 = Environment.GetEnvironmentVariable "DEP_2"
Error management
Error management in large systems is a complex and nuanced endeavor, and there are no silver bullets in
ensuring your systems are fault-tolerant and behave well. The following guidelines should offer guidance in
navigating this difficult space.
Represent error cases and illegal state in types intrinsic to your domain
With Discriminated Unions, F# gives you the ability to represent faulty program state in your type system. For
example:
type MoneyWithdrawalResult =
| Success of amount:decimal
| InsufficientFunds of balance:decimal
| CardExpired of DateTime
| UndisclosedFailure
In this case, there are three known ways that withdrawing money from a bank account can fail. Each error case is
represented in the type, and can thus be dealt with safely throughout the program.
In general, if you can model the different ways that something can fail in your domain, then error handling code
is no longer treated as something you must deal with in addition to regular program flow. It is simply a part of
normal program flow, and not considered exceptional . There are two primary benefits to this:
1. It is easier to maintain as your domain changes over time.
2. Error cases are easier to unit test.
Use exceptions when errors cannot be represented with types
Not all errors can be represented in a problem domain. These kinds of faults are exceptional in nature, hence the
ability to raise and catch exceptions in F#.
First, it is recommended that you read the Exception Design Guidelines. These are also applicable to F#.
The main constructs available in F# for the purposes of raising exceptions should be considered in the following
order of preference:
F UN C T IO N SY N TA X P URP O SE
The failwith and failwithf functions should generally be avoided because they raise the base Exception
type, not a specific exception. As per the Exception Design Guidelines, you want to raise more specific exceptions
when you can.
Use exception-handling syntax
F# supports exception patterns via the try...with syntax:
try
tryGetFileContents()
with
| :? System.IO.FileNotFoundException as e -> // Do something with it here
| :? System.Security.SecurityException as e -> // Do something with it here
Reconciling functionality to perform in the face of an exception with pattern matching can be a bit tricky if you
wish to keep the code clean. One such way to handle this is to use active patterns as a means to group
functionality surrounding an error case with an exception itself. For example, you may be consuming an API that,
when it throws an exception, encloses valuable information in the exception metadata. Unwrapping a useful
value in the body of the captured exception inside the Active Pattern and returning that value can be helpful in
some situations.
Do not use monadic error handling to replace exceptions
Exceptions are often seen as taboo in functional programming. Indeed, exceptions violate purity, so it's safe to
consider them not-quite functional. However, this ignores the reality of where code must run, and that runtime
errors can occur. In general, write code on the assumption that most things aren't pure or total, to minimize
unpleasant surprises.
It is important to consider the following core strengths/aspects of Exceptions with respect to their relevance and
appropriateness in the .NET runtime and cross-language ecosystem as a whole:
They contain detailed diagnostic information, which is helpful when debugging an issue.
They are well understood by the runtime and other .NET languages.
They can reduce significant boilerplate when compared with code that goes out of its way to avoid
exceptions by implementing some subset of their semantics on an ad-hoc basis.
This third point is critical. For nontrivial complex operations, failing to use exceptions can result in dealing with
structures like this:
Which can easily lead to fragile code like pattern matching on "stringly typed" errors:
Additionally, it can be tempting to swallow any exception in the desire for a "simple" function that returns a
"nicer" type:
// This is bad!
let tryReadAllText (path : string) =
try System.IO.File.ReadAllText path |> Some
with _ -> None
Unfortunately, tryReadAllText can throw numerous exceptions based on the myriad of things that can happen
on a file system, and this code discards away any information about what might actually be going wrong in your
environment. If you replace this code with a result type, then you're back to "stringly typed" error message
parsing:
// This is bad!
let tryReadAllText (path : string) =
try System.IO.File.ReadAllText path |> Ok
with e -> Error e.Message
And placing the exception object itself in the Error constructor just forces you to properly deal with the
exception type at the call site rather than in the function. Doing this effectively creates checked exceptions, which
are notoriously unfun to deal with as a caller of an API.
A good alternative to the above examples is to catch specific exceptions and return a meaningful value in the
context of that exception. If you modify the tryReadAllText function as follows, None has more meaning:
let funcWithApplication =
printfn "My name is %s and I am %d years old!"
Both are valid functions, but funcWithApplication is a curried function. When you hover over their types in an
editor, you see this:
At the call site, tooltips in tooling such as Visual Studio will give you the type signature, but since there are no
names defined, it won't display names. Names are critical to good API design because they help callers better
understanding the meaning behind the API. Using point-free code in the public API can make it harder for callers
to understand.
If you encounter point-free code like funcWithApplication that is publicly consumable, it is recommended to do
a full η-expansion so that tooling can pick up on meaningful names for arguments.
Furthermore, debugging point-free code can be challenging, if not impossible. Debugging tools rely on values
bound to names (for example, let bindings) so that you can inspect intermediate values midway through
execution. When your code has no values to inspect, there is nothing to debug. In the future, debugging tools
may evolve to synthesize these values based on previously executed paths, but it's not a good idea to hedge
your bets on potential debugging functionality.
Consider partial application as a technique to reduce internal boilerplate
In contrast to the previous point, partial application is a wonderful tool for reducing boilerplate inside of an
application or the deeper internals of an API. It can be helpful for unit testing the implementation of more
complicated APIs, where boilerplate is often a pain to deal with. For example, the following code shows how you
can accomplish what most mocking frameworks give you without taking an external dependency on such a
framework and having to learn a related bespoke API.
For example, consider the following solution topography:
MySolution.sln
|_/ImplementationLogic.fsproj
|_/ImplementationLogic.Tests.fsproj
|_/API.fsproj
module Transactions =
let doTransaction txnContext txnType balance =
...
namespace TransactionsTestingUtil
open Transactions
module TransactionsTestable =
let getTestableTransactionRoutine mockContext = Transactions.doTransaction mockContext
Partially applying doTransaction with a mocking context object lets you call the function in all of your unit tests
without needing to construct a mocked context each time:
namespace TransactionTests
open Xunit
open TransactionTypes
open TransactionsTestingUtil
open TransactionsTestingUtil.TransactionsTestable
let testableContext =
{ new ITransactionContext with
member _.TheFirstMember() = ...
member _.TheSecondMember() = ... }
[<Fact>]
let ``Test withdrawal transaction with 0.0 for balance``() =
let expected = ...
let actual = transactionRoutine TransactionType.Withdraw 0.0
Assert.Equal(expected, actual)
Don't apply this technique universally to your entire codebase, but it is a good way to reduce boilerplate for
complicated internals and unit testing those internals.
Access control
F# has multiple options for Access control, inherited from what is available in the .NET runtime. These are not
just usable for types - you can use them for functions, too.
Prefer non- public types and members until you need them to be publicly consumable. This also minimizes
what consumers couple to.
Strive to keep all helper functionality private .
Consider the use of [<AutoOpen>] on a private module of helper functions if they become numerous.
Performance
Consider structs for small types with high allocation rates
Using structs (also called Value Types) can often result in higher performance for some code because it typically
avoids allocating objects. However, structs are not always a "go faster" button: if the size of the data in a struct
exceeds 16 bytes, copying the data can often result in more CPU time spend than using a reference type.
To determine if you should use a struct, consider the following conditions:
If the size of your data is 16 bytes or smaller.
If you're likely to have many instances of these types resident in memory in a running program.
If the first condition applies, you should generally use a struct. If both apply, you should almost always use a
struct. There may be some cases where the previous conditions apply, but using a struct is no better or worse
than using a reference type, but they are likely to be rare. It's important to always measure when making
changes like this, though, and not operate on assumption or intuition.
Consider struct tuples when grouping small value types with high allocation rates
Consider the following two functions:
When you benchmark these functions with a statistical benchmarking tool like BenchmarkDotNet, you'll find
that the runWithStructTuple function that uses struct tuples runs 40% faster and allocates no memory.
However, these results won't always be the case in your own code. If you mark a function as inline , code that
uses reference tuples may get some additional optimizations, or code that would allocate could simply be
optimized away. You should always measure results whenever performance is concerned, and never operate
based on assumption or intuition.
Consider struct records when the type is small and has high allocation rates
The rule of thumb described earlier also holds for F# record types. Consider the following data types and
functions that process them:
type Point = { X: float; Y: float; Z: float }
[<Struct>]
type SPoint = { X: float; Y: float; Z: float }
This is similar to the previous tuple code, but this time the example uses records and an inlined inner function.
When you benchmark these functions with a statistical benchmarking tool like BenchmarkDotNet, you'll find
that processStructPoint runs nearly 60% faster and allocates nothing on the managed heap.
Consider struct discriminated unions when the data type is small with high allocation rates
The previous observations about performance with struct tuples and records also holds for F# Discriminated
Unions. Consider the following code:
[<Struct>]
type SName = SName of string
It's common to define single-case Discriminated Unions like this for domain modeling. When you benchmark
these functions with a statistical benchmarking tool like BenchmarkDotNet, you'll find that structReverseName
runs about 25% faster than reverseName for small strings. For large strings, both perform about the same. So, in
this case, it's always preferable to use a struct. As previously mentioned, always measure and do not operate on
assumptions or intuition.
Although the previous example showed that a struct Discriminated Union yielded better performance, it is
common to have larger Discriminated Unions when modeling a domain. Larger data types like that may not
perform as well if they are structs depending on the operations on them, since more copying could be involved.
Functional programming and mutation
F# values are immutable by default, which allows you to avoid certain classes of bugs (especially those involving
concurrency and parallelism). However, in certain cases, in order to achieve optimal (or even reasonable)
efficiency of execution time or memory allocations, a span of work may best be implemented by using in-place
mutation of state. This is possible in an opt-in basis with F# with the mutable keyword.
Use of mutable in F# may feel at odds with functional purity. This is understandable, but functional purity
everywhere can be at odds with performance goals. A compromise is to encapsulate mutation such that callers
need not care about what happens when they call a function. This allows you to write a functional interface over
a mutation-based implementation for performance-critical code.
Wrap mutable code in immutable interfaces
With referential transparency as a goal, it is critical to write code that does not expose the mutable underbelly of
performance-critical functions. For example, the following code implements the Array.contains function in the
F# core library:
[<CompiledName("Contains")>]
let inline contains value (array:'T[]) =
checkNonNull "array" array
let mutable state = false
let mutable i = 0
while not state && i < array.Length do
state <- value = array.[i]
i <- i + 1
state
Calling this function multiple times does not change the underlying array, nor does it require you to maintain
any mutable state in consuming it. It is referentially transparent, even though almost every line of code within it
uses mutation.
Consider encapsulating mutable data in classes
The previous example used a single function to encapsulate operations using mutable data. This is not always
sufficient for more complex sets of data. Consider the following sets of functions:
open System.Collections.Generic
This code is performant, but it exposes the mutation-based data structure that callers are responsible for
maintaining. This can be wrapped inside of a class with no underlying members that can change:
open System.Collections.Generic
Closure1Table encapsulates the underlying mutation-based data structure, thereby not forcing callers to
maintain the underlying data structure. Classes are a powerful way to encapsulate data and routines that are
mutation-based without exposing the details to callers.
Prefer let mutable to reference cells
Reference cells are a way to represent the reference to a value rather than the value itself. Although they can be
used for performance-critical code, they are not recommended. Consider the following example:
let kernels =
let acc = ref Set.empty
The use of a reference cell now "pollutes" all subsequent code with having to dereference and re-reference the
underlying data. Instead, consider let mutable :
let kernels =
let mutable acc = Set.empty
Aside from the single point of mutation in the middle of the lambda expression, all other code that touches acc
can do so in a manner that is no different to the usage of a normal let -bound immutable value. This will make
it easier to change over time.
Object programming
F# has full support for objects and object-oriented (OO) concepts. Although many OO concepts are powerful
and useful, not all of them are ideal to use. The following lists offer guidance on categories of OO features at a
high level.
Consider using these features in many situations:
Dot notation ( x.Length )
Instance members
Implicit constructors
Static members
Indexer notation ( arr.[x] )
Named and Optional arguments
Interfaces and interface implementations
Don't reach for these features first, but do judiciously apply them when they are convenient to
solve a problem:
Method overloading
Encapsulated mutable data
Operators on types
Auto properties
Implementing IDisposable and IEnumerable
Type extensions
Events
Structs
Delegates
Enums
Generally avoid these features unless you must use them:
Inheritance-based type hierarchies and implementation inheritance
Nulls and Unchecked.defaultof<_>
Prefer composition over inheritance
Composition over inheritance is a long-standing idiom that good F# code can adhere to. The fundamental
principle is that you should not expose a base class and force callers to inherit from that base class to get
functionality.
Use object expressions to implement interfaces if you don't need a class
Object Expressions allow you to implement interfaces on the fly, binding the implemented interface to a value
without needing to do so inside of a class. This is convenient, especially if you only need to implement the
interface and have no need for a full class.
For example, here is the code that is run in Ionide to provide a code fix action if you've added a symbol that you
don't have an open statement for:
let private createProvider () =
{ new CodeActionProvider with
member this.provideCodeActions(doc, range, context, ct) =
let diagnostics = context.diagnostics
let diagnostic = diagnostics |> Seq.tryFind (fun d -> d.message.Contains "Unused open
statement")
let res =
match diagnostic with
| None -> [||]
| Some d ->
let line = doc.lineAt d.range.start.line
let cmd = createEmpty<Command>
cmd.title <- "Remove unused open"
cmd.command <- "fsharp.unusedOpenFix"
cmd.arguments <- Some ([| doc |> unbox; line.range |> unbox; |] |> ResizeArray)
[|cmd |]
res
|> ResizeArray
|> U2.Case1
}
Because there is no need for a class when interacting with the Visual Studio Code API, Object Expressions are an
ideal tool for this. They are also valuable for unit testing, when you want to stub out an interface with test
routines in an improvised manner.
open CNTK
The Computation name is a convenient way to denote any function that matches the signature it is aliasing.
Using Type Abbreviations like this is convenient and allows for more succinct code.
Avoid using Type Abbreviations to represent your domain
Although Type Abbreviations are convenient for giving a name to function signatures, they can be confusing
when abbreviating other types. Consider this abbreviation:
In summary, the pitfall with Type Abbreviations is that they are not abstractions over the types they are
abbreviating. In the previous example, BufferSize is just an int under the covers, with no extra data, nor any
benefits from the type system besides what int already has.
An alternative approach to using type abbreviations to represent a domain is to use single-case discriminated
unions. The previous sample can be modeled as follows:
If you write code that operates in terms of BufferSize and its underlying value, you need to construct one
rather than pass in any arbitrary integer:
module Networking =
...
let send data (BufferSize size) =
...
This reduces the likelihood of mistakenly passing an arbitrary integer into the send function, because the caller
must construct a BufferSize type to wrap a value before calling the function.
F# component design guidelines
3/6/2021 • 27 minutes to read • Edit Online
This document is a set of component design guidelines for F# programming, based on the F# Component
Design Guidelines, v14, Microsoft Research, and a version that was originally curated and maintained by the F#
Software Foundation.
This document assumes you are familiar with F# programming. Many thanks to the F# community for their
contributions and helpful feedback on various versions of this guide.
Overview
This document looks at some of the issues related to F# component design and coding. A component can mean
any of the following:
A layer in your F# project that has external consumers within that project.
A library intended for consumption by F# code across assembly boundaries.
A library intended for consumption by any .NET language across assembly boundaries.
A library intended for distribution via a package repository, such as NuGet.
Techniques described in this article follow the Five principles of good F# code, and thus utilize both functional
and object programming as appropriate.
Regardless of the methodology, the component and library designer faces a number of practical and prosaic
issues when trying to craft an API that is most easily usable by developers. Conscientious application of the .NET
Library Design Guidelines will steer you towards creating a consistent set of APIs that are pleasant to consume.
General guidelines
There are a few universal guidelines that apply to F# libraries, regardless of the intended audience for the
library.
Learn the .NET Library Design Guidelines
Regardless of the kind of F# coding you are doing, it is valuable to have a working knowledge of the .NET
Library Design Guidelines. Most other F# and .NET programmers will be familiar with these guidelines, and
expect .NET code to conform to them.
The .NET Library Design Guidelines provide general guidance regarding naming, designing classes and
interfaces, member design (properties, methods, events, etc.) and more, and are a useful first point of reference
for a variety of design guidance.
Add XML documentation comments to your code
XML documentation on public APIs ensures that users can get great Intellisense and Quickinfo when using these
types and members, and enable building documentation files for the library. See the XML Documentation about
various xml tags that can be used for additional markup within xmldoc comments.
Consider using explicit signature files (.fsi) for stable library and component APIs
Using explicit signatures files in an F# library provides a succinct summary of public API, which helps to ensure
that you know the full public surface of your library, and provides a clean separation between public
documentation and internal implementation details. Signature files add friction to changing the public API, by
requiring changes to be made in both the implementation and signature files. As a result, signature files should
typically only be introduced when an API has become solidified and is no longer expected to change
significantly.
Always follow best practices for using strings in .NET
Follow Best Practices for Using Strings in .NET guidance. In particular, always explicitly state cultural intent in the
conversion and comparison of strings (where applicable).
C O N ST RUC T C A SE PA RT EXA M P L ES N OT ES
Concrete types PascalCase Noun/ adjective List, Double, Complex Concrete types are
structs, classes,
enumerations,
delegates, records,
and unions. Though
type names are
traditionally
lowercase in OCaml,
F# has adopted the
.NET naming scheme
for types.
Union tags PascalCase Noun Some, Add, Success Do not use a prefix in
public APIs.
Optionally use a
prefix when internal,
such as
type Teams =
TAlpha | TBeta |
TDelta.
let values (external) camelCase or Noun/verb List.map, Dates.Today let-bound values are
PascalCase often public when
following traditional
functional design
patterns. However,
generally use
PascalCase when the
identifier can be used
from other .NET
languages.
Avoid abbreviations
The .NET guidelines discourage the use of abbreviations (for example, "use OnButtonClick rather than
OnBtnClick "). Common abbreviations, such as Async for "Asynchronous", are tolerated. This guideline is
sometimes ignored for functional programming; for example, List.iter uses an abbreviation for "iterate". For
this reason, using abbreviations tends to be tolerated to a greater degree in F#-to-F# programming, but should
still generally be avoided in public component design.
Avoid casing name collisions
The .NET guidelines say that casing alone cannot be used to disambiguate name collisions, since some client
languages (for example, Visual Basic) are case-insensitive.
Use acronyms where appropriate
Acronyms such as XML are not abbreviations and are widely used in .NET libraries in uncapitalized form (Xml).
Only well-known, widely recognized acronyms should be used.
Use PascalCase for generic parameter names
Do use PascalCase for generic parameter names in public APIs, including for F#-facing libraries. In particular, use
names like T , U , T1 , T2 for arbitrary generic parameters, and when specific names make sense, then for F#-
facing libraries use names like Key , Value , Arg (but not for example, TKey ).
Use either PascalCase or camelCase for public functions and values in F# modules
camelCase is used for public functions that are designed to be used unqualified (for example, invalidArg ), and
for the "standard collection functions" (for example, List.map). In both these cases, the function names act much
like keywords in the language.
Object, Type, and Module design
Use namespaces or modules to contain your types and modules
Each F# file in a component should begin with either a namespace declaration or a module declaration.
namespace Fabrikam.BasicOperationsAndTypes
type ObjectType1() =
...
type ObjectType2() =
...
module CommonOperations =
...
or
module Fabrikam.BasicOperationsAndTypes
type ObjectType1() =
...
type ObjectType2() =
...
module CommonOperations =
...
The differences between using modules and namespaces to organize code at the top level are as follows:
Namespaces can span multiple files
Namespaces cannot contain F# functions unless they are within an inner module
The code for any given module must be contained within a single file
Top-level modules can contain F# functions without the need for an inner module
The choice between a top-level namespace or module affects the compiled form of the code, and thus will affect
the view from other .NET languages should your API eventually be consumed outside of F# code.
Use methods and properties for operations intrinsic to object types
When working with objects, it is best to ensure that consumable functionality is implemented as methods and
properties on that type.
type HardwareDevice() =
The bulk of functionality for a given member need not necessarily be implemented in that member, but the
consumable piece of that functionality should be.
Use classes to encapsulate mutable state
In F#, this only needs to be done where that state is not already encapsulated by another language construct,
such as a closure, sequence expression, or asynchronous computation.
type Counter() =
// let-bound values are private in classes.
let mutable count = 0
member this.Next() =
count <- count + 1
count
type Serializer =
abstract Serialize<'T> : preserveRefEq: bool -> value: 'T -> string
abstract Deserialize<'T> : preserveRefEq: bool -> pickle: string -> 'T
In preference to:
type Serializer<'T> = {
Serialize: bool -> 'T -> string
Deserialize: bool -> string -> 'T
}
Interfaces are first-class concepts in .NET, which you can use to achieve what Functors would normally give you.
Additionally, they can be used to encode existential types into your program, which records of functions cannot.
Use a module to group functions that act on collections
When you define a collection type, consider providing a standard set of operations like CollectionType.map and
CollectionType.iter ) for new collection types.
module CollectionType =
let map f c =
...
let iter f c =
...
If you include such a module, follow the standard naming conventions for functions found in FSharp.Core.
Use a module to group functions for common, canonical functions, especially in math and DSL libraries
For example, Microsoft.FSharp.Core.Operators is an automatically opened collection of top-level functions (like
abs and sin ) provided by FSharp.Core.dll.
Likewise, a statistics library might include a module with functions erf and erfc , where this module is
designed to be explicitly or automatically opened.
Consider using RequireQualifiedAccess and carefully apply AutoOpen attributes
Adding the [<RequireQualifiedAccess>] attribute to a module indicates that the module may not be opened and
that references to the elements of the module require explicit qualified access. For example, the
Microsoft.FSharp.Collections.List module has this attribute.
This is useful when functions and values in the module have names that are likely to conflict with names in other
modules. Requiring qualified access can greatly increase the long-term maintainability and evolvability of a
library.
Adding the [<AutoOpen>] attribute to a module means the module will be opened when the containing
namespace is opened. The [<AutoOpen>] attribute may also be applied to an assembly to indicate a module that
is automatically opened when the assembly is referenced.
For example, a statistics library MathsHeaven.Statistics might contain a
module MathsHeaven.Statistics.Operators containing functions erf and erfc . It is reasonable to mark this
module as [<AutoOpen>] . This means open MathsHeaven.Statistics will also open this module and bring the
names erf and erfc into scope. Another good use of [<AutoOpen>] is for modules containing extension
methods.
Overuse of [<AutoOpen>] leads to polluted namespaces, and the attribute should be used with care. For specific
libraries in specific domains, judicious use of [<AutoOpen>] can lead to better usability.
Consider defining operator members on classes where using well-known operators is appropriate
Sometimes classes are used to model mathematical constructs such as Vectors. When the domain being
modeled has well-known operators, defining them as members intrinsic to the class is helpful.
member v.X = x
let v = Vector(5.0)
let u = v * 10.0
This guidance corresponds to general .NET guidance for these types. However, it can be additionally important in
F# coding as this allows these types to be used in conjunction with F# functions and methods with member
constraints, such as List.sumBy.
Consider using CompiledName to provide a .NET-friendly name for other .NET language consumers
Sometimes you may wish to name something in one style for F# consumers (such as a static member in lower
case so that it appears as if it were a module-bound function), but have a different style for the name when it is
compiled into an assembly. You can use the [<CompiledName>] attribute to provide a different style for non F#
code consuming the assembly.
type Vector(x:float, y:float) =
member v.X = x
member v.Y = y
[<CompiledName("Create")>]
static member create x y = Vector (x, y)
By using [<CompiledName>] , you can use .NET naming conventions for non F# consumers of the assembly.
Use method overloading for member functions, if doing so provides a simpler API
Method overloading is a powerful tool for simplifying an API that may need to perform similar functionality, but
with different options or arguments.
type Logger() =
member this.Log(message) =
...
member this.Log(message, retryPolicy) =
...
In F#, it is more common to overload on number of arguments rather than types of arguments.
Hide the representations of record and union types if the design of these types is likely to evolve
Avoid revealing concrete representations of objects. For example, the concrete representation of DateTime
values is not revealed by the external, public API of the .NET library design. At run time, the Common Language
Runtime knows the committed implementation that will be used throughout execution. However, compiled code
doesn't itself pick up dependencies on the concrete representation.
Avoid the use of implementation inheritance for extensibility
In F#, implementation inheritance is rarely used. Furthermore, inheritance hierarchies are often complex and
difficult to change when new requirements arrive. Inheritance implementation still exists in F# for compatibility
and rare cases where it is the best solution to a problem, but alternative techniques should be sought in your F#
programs when designing for polymorphism, such as interface implementation.
Function and member signatures
Use tuples for return values when returning a small number of multiple unrelated values
Here is a good example of using a tuple in a return type:
For return types containing many components, or where the components are related to a single identifiable
entity, consider using a named type instead of a tuple.
Use Async<T> for async programming at F# API boundaries
If there is a corresponding synchronous operation named Operation that returns a T , then the async operation
should be named AsyncOperation if it returns Async<T> or OperationAsync if it returns Task<T> . For commonly
used .NET types that expose Begin/End methods, consider using Async.FromBeginEnd to write extension methods
as a façade to provide the F# async programming model to those .NET APIs.
type SomeType =
member this.Compute(x:int): int =
...
member this.AsyncCompute(x:int): Async<int> =
...
Exceptions
See Error Management to learn about appropriate use of exceptions, results, and options.
Extension Members
Carefully apply F# extension members in F#-to-F# components
F# extension members should generally only be used for operations that are in the closure of intrinsic
operations associated with a type in the majority of its modes of use. One common use is to provide APIs that
are more idiomatic to F# for various .NET types:
Union Types
Use discriminated unions instead of class hierarchies for tree-structured data
Tree-like structures are recursively defined. This is awkward with inheritance, but elegant with Discriminated
Unions.
type BST<'T> =
| Empty
| Node of 'T * BST<'T> * BST<'T>
Representing tree-like data with Discriminated Unions also allows you to benefit from exhaustiveness in pattern
matching.
Use [<RequireQualifiedAccess>] on union types whose case names are not sufficiently unique
You may find yourself in a domain where the same name is the best name for different things, such as
Discriminated Union cases. You can use [<RequireQualifiedAccess>] to disambiguate case names in order to
avoid triggering confusing errors due to shadowing dependent on the ordering of open statements
Hide the representations of discriminated unions for binary compatible APIs if the design of these types is likely to evolve
Unions types rely on F# pattern-matching forms for a succinct programming model. As mentioned previously,
you should avoid revealing concrete data representations if the design of these types is likely to evolve.
For example, the representation of a discriminated union can be hidden using a private or internal declaration,
or by using a signature file.
type Union =
private
| CaseA of int
| CaseB of string
If you reveal discriminated unions indiscriminately, you may find it hard to version your library without breaking
user code. Instead, consider revealing one or more active patterns to permit pattern matching over values of
your type.
Active patterns provide an alternate way to provide F# consumers with pattern matching while avoiding
exposing F# Union Types directly.
Inline Functions and Member Constraints
Define generic numeric algorithms using inline functions with implied member constraints and statically resolved generic types
Arithmetic member constraints and F# comparison constraints are a standard for F# programming. For
example, consider the following code:
However, the logical dot-notation operations on this type are not the same as the operations on a Map – for
example, it is reasonable that the lookup operator map.[key] return the empty list if the key is not in the
dictionary, rather than raising an exception.
Use namespaces, types, and members as the primary organizational structure for your components
All files containing public functionality should begin with a namespace declaration, and the only public-facing
entities in namespaces should be types. Do not use F# modules.
Use non-public modules to hold implementation code, utility types, and utility functions.
Static types should be preferred over modules, as they allow for future evolution of the API to use overloading
and other .NET API design concepts that may not be used within F# modules.
For example, in place of the following public API:
module Fabrikam
module Utilities =
let Name = "Bob"
let Add2 x y = x + y
let Add3 x y z = x + y + z
Consider instead:
namespace Fabrikam
[<AbstractClass; Sealed>]
type Utilities =
static member Name = "Bob"
static member Add(x,y) = x + y
static member Add(x,y,z) = x + y + z
Use F# record types in vanilla .NET APIs if the design of the types won't evolve
F# record types compile to a simple .NET class. These are suitable for some simple, stable types in APIs. Consider
using the [<NoEquality>] and [<NoComparison>] attributes to suppress the automatic generation of interfaces.
Also avoid using mutable record fields in vanilla .NET APIs as these expose a public field. Always consider
whether a class would provide a more flexible option for future evolution of the API.
For example, the following F# code exposes the public API to a C# consumer:
F#:
[<NoEquality; NoComparison>]
type MyRecord =
{ FirstThing: int
SecondThing: string }
C#:
type PropLogic =
private
| And of PropLogic * PropLogic
| Not of PropLogic
| True
You may also augment types that use a union representation internally with members to provide a desired .NET-
facing API.
type PropLogic =
private
| And of PropLogic * PropLogic
| Not of PropLogic
| True
Design GUI and other components using the design patterns of the framework
There are many different frameworks available within .NET, such as WinForms, WPF, and ASP.NET. Naming and
design conventions for each should be used if you are designing components for use in these frameworks. For
example, for WPF programming, adopt WPF design patterns for the classes you are designing. For models in
user interface programming, use design patterns such as events and notification-based collections such as those
found in System.Collections.ObjectModel.
Object and Member design (for libraries for use from other .NET Languages)
Use the CLIEvent attribute to expose .NET events
Construct a DelegateEvent with a specific .NET delegate type that takes an object and EventArgs (rather than an
Event , which just uses the FSharpHandler type by default) so that the events are published in the familiar way
to other .NET languages.
type MyBadType() =
let myEv = new Event<int>()
[<CLIEvent>]
member this.MyEvent = myEv.Publish
/// A type in a component designed for use from other .NET languages
type MyGoodType() =
let myEv = new DelegateEvent<EventHandler<MyEventArgs>>()
[<CLIEvent>]
member this.MyEvent = myEv.Publish
/// A type in a component designed for use from other .NET languages
type MyType() =
let compute(x: int): Async<int> = async { ... }
member this.ComputeAsTask(x, cancellationToken) = Async.StartAsTask(compute x, cancellationToken)
Do this:
The F# function type appears as class FSharpFunc<T,U> to other .NET languages, and is less suitable for
language features and tooling that understands delegate types. When authoring a higher-order method
targeting .NET Framework 3.5 or higher, the System.Func and System.Action delegates are the right APIs to
publish to enable .NET developers to consume these APIs in a low-friction manner. (When targeting .NET
Framework 2.0, the system-defined delegate types are more limited; consider using predefined delegate types
such as System.Converter<T,U> or defining a specific delegate type.)
On the flip side, .NET delegates are not natural for F#-facing libraries (see the next Section on F#-facing
libraries). As a result, a common implementation strategy when developing higher-order methods for vanilla
.NET libraries is to author all the implementation using F# function types, and then create the public API using
delegates as a thin façade atop the actual F# implementation.
Use the TryGetValue pattern instead of returning F# option values, and prefer method overloading to taking F# option values as
arguments
Common patterns of use for the F# option type in APIs are better implemented in vanilla .NET APIs using
standard .NET design techniques. Instead of returning an F# option value, consider using the bool return type
plus an out parameter as in the "TryGetValue" pattern. And instead of taking F# option values as parameters,
consider using method overloading or optional arguments.
member this.ReturnOption() = Some 3
Use the .NET collection interface types IEnumerable<T> and IDictionary<Key,Value> for parameters and return values
Avoid the use of concrete collection types such as .NET arrays T[] , F# types list<T> , Map<Key,Value> and
Set<T> , and .NET concrete collection types such as Dictionary<Key,Value> . The .NET Library Design Guidelines
have good advice regarding when to use various collection types like IEnumerable<T> . Some use of arrays ( T[]
) is acceptable in some circumstances, on performance grounds. Note especially that seq<T> is just the F# alias
for IEnumerable<T> , and thus seq is often an appropriate type for a vanilla .NET API.
Instead of F# lists:
Use F# sequences:
Use the unit type as the only input type of a method to define a zero-argument method, or as the only return type to define a
void-returning method
Avoid other uses of the unit type. These are good:
✔ member this.NoArguments() = 3
This is bad:
Tip: If you're designing libraries for use from any .NET language, then there's no substitute for actually doing
some experimental C# and Visual Basic programming to ensure that your libraries "feel right" from these
languages. You can also use tools such as .NET Reflector and the Visual Studio Object Browser to ensure that
libraries and their documentation appear as expected to developers.
Appendix
End-to -end example of designing F# code for use by other .NET languages
Consider the following class:
open System
type Point1(angle,radius) =
new() = Point1(angle=0.0, radius=0.0)
member x.Angle = angle
member x.Radius = radius
member x.Stretch(l) = Point1(angle=x.Angle, radius=x.Radius * l)
member x.Warp(f) = Point1(angle=f(x.Angle), radius=x.Radius)
static member Circle(n) =
[ for i in 1..n -> Point1(angle=2.0*Math.PI/float(n), radius=1.0) ]
type Point1 =
new : unit -> Point1
new : angle:double * radius:double -> Point1
static member Circle : n:int -> Point1 list
member Stretch : l:double -> Point1
member Warp : f:(double -> double) -> Point1
member Angle : double
member Radius : double
Let's take a look at how this F# type appears to a programmer using another .NET language. For example, the
approximate C# "signature" is as follows:
// C# signature for the unadjusted Point1 class
public class Point1
{
public Point1();
There are some important points to notice about how F# represents constructs here. For example:
Metadata such as argument names has been preserved.
F# methods that take two arguments become C# methods that take two arguments.
Functions and lists become references to corresponding types in the F# library.
The following code shows how to adjust this code to take these things into account.
namespace SuperDuperFSharpLibrary.Types
/// Return a new point, with radius multiplied by the given factor
member x.Stretch(factor) =
RadialPoint(angle=angle, radius=radius * factor)
The fixes made to prepare this type for use as part of a vanilla .NET library are as follows:
Adjusted several names: Point1 , n , l , and f became RadialPoint , count , factor , and transform ,
respectively.
Used a return type of seq<RadialPoint> instead of RadialPoint list by changing a list construction
using [ ... ] to a sequence construction using IEnumerable<RadialPoint> .
Used the .NET delegate type System.Func instead of an F# function type.
F# is a superb language for cloud programming and is frequently used to write web applications, cloud services,
cloud-hosted microservices, and for scalable data processing.
In the following sections, you will find resources on how to use a range of Azure services with F#.
NOTE
If a particular Azure service isn't in this documentation set, please consult either the Azure Functions or .NET
documentation for that service. Some Azure services are language-independent and require no language-specific
documentation and are not listed here.
Other resources
Full documentation on all Azure services
Get started with Azure Blob Storage using F#
3/6/2021 • 12 minutes to read • Edit Online
Azure Blob Storage is a service that stores unstructured data in the cloud as objects/blobs. Blob storage can
store any type of text or binary data, such as a document, media file, or application installer. Blob storage is also
referred to as object storage.
This article shows you how to perform common tasks using Blob storage. The samples are written using F#
using the Azure Storage Client Library for .NET. The tasks covered include how to upload, list, download, and
delete blobs.
For a conceptual overview of blob storage, see the .NET guide for blob storage.
Prerequisites
To use this guide, you must first create an Azure storage account. You also need your storage access key for this
account.
open System
open System.IO
open Microsoft.Azure // Namespace for CloudConfigurationManager
open Microsoft.Azure.Storage // Namespace for CloudStorageAccount
open Microsoft.Azure.Storage.Blob // Namespace for Blob storage types
However, this is not recommended for real projects. Your storage account key is similar to the root password
for your storage account. Always be careful to protect your storage account key. Avoid distributing it to other
users, hard-coding it, or saving it in a plain-text file that is accessible to others. You can regenerate your key
using the Azure portal if you believe it may have been compromised.
For real applications, the best way to maintain your storage connection string is in a configuration file. To fetch
the connection string from a configuration file, you can do this:
// Parse the connection string and return a reference to the storage account.
let storageConnString =
CloudConfigurationManager.GetSetting("StorageConnectionString")
Using Azure Configuration Manager is optional. You can also use an API such as the .NET Framework's
ConfigurationManager type.
// Parse the connection string and return a reference to the storage account.
let storageAccount = CloudStorageAccount.Parse(storageConnString)
Now you are ready to write code that reads data from and writes data to Blob storage.
Create a container
This example shows how to create a container if it does not already exist:
By default, the new container is private, meaning that you must specify your storage access key to download
blobs from this container. If you want to make the files within the container available to everyone, you can set
the container to be public using the following code:
Anyone on the Internet can see blobs in a public container, but you can modify or delete them only if you have
the appropriate account access key or a shared access signature.
// Create or overwrite the "myblob.txt" blob with contents from the local file.
do blockBlob.UploadFromFile(localFile)
// Loop over items within the container and output the length and URI.
for item in container.ListBlobs(null, false) do
match item with
| :? CloudBlockBlob as blob ->
printfn "Block blob of length %d: %O" blob.Properties.Length blob.Uri
| _ ->
printfn "Unknown blob type: %O" (item.GetType())
You can also name blobs with path information in their names. This creates a virtual directory structure that you
can organize and traverse as you would a traditional file system. The directory structure is virtual only - the only
resources available in Blob storage are containers and blobs. However, the storage client library offers a
CloudBlobDirectory object to refer to a virtual directory and simplify the process of working with blobs that are
organized in this way.
For example, consider the following set of block blobs in a container named photos :
photo1.jpg
2015/architecture/description.txt
2015/architecture/photo3.jpg
2015/architecture/photo4.jpg
2016/architecture/photo5.jpg
2016/architecture/photo6.jpg
2016/architecture/description.txt
2016/photo7.jpg\
When you call ListBlobs on a container (as in the above sample), a hierarchical listing is returned. If it contains
both CloudBlobDirectory and CloudBlockBlob objects, representing the directories and blobs in the container,
respectively, then the resulting output looks similar to this:
Directory: https://<accountname>.blob.core.windows.net/photos/2015/
Directory: https://<accountname>.blob.core.windows.net/photos/2016/
Block blob of length 505623: https://<accountname>.blob.core.windows.net/photos/photo1.jpg
Optionally, you can set the UseFlatBlobListing parameter of the ListBlobs method to true . In this case, every
blob in the container is returned as a CloudBlockBlob object. The call to ListBlobs to return a flat listing looks
like this:
// Loop over items within the container and output the length and URI.
for item in container.ListBlobs(null, true) do
match item with
| :? CloudBlockBlob as blob ->
printfn "Block blob of length %d: %O" blob.Properties.Length blob.Uri
| _ ->
printfn "Unexpected blob type: %O" (item.GetType())
and, depending on the current contents of your container, the results look like this:
Download blobs
To download blobs, first retrieve a blob reference and then call the DownloadToStream method. The following
example uses the DownloadToStream method to transfer the blob contents to a stream object that you can then
persist to a local file.
You can also use the DownloadToStream method to download the contents of a blob as a text string.
let text =
use memoryStream = new MemoryStream()
blobToDownload.DownloadToStream(memoryStream)
Text.Encoding.UTF8.GetString(memoryStream.ToArray())
Delete blobs
To delete a blob, first get a blob reference and then call the Delete method on it.
// Retrieve reference to a blob named "myblob.txt".
let blobToDelete = container.GetBlockBlobReference("myblob.txt")
The sample defines an asynchronous method, using an async block. The let! keyword suspends execution of
the sample method until the listing task completes.
let ListBlobsSegmentedInFlatListing(container:CloudBlobContainer) =
async {
printfn ""
We can now use this asynchronous routine as follows. First you upload some dummy data (using the local file
created earlier in this tutorial).
// Create some dummy data by uploading the same file over and over again
for i in 1 .. 100 do
let blob = container.GetBlockBlobReference("myblob" + string i + ".txt")
use fileStream = System.IO.File.OpenRead(localFile)
blob.UploadFromFile(localFile)
Now, call the routine. You use Async.RunSynchronously to force the execution of the asynchronous operation.
// Create the append blob. Note that if the blob already exists, the
// CreateOrReplace() method will overwrite it. You can check whether the
// blob exists to avoid overwriting it by using CloudAppendBlob.Exists().
appendBlob.CreateOrReplace()
let numBlocks = 10
// Simulate a logging operation by writing text data and byte data to the
// end of the append blob.
for i in 0 .. numBlocks - 1 do
let msg = sprintf "Timestamp: %u \tLog Entry: %d\n" DateTime.UtcNow bytes.[i]
appendBlob.AppendText(msg)
See Understanding Block Blobs, Page Blobs, and Append Blobs for more information about the differences
between the three types of blobs.
Concurrent access
To support concurrent access to a blob from multiple clients or multiple process instances, you can use ETags or
leases .
Etag - provides a way to detect that the blob or container has been modified by another process
Lease - provides a way to obtain exclusive, renewable, write, or delete access to a blob for a period of
time
For more information, see Managing Concurrency in Microsoft Azure Storage.
Naming containers
Every blob in Azure storage must reside in a container. The container forms part of the blob name. For example,
mydata is the name of the container in these sample blob URIs:
https://storagesample.blob.core.windows.net/mydata/blob1.txt
https://storagesample.blob.core.windows.net/mydata/photos/myphoto.jpg
A container name must be a valid DNS name, conforming to the following naming rules:
1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-)
character.
2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive
dashes are not permitted in container names.
3. All letters in a container name must be lowercase.
4. Container names must be from 3 through 63 characters long.
The name of a container must always be lowercase. If you include an upper-case letter in a container name, or
otherwise violate the container naming rules, you may receive a 400 error (Bad Request).
Next steps
Now that you've learned the basics of Blob storage, follow these links to learn more.
Tools
F# AzureStorageTypeProvider
An F# Type Provider that can be used to explore Blob, Table, and Queue Azure Storage assets and easily
apply CRUD operations on them.
FSharp.Azure.Storage
An F# API for using Microsoft Azure Table Storage service
Microsoft Azure Storage Explorer (MASE)
A free, standalone app from Microsoft that enables you to work visually with Azure Storage data on
Windows, OS X, and Linux.
Blob storage reference
Azure Storage APIs for .NET
Azure Storage Services REST API Reference
Related guides
Azure Blob Storage Samples for .NET
Get started with AzCopy
Configure Azure Storage connection strings
Azure Storage Team Blog
Quickstart: Use .NET to create a blob in object storage
Get started with Azure File Storage using F#
7/2/2021 • 6 minutes to read • Edit Online
Azure File Storage is a service that offers file shares in the cloud using the standard Server Message Block (SMB)
Protocol. Both SMB 2.1 and SMB 3.0 are supported. With Azure File Storage, you can migrate legacy applications
that rely on file shares to Azure quickly and without costly rewrites. Applications running in Azure virtual
machines or cloud services or from on-premises clients can mount a file share in the cloud, just as a desktop
application mounts a typical SMB share. Any number of application components can then mount and access the
File storage share simultaneously.
For a conceptual overview of file storage, see the .NET guide for file storage.
Prerequisites
To use this guide, you must first create an Azure storage account. You'll also need your storage access key for
this account.
open System
open System.IO
open Azure
open Azure.Storage // Namespace for StorageSharedKeyCredential
open Azure.Storage.Blobs // Namespace for BlobContainerClient
open Azure.Storage.Sas // Namespace for ShareSasBuilder
open Azure.Storage.Files.Shares // Namespace for File storage types
open Azure.Storage.Files.Shares.Models // Namespace for ShareServiceProperties
However, this is not recommended for real projects. Your storage account key is similar to the root password
for your storage account. Always be careful to protect your storage account key. Avoid distributing it to other
users, hard-coding it, or saving it in a plain-text file that is accessible to others. You can regenerate your key
using the Azure portal if you believe it may have been compromised.
For real applications, the best way to maintain your storage connection string is in a configuration file. To fetch
the connection string from a configuration file, you can do this:
// Parse the connection string and return a reference to the storage account.
let storageConnString =
CloudConfigurationManager.GetSetting("StorageConnectionString")
Using Azure Configuration Manager is optional. You can also use an API such as the .NET Framework's
ConfigurationManager type.
Now you are ready to write code that reads data from and writes data to File storage.
share.CreateIfNotExistsAsync()
Create a directory
Here, you get the directory. You create if it doesn't already exist.
For more information about creating and using shared access signatures, see Using Shared Access Signatures
(SAS) and Create and use a SAS with Blob storage.
Copy files
You can copy a file to another file or to a blob, or a blob to a file. If you are copying a blob to a file, or a file to a
blob, you must use a shared access signature (SAS) to authenticate the source object, even if you are copying
within the same storage account.
Copy a file to another file
Here, you copy a file to another file in the same share. Because this copy operation copies between files in the
same storage account, you can use Shared Key authentication to perform the copy.
let sourceFile = ShareFileClient(storageConnString, "shareName", "sourceFilePath")
let destFile = ShareFileClient(storageConnString, "shareName", "destFilePath")
destFile.StartCopyAsync(sourceFile.Uri)
You can copy a blob to a file in the same way. If the source object is a blob, then create a SAS to authenticate
access to that blob during the copy operation.
// Instatiate a ShareServiceClient
let shareService = ShareServiceClient(storageConnString);
props.HourMetrics = ShareMetrics(
Enabled = true,
IncludeApis = true,
Version = "1.0",
RetentionPolicy = ShareRetentionPolicy(Enabled = true,Days = 14))
props.MinuteMetrics = ShareMetrics(
Enabled = true,
IncludeApis = true,
Version = "1.0",
RetentionPolicy = ShareRetentionPolicy(Enabled = true,Days = 7))
shareService.SetPropertiesAsync(props)
Next steps
For more information about Azure File Storage, see these links.
Conceptual articles and videos
Azure Files Storage: a frictionless cloud SMB file system for Windows and Linux
How to use Azure File Storage with Linux
Tooling support for File storage
Using Azure PowerShell with Azure Storage
How to use AzCopy with Microsoft Azure Storage
Create, download, and list blobs with Azure CLI
Reference
Storage Client Library for .NET reference
File Service REST API reference
Blog posts
Azure File Storage is now generally available
Inside Azure File Storage
Introducing Microsoft Azure File Service
Persisting connections to Microsoft Azure Files
Get started with Azure Queue Storage using F#
7/15/2021 • 6 minutes to read • Edit Online
Azure Queue Storage provides cloud messaging between application components. In designing applications for
scale, application components are often decoupled, so that they can scale independently. Queue storage delivers
asynchronous messaging for communication between application components, whether they are running in the
cloud, on the desktop, on an on-premises server, or on a mobile device. Queue storage also supports managing
asynchronous tasks and building process work flows.
About this tutorial
This tutorial shows how to write F# code for some common tasks using Azure Queue Storage. Tasks covered
include creating and deleting queues and adding, reading, and deleting queue messages.
For a conceptual overview of queue storage, see the .NET guide for queue storage.
Prerequisites
To use this guide, you must first create an Azure storage account. You'll also need your storage access key for
this account.
However, this is not recommended for real projects. Your storage account key is similar to the root password
for your storage account. Always be careful to protect your storage account key. Avoid distributing it to other
users, hard-coding it, or saving it in a plain-text file that is accessible to others. You can regenerate your key
using the Azure portal if you believe it may have been compromised.
For real applications, the best way to maintain your storage connection string is in a configuration file. To fetch
the connection string from a configuration file, you can do this:
// Parse the connection string and return a reference to the storage account.
let storageConnString =
CloudConfigurationManager.GetSetting("StorageConnectionString")
Using Azure Configuration Manager is optional. You can also use an API such as the .NET Framework's
ConfigurationManager type.
Now you are ready to write code that reads data from and writes data to Queue storage.
Create a queue
This example shows how to create a queue if it doesn't already exist:
queueClient.CreateIfNotExists()
queueClient.UpdateMessage(
updateMessage.MessageId,
updateMessage.PopReceipt,
"Updated contents.",
TimeSpan.FromSeconds(60.0))
async {
let! exists = queueClient.CreateIfNotExistsAsync() |> Async.AwaitTask
Delete a queue
To delete a queue and all the messages contained in it, call the Delete method on the queue object.
queueClient.DeleteIfExists()
Note
If you're migrating from the old libraries, they Base64-encoded messages by default, but the new libraries don't
because it's more performant. For information on how to set up encoding, see MessageEncoding.
Next steps
Now that you've learned the basics of Queue storage, follow these links to learn about more complex storage
tasks.
Azure Storage APIs for .NET
Azure Storage Type Provider
Azure Storage Team Blog
Configure Azure Storage connection strings
Azure Storage Services REST API Reference
Get started with Azure Table Storage and the Azure
Cosmos DB Table API using F#
3/6/2021 • 9 minutes to read • Edit Online
Azure Table Storage is a service that stores structured NoSQL data in the cloud. Table storage is a key/attribute
store with a schemaless design. Because Table storage is schemaless, it's easy to adapt your data as the needs of
your application evolve. Access to data is fast and cost-effective for all kinds of applications. Table storage is
typically significantly lower in cost than traditional SQL for similar volumes of data.
You can use Table storage to store flexible datasets, such as user data for web applications, address books, device
information, and any other type of metadata that your service requires. You can store any number of entities in a
table, and a storage account may contain any number of tables, up to the capacity limit of the storage account.
Azure Cosmos DB provides the Table API for applications that are written for Azure Table Storage and that
require premium capabilities such as:
Turnkey global distribution.
Dedicated throughput worldwide.
Single-digit millisecond latencies at the 99th percentile.
Guaranteed high availability.
Automatic secondary indexing.
Applications written for Azure Table Storage can migrate to Azure Cosmos DB by using the Table API with no
code changes and take advantage of premium capabilities. The Table API has client SDKs available for .NET, Java,
Python, and Node.js.
For more information, see Introduction to Azure Cosmos DB Table API.
Prerequisites
To use this guide, you must first create an Azure storage account or Azure Cosmos DB account.
However, this is not recommended for real projects. Your storage account key is similar to the root password
for your storage account. Always be careful to protect your storage account key. Avoid distributing it to other
users, hard-coding it, or saving it in a plain-text file that is accessible to others. You can regenerate your key
using the Azure portal if you believe it may have been compromised.
For real applications, the best way to maintain your storage connection string is in a configuration file. To fetch
the connection string from a configuration file, you can do this:
// Parse the connection string and return a reference to the storage account.
let storageConnString =
CloudConfigurationManager.GetSetting("StorageConnectionString")
Using Azure Configuration Manager is optional. You can also use an API such as the .NET Framework's
ConfigurationManager type.
// Parse the connection string and return a reference to the storage account.
let storageAccount = CloudStorageAccount.Parse(storageConnString)
Now you are ready to write code that reads data from and writes data to Table storage.
Create a table
This example shows how to create a table if it does not already exist:
let customer =
Customer("Walter", "Harp", "[email protected]", "425-555-0101")
Now add Customer to the table. To do so, create a TableOperation that executes on the table. In this case, you
create an Insert operation.
let customer1 =
Customer("Jeff", "Smith", "[email protected]", "425-555-0102")
let customer2 =
Customer("Ben", "Smith", "[email protected]", "425-555-0103")
let query =
TableQuery<Customer>().Where(
TableQuery.GenerateFilterCondition(
"PartitionKey", QueryComparisons.Equal, "Smith"))
let range =
TableQuery<Customer>().Where(
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition(
"PartitionKey", QueryComparisons.Equal, "Smith"),
TableOperators.And,
TableQuery.GenerateFilterCondition(
"RowKey", QueryComparisons.LessThan, "M")))
Replace an entity
To update an entity, retrieve it from the Table service, modify the entity object, and then save the changes back to
the Table service using a Replace operation. This causes the entity to be fully replaced on the server, unless the
entity on the server has changed since it was retrieved, in which case the operation fails. This failure is to
prevent your application from inadvertently overwriting changes from other sources.
try
let customer = retrieveResult.Result :?> Customer
customer.PhoneNumber <- "425-555-0103"
let replaceOp = TableOperation.Replace(customer)
table.Execute(replaceOp) |> ignore
Console.WriteLine("Update succeeeded")
with e ->
Console.WriteLine("Update failed")
Insert-or-replace an entity
Sometimes, you don't know whether an entity exists in the table. And if it does, the current values stored in it are
no longer needed. You can use InsertOrReplace to create the entity, or replace it if it exists, regardless of its
state.
try
let customer = retrieveResult.Result :?> Customer
customer.PhoneNumber <- "425-555-0104"
let replaceOp = TableOperation.InsertOrReplace(customer)
table.Execute(replaceOp) |> ignore
Console.WriteLine("Update succeeeded")
with e ->
Console.WriteLine("Update failed")
let asyncQuery =
let rec loop (cont: TableContinuationToken) = async {
let! ct = Async.CancellationToken
let! result = table.ExecuteQuerySegmentedAsync(tableQ, cont, ct) |> Async.AwaitTask
Delete an entity
You can delete an entity after you have retrieved it. As with updating an entity, this fails if the entity has changed
since you retrieved it.
Delete a table
You can delete a table from a storage account. A table that has been deleted will be unavailable to be re-created
for a period of time following the deletion.
table.DeleteIfExists()
Next steps
Now that you've learned the basics of Table storage, follow these links to learn about more complex storage
tasks and the Azure Cosmos DB Table API.
Introduction to Azure Cosmos DB Table API
Storage Client Library for .NET reference
Azure Storage Type Provider
Azure Storage Team Blog
Configuring Connection Strings
Package Management for F# Azure Dependencies
11/2/2020 • 2 minutes to read • Edit Online
Obtaining packages for Azure development is easy when you use a package manager. The two options are Paket
and NuGet.
Using Paket
If you're using Paket as your dependency manager, you can use the paket.exe tool to add Azure dependencies.
For example:
This will add WindowsAzure.Storage to your set of package dependencies for the project in the current directory,
modify the paket.dependencies file, and download the package. If you have previously set up dependencies, or
are working with a project where dependencies have been set up by another developer, you can resolve and
install dependencies locally like this:
You can update all your package dependencies to the latest version like this:
Using NuGet
If you're using NuGet as your dependency manager, you can use the nuget.exe tool to add Azure dependencies.
For example:
This will add WindowsAzure.Storage to your set of package dependencies for the project in the current directory,
and download the package. If you have previously set up dependencies, or are working with a project where
dependencies have been set up by another developer, you can resolve and install dependencies locally like this:
You can update all your package dependencies to the latest version like this:
Referencing Assemblies
In order to use your packages in your F# script, you need to reference the assemblies included in the packages
using a #r directive. For example:
> #r "packages/WindowsAzure.Storage/lib/net40/Microsoft.WindowsAzure.Storage.dll"
As you can see, you'll need to specify the relative path to the DLL and the full DLL name, which may not be
exactly the same as the package name. The path will include a framework version and possibly a package
version number. To find all the installed assemblies, you can use something like this on a Windows command
line:
> cd packages/WindowsAzure.Storage
> dir /s/b *.dll
This will give you the paths to the installed assemblies. From there, you can select the correct path for your
framework version.