F# 教程Tour of F#

要了解的最佳方法F#是阅读和编写F#代码。The best way to learn about F# is to read and write F# code. 本文将介绍F#语言的一些主要功能,并提供可在计算机上执行的一些代码片段。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.

中F#有两个主要概念:函数和类型。There are two primary concepts in F#: functions and types. 本教程将重点介绍这两个概念的语言功能。This tour will emphasize features of the language which fall into these two concepts.

联机执行代码Executing the code online

如果未在计算机F#上安装,则可以在浏览器 F# 中执行 WebAssembly 中的所有示例。If you don't have F# installed on your machine, you can execute all of the samples in your browser with Try F# on WebAssembly. Fable 是直接在你F#的浏览器中执行的的方言。Fable is a dialect of F# that executes directly in your browser. 若要查看复制中跟随的示例,请查看示例 > 了解F# Fable 复制的左侧菜单栏中的 > 教程。To view the samples that follow in the REPL, check out Samples > Learn > Tour of F# in the left-hand menu bar of the Fable REPL.

函数和模块Functions and Modules

任何F#程序的最基本部分都是组织到模块中的功能The most fundamental pieces of any F# program are functions organized into modules. 函数在输入时执行工作,以生成输出,它们在 "模块" 下进行组织,这是作为分组依据F#的主要方式。Functions perform work on inputs to produce outputs, and they are organized under Modules, which are the primary way you group things in F#. 它们是使用let 绑定定义的,该绑定为函数指定名称并定义其参数。They are defined using the let binding, which give the function a name and define its arguments.

module BasicFunctions = 

    /// 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

    let result2 = sampleFunction2 (7 + 4)
    printfn "The result of applying the 2nd sample function to (7 + 4) is %d" result2

    /// Conditionals use if/then/elif/else.
    /// Note that F# uses white space indentation-aware syntax, similar to languages like Python.
    let sampleFunction3 x = 
        if x < 100.0 then 
            2.0*x*x - x/5.0 + 3.0
            2.0*x*x + x/5.0 - 37.0

    let result3 = sampleFunction3 (6.5 + 4.5)

    // 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 绑定也是将值绑定到名称(类似于其他语言中的变量)的方式。let bindings are also how you bind a value to a name, similar to a variable in other languages. 默认情况下,let 绑定是不可变的,这意味着一旦值或函数绑定到名称,就不能就地更改。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. 如果需要可变绑定,可以使用 let mutable ... 语法。If you require a mutable binding, you can use let mutable ... syntax.

module Immutability =

    /// Binding a value to a name via 'let' makes it immutable.
    /// The second line of code fails to compile because 'number' is immutable and bound.
    /// Re-defining 'number' to be a different value is not allowed in F#.
    let number = 2
    // let number = 3

    /// A mutable binding.  This is required to be able to mutate the value of 'otherNumber'.
    let mutable otherNumber = 2

    printfn "'otherNumber' is %d" otherNumber

    // When mutating a value, use '<-' to assign a new value.
    // Note that '=' is not the same as this.  '=' is used to test equality.
    otherNumber <- otherNumber + 1

    printfn "'otherNumber' changed to be %d" otherNumber

数字、布尔值和字符串Numbers, Booleans, and Strings

作为一种 .NET 语言F# ,支持 .net 中存在的相同基础基元类型As a .NET language, F# supports the same underlying primitive types that exist in .NET.

下面介绍如何在中F#表示各种数值类型:Here is how various numeric types are represented in F#:

module IntegersAndNumbers = 

    /// This is a sample integer.
    let sampleInteger = 176

    /// This is a sample floating point number.
    let sampleDouble = 4.1

    /// 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 the numbers from 0 to 99.
    let sampleNumbers = [ 0 .. 99 ]

    /// 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 '%A' for generic printing.
    printfn "The table of squares from 0 to 99 is:\n%A" sampleTableOfSquares

下面是布尔值和执行基本条件逻辑的形式:Here's what Boolean values and performing basic conditional logic looks like:

module Booleans =

    /// Booleans values are 'true' and 'false'.
    let boolean1 = true
    let boolean2 = false

    /// Operators on booleans are 'not', '&&' and '||'.
    let boolean3 = not boolean1 && (boolean2 || false)

    // This line uses '%b'to print a boolean value.  This is type-safe.
    printfn "The expression 'not boolean1 && (boolean2 || false)' is %b" boolean3

基本字符串操作如下所示:And here's what basic string manipulation looks like:

module StringManipulation = 

    /// Strings use double quotes.
    let string1 = "Hello"
    let string2  = "world"

    /// Strings can also use @ to create a verbatim string literal.
    /// This will ignore escape characters such as '\', '\n', '\t', etc.
    let string3 = @"C:\Program Files\"

    /// String literals can also use triple-quotes.
    let string4 = """The computer said "hello world" when I told it to!"""

    /// String concatenation is normally done with the '+' operator.
    let helloWorld = string1 + " " + string2 

    // This line uses '%s' to print a string value.  This is type-safe.
    printfn "%s" helloWorld

    /// 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 "%s" substring


元组是一项F#重大交易。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 =

    /// A simple tuple of integers.
    let tuple1 = (1, 2, 3)

    /// A function that swaps the order of two values in a tuple. 
    /// F# Type Inference will automatically generalize the function to have a generic type,
    /// meaning that it will work with any type.
    let swapElems (a, b) = (b, a)

    printfn "The result of swapping (1, 2) is %A" (swapElems (1,2))

    /// A tuple consisting of an integer, a string,
    /// and a double-precision floating point number.
    let tuple2 = (1, "fred", 3.1415)

    printfn "tuple1: %A\ttuple2: %A" tuple1 tuple2

从F# 4.1,你还可以创建 struct 元组。As of F# 4.1, you can also create struct tuples. 它们还与 c # 7/Visual Basic 15 元组完全互操作,这两个元组也 struct 元组: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)

// Although you can
let convertFromStructTuple (struct(a, b)) = (a, b)
let convertToStructTuple (a, b) = struct(a, b)

printfn "Struct Tuple: %A\nReference tuple made from the Struct Tuple: %A" sampleStructTuple (sampleStructTuple |> convertFromStructTuple)

请务必注意,因为 struct 元组是值类型,所以它们不能隐式转换为引用元组,反之亦然。It's important to note that because struct tuples are value types, they cannot be implicitly converted to reference tuples, or vice versa. 必须在 reference 和 struct 元组之间显式转换。You must explicitly convert between a reference and struct tuple.

管道和组合Pipelines and Composition

管道运算符(如 |>)在处理中F#的数据时广泛使用。Pipe operators such as |> are used extensively when processing data in F#. 这些运算符是允许您以灵活的方式建立函数的 "管道" 的函数。These operators are functions that allow you to establish "pipelines" of functions in a flexible manner. 下面的示例演示如何利用这些运算符来生成简单的功能管道:The following example walks through how you can take advantage of these operators to build a simple functional pipeline:

module PipelinesAndComposition =

    /// Squares a value.
    let square x = x * x

    /// Adds 1 to a value.
    let addOne x = x + 1

    /// Tests if an integer value is odd via modulo.
    let isOdd x = x % 2 <> 0

    /// A list of 5 numbers.  More on lists later.
    let numbers = [ 1; 2; 3; 4; 5 ]

    /// Given a list of integers, it filters out the even numbers,
    /// squares the resulting odds, and adds 1 to the squared odds.
    let squareOddValuesAndAddOne values = 
        let odds = List.filter isOdd values
        let squares = List.map square odds
        let result = List.map addOne squares

    printfn "processing %A through 'squareOddValuesAndAddOne' produces: %A" numbers (squareOddValuesAndAddOne numbers)
    /// A shorter way to write 'squareOddValuesAndAddOne' is to nest each
    /// sub-result into the function calls themselves.
    /// This makes the function much shorter, but it's difficult to see the
    /// order in which the data is processed.
    let squareOddValuesAndAddOneNested values = 
        List.map addOne (List.map square (List.filter isOdd values))

    printfn "processing %A through 'squareOddValuesAndAddOneNested' produces: %A" numbers (squareOddValuesAndAddOneNested numbers)

    /// A preferred way to write 'squareOddValuesAndAddOne' is to use F# pipe operators.
    /// This allows you to avoid creating intermediate results, but is much more readable
    /// than nesting function calls like 'squareOddValuesAndAddOneNested'
    let squareOddValuesAndAddOnePipeline values =
        |> List.filter isOdd
        |> List.map square
        |> List.map addOne

    printfn "processing %A through 'squareOddValuesAndAddOnePipeline' produces: %A" numbers (squareOddValuesAndAddOnePipeline numbers)

    /// 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 =
        |> List.filter isOdd
        |> List.map(fun x -> x |> square |> addOne)

    printfn "processing %A through 'squareOddValuesAndAddOneShorterPipeline' produces: %A" numbers (squareOddValuesAndAddOneShorterPipeline numbers)

之前的示例使用了许多功能F#,包括列表处理函数、第一类函数和部分应用程序The previous sample made use of many features of F#, including list processing functions, first-class functions, and partial application. 尽管深入了解其中每个概念可能会变得很简单,但应清楚地了解如何在生成管道时使用函数来处理数据。Although a deep understanding of each of those concepts can become somewhat advanced, it should be clear how easily functions can be used to process data when building pipelines.

列表、数组和序列Lists, Arrays, and Sequences

列表、数组和序列是F#核心库中的三种主要集合类型。Lists, Arrays, and Sequences are three primary collection types in the F# core library.

列表是具有相同类型的元素的有序集合,不可变集合。Lists are ordered, immutable collections of elements of the same type. 它们是一种单向链接列表,这意味着它们适用于枚举,但如果它们很大,则随机访问和串联的选择会很糟糕。They are singly-linked lists, which means they are meant for enumeration, but a poor choice for random access and concatenation if they're large. 这与其他常用语言的列表相反,后者通常不使用单向链接列表来表示列表。This in contrast to Lists in other popular languages, which typically do not use a singly-linked list to represent Lists.

module Lists =

    /// Lists are defined using [ ... ].  This is an empty list.
    let list1 = [ ]  

    /// 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 = [

    /// This is a list of integers from 1 to 1000
    let numberList = [ 1 .. 1000 ]  

    /// Lists can also be generated by computations. This is a list containing 
    /// all the days of the year.
    let daysList = 
        [ for month in 1 .. 12 do
              for day in 1 .. System.DateTime.DaysInMonth(2017, month) do 
                  yield System.DateTime(2017, month, day) ]

    // Print the first 5 elements of 'daysList' using 'List.take'.
    printfn "The first 5 days of 2017 are: %A" (daysList |> List.take 5)

    /// 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 = 
        |> 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 = 
        |> 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. 它们支持快速随机访问元素,并且比F#列表更快,因为它们只是连续的内存块。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 of numbers from 1 to 1000.
    let array3 = [| 1 .. 1000 |]

    /// 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) 

    /// Sub-arrays are extracted using slicing notation.
    let evenNumbersSlice = evenNumbers.[0..500]

    /// You can loop over arrays and lists using 'for' loops.
    for word in array4 do 
        printfn "word: %s" 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'.
    let sumOfLengthsOfWords = 
        |> 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 is the empty sequence.
    let seq1 = Seq.empty

    /// This a sequence of values.
    let seq2 = seq { yield "hello"; yield "world"; yield "and"; yield "hello"; yield "world"; yield "again" }

    /// This is an on-demand sequence from 1 to 1000.
    let numbersSeq = seq { 1 .. 1000 }

    /// This is a sequence producing the words "hello" and "world"
    let seq3 = 
        seq { for word in seq2 do
                  if word.Contains("l") then 
                      yield word }

    /// This sequence producing the even numbers up to 2000.
    let evenNumbers = Seq.init 1001 (fun n -> n * 2) 

    let rnd = System.Random()

    /// This is an infinite sequence which is a random walk.
    /// This example uses yield! to return each element of a subsequence.
    let rec randomWalk x =
        seq { yield x
              yield! randomWalk (x + rnd.NextDouble() - 0.5) }

    /// This example shows the first 100 elements of the random walk.
    let first100ValuesOfRandomWalk = 
        randomWalk 5.0 
        |> Seq.truncate 100
        |> Seq.toList

    printfn "First 100 elements of a random walk: %A" first100ValuesOfRandomWalk

递归函数Recursive Functions

处理集合或元素序列通常是通过中F#的递归来完成的。Processing collections or sequences of elements is typically done with recursion in F#. 尽管F#支持循环和命令式编程,但递归是首选的,因为这样可以更轻松地保证正确性。Although F# has support for loops and imperative programming, recursion is preferred because it is easier to guarantee correctness.


下面的示例通过 match 表达式利用模式匹配。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 "Factorial of 6 is: %d" (factorial 6)

    /// Computes the greatest common factor of two integers.
    /// Since all of the recursive calls are tail calls,
    /// the compiler will turn the function into a loop,
    /// which improves performance and reduces memory consumption.
    let rec greatestCommonFactor a b =
        if a = 0 then b
        elif a < b then greatestCommonFactor a (b - a)
        else greatestCommonFactor (a - b) b

    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.
    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

    let oneThroughTen = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

    printfn "The sum 1-10 is %d" (sumListTailRecursive oneThroughTen)

F#还完全支持对尾调用优化,这是一种优化递归调用的方法,使其与循环构造一样快。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.

记录和可区分联合类型Record and Discriminated Union Types

记录类型和联合类型是代码中F#使用的两种基本数据类型,通常是在F#程序中表示数据的最佳方式。Record and Union types are two fundamental data types used in F# code, and are generally the best way to represent data in an F# program. 虽然这使得它们类似于其他语言中的类,但其主要区别之一是它们具有结构相等语义。Although this makes them similar to classes in other languages, one of their primary differences is that they have structural equality semantics. 这意味着,它们是 "本机" 可比较的,相等非常简单-只需检查是否有一个与另一个相等。This means that they are "natively" comparable and equality is straightforward - just check if one is equal to the other.

记录是命名值的聚合,具有可选成员(例如方法)。Records are an aggregate of named values, with optional members (such as methods). 如果你熟悉C#或 Java,则这些内容应类似于 Poco 或 pojo,只是具有结构相等性和不足的情况。If you're familiar with C# or Java, then these should feel similar to POCOs or POJOs - just with structural equality and less ceremony.

module RecordTypes = 

    /// This example shows how to define a new record type.  
    type ContactCard = 
        { Name     : string
          Phone    : string
          Verified : bool }
    /// This example shows how to instantiate a record type.
    let contact1 = 
        { Name = "Alf" 
          Phone = "(206) 555-0157" 
          Verified = false }

    /// 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 "")

    printfn "Alf's Contact Card: %s" (showContactCard contact1)

    /// This is an example of a Record with a member.
    type ContactCardAlternate =
        { Name     : string
          Phone    : string
          Address  : string
          Verified : bool }

        /// Members can implement object-oriented members.
        member this.PrintedContactCard =
            this.Name + " Phone: " + this.Phone + (if not this.Verified then " (unverified)" else "") + this.Address

    let contactAlternate = 
        { Name = "Alf" 
          Phone = "(206) 555-0157" 
          Verified = false 
          Address = "111 Alf Street" }
    // Members are accessed via the '.' operator on an instantiated type.
    printfn "Alf's alternate contact card is %s" contactAlternate.PrintedContactCard

从F# 4.1,你还可以将记录表示为 structAs of F# 4.1, you can also represent Records as structs. 这是通过 [<Struct>] 属性完成的:This is done with the [<Struct>] attribute:

/// Records can also be represented as structs via the 'Struct' attribute.
/// This is helpful in situations where the performance of structs outweighs
/// the flexibility of reference types.
type ContactCardStruct = 
    { Name     : string
      Phone    : string
      Verified : bool }

区分联合(du)是可能是一些命名窗体或事例的值。Discriminated Unions (DUs) are values which could be a number of named forms or cases. 类型中存储的数据可以是多个不同值之一。Data stored in the type can be one of several distinct values.

module DiscriminatedUnions = 

    /// The following represents the suit of a playing card.
    type Suit = 
        | Hearts 
        | Clubs 
        | Diamonds 
        | Spades

    /// 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

        /// Discriminated Unions can also implement object-oriented members.
        static member GetAllRanks() = 
            [ yield Ace
              for i in 2 .. 10 do yield Value i
              yield Jack
              yield Queen
              yield King ]
    /// This is a record type that combines a Suit and a Rank.
    /// It's common to use both Records and Discriminated Unions when representing data.
    type Card = { Suit: Suit; Rank: Rank }
    /// 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 } ]

    /// This example converts a 'Card' object to a string.
    let showPlayingCard (c: Card) = 
        let rankString = 
            match c.Rank with 
            | Ace -> "Ace"
            | King -> "King"
            | Queen -> "Queen"
            | Jack -> "Jack"
            | Value n -> string n
        let suitString = 
            match c.Suit with 
            | Clubs -> "clubs"
            | Diamonds -> "diamonds"
            | Spades -> "spades"
            | Hearts -> "hearts"
        rankString  + " of " + suitString

    /// This example prints all the cards in a playing deck.
    let printAllCards() = 
        for card in fullDeck do 
            printfn "%s" (showPlayingCard card)

你还可以使用 Du 作为单用例可区分联合,以帮助进行基于基元类型的域建模。You can also use DUs as Single-Case Discriminated Unions, to help with domain modeling over primitive types. 通常情况下,字符串和其他基元类型用于表示某些内容,因此具有特定的含义。Often times, 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

// You can easily instantiate a single-case DU as follows.
let address = Address "111 Alf Way"
let name = Name "Alf"
let ssn = SSN 1234567890

/// 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

// Printing single-case DUs is simple with unwrapping functions.
printfn "Address: %s, Name: %s, and SSN: %d" (address |> unwrapAddress) (name |> unwrapName) (ssn |> unwrapSSN)

如上面的示例所示,若要获取单个用例可区分联合的基础值,必须将其显式解包。As the above sample demonstrates, to get the underlying value in a single-case Discriminated Union, you must explicitly unwrap it.

此外,Du 还支持递归定义,使你能够轻松地表示树和本质上递归的数据。Additionally, DUs also support recursive definitions, allowing you to easily represent trees and inherently recursive data. 例如,下面介绍了如何使用 existsinsert 函数来表示二进制搜索树。For example, here's how you can represent a Binary Search Tree with exists and insert functions.

/// Discriminated Unions also support recursive definitions.
/// This represents a Binary Search Tree, with one case being the Empty tree,
/// and the other being a Node with a value and two subtrees.
type BST<'T> =
    | Empty
    | Node of value:'T * left: BST<'T> * right: BST<'T>

/// Check if an item exists in the binary search tree.
/// Searches recursively using Pattern Matching.  Returns true if it exists; otherwise, false.
let rec exists item bst =
    match bst with
    | Empty -> false
    | Node (x, left, right) ->
        if item = x then true
        elif item < x then (exists item left) // Check the left subtree.
        else (exists item right) // Check the right subtree.

/// Inserts an item in the Binary Search Tree.
/// Finds the place to insert recursively using Pattern Matching, then inserts a new node.
/// If the item is already present, it does not insert anything.
let rec insert item bst =
    match bst with
    | Empty -> Node(item, Empty, Empty)
    | Node(x, left, right) as node ->
        if item = x then node // No need to insert, it already exists; return the node.
        elif item < x then Node(x, insert item left, right) // Call into left subtree.
        else Node(x, left, insert item right) // Call into right subtree.

由于 Du 允许您在数据类型中表示树的递归结构,因此,对此递归结构进行操作非常简单,并且保证正确性。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.

此外,还可以将 Du 表示为具有 [<Struct>] 属性的 structAdditionally, you can represent DUs as structs with the [<Struct>] attribute:

/// Discriminated Unions can also be represented as structs via the 'Struct' attribute.
/// This is helpful in situations where the performance of structs outweighs
/// the flexibility of reference types.
/// However, there are two important things to know when doing this:
///     1. A struct DU cannot be recursively-defined.
///     2. A struct DU must have unique names for each of its cases.
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. 不能以递归方式定义结构 DU。A struct DU cannot be recursively-defined.
  2. 结构 DU 的每个事例都必须具有唯一的名称。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

模式匹配是一F#种语言功能,可用于对F#类型进行操作。Pattern Matching is the F# language feature which enables correctness for operating on F# types. 在上述示例中,您可能注意到了很多 match x with ... 语法。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 runtime concern into compile-time.

module PatternMatching =

    /// A record for a person's first and last name
    type Person = {
        First : string
        Last  : string

    /// A Discriminated Union of 3 different kinds of employees
    type Employee =
        | Engineer of engineer: Person
        | Manager of manager: Person * reports: List<Employee>
        | Executive of executive: Person * reports: List<Employee> * assistant: Employee

    /// Count everyone underneath the employee in the management hierarchy,
    /// including the employee. The matches bind names to the properties 
    /// of the cases so that those names can be used inside the match branches.
    /// Note that the names used for binding do not need to be the same as the 
    /// names given in the DU definition above.
    let rec countReports(emp : Employee) =
        1 + match emp with
            | Engineer(person) ->
            | Manager(person, reports) ->
                reports |> List.sumBy countReports
            | Executive(person, reports, assistant) ->
                (reports |> List.sumBy countReports) + countReports assistant

    /// Find all managers/executives named "Dave" who do not have any reports.
    /// This uses the 'function' shorthand to as a lambda expression.
    let rec findDaveWithOpenPosition(emps : List<Employee>) =
        |> 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.

您可能已注意到,使用了 _ 模式。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.

活动模式是另一个功能强大的构造,可与模式匹配一起使用。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:

// 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. 
// To learn more, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/active-patterns
let (|Int|_|) = parseInt
let (|Double|_|) = parseDouble
let (|Date|_|) = parseDateTimeOffset
let (|TimeSpan|_|) = parseTimeSpan

/// 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 "%s" (d.ToString())
    | TimeSpan t -> printfn "%s" (t.ToString())
    | _ -> printfn "Nothing was parse-able!"

// Call the printer with some different values to parse.
printParseResult "12"
printParseResult "12.045"
printParseResult "12/28/2016"
printParseResult "9:01PM"
printParseResult "banana!"

可选类型Optional Types

区分联合类型的一种特殊情况是 "选项类型",它非常有用,因为它是F#核心库的一部分。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 which 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. 这通常用于 null 用来表示 "nothing" 的 Api,从而无需担心在许多情况下 NullReferenceExceptionThese are often used in APIs where null is used to represent "nothing" instead, thus eliminating the need to worry about NullReferenceException in many circumstances.

/// Option values are any kind of value tagged with either 'Some' or 'None'.
/// They are used extensively in F# code to represent the cases where many other
/// languages would use null references.
/// To learn more, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/options
module OptionValues = 

    /// First, define a zip code defined via Single-case Discriminated Union.
    type ZipCode = ZipCode of string

    /// Next, define a type where the ZipCode is optional.
    type Customer = { ZipCode: ZipCode option }

    /// 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) =
        |> Option.bind calculator.GetState 
        |> Option.map calculator.GetShippingZone

度量单位Units of Measure

类型系统的一F#项独特功能是能够通过度量单位为数字文本提供上下文。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.

/// Units of measure are a way to annotate primitive numeric types in a type-safe way.
/// You can then perform type-safe arithmetic on these values.
/// To learn more, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/units-of-measure
module UnitsOfMeasure = 

    /// First, open a collection of common unit names
    open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames

    /// Define a unitized constant
    let sampleValue1 = 1600.0<meter>          

    /// Next, define a new unit type
    type mile =
        /// Conversion factor mile to meter.
        static member asMeter = 1609.34<meter/mile>

    /// Define a unitized constant
    let sampleValue2  = 500.0<mile>          

    /// Compute  metric-system constant
    let sampleValue3 = sampleValue2 * mile.asMeter   

    // Values using Units of Measure can be used just like the primitive numeric type for things like printing.
    printfn "After a %f race I would walk %f miles which would be %f meters" sampleValue1 sampleValue2 sampleValue3

F#核心库定义了许多 SI 单元类型和单位转换。The F# Core library defines many SI unit types and unit conversions. 若要了解详细信息,请查看Microsoft.FSharp.Data.UnitSystems.SI 命名空间To learn more, check out the Microsoft.FSharp.Data.UnitSystems.SI Namespace.

类和接口Classes and Interfaces

F#还对 .NET 类、接口抽象类继承等提供完全支持。F# also has full support for .NET classes, Interfaces, Abstract Classes, Inheritance, and so on.

是表示 .net 对象的类型,这些对象可以具有属性、方法和事件作为其成员Classes are types that represent .NET objects, which can have properties, methods, and events as its Members.

/// Classes are a way of defining new object types in F#, and support standard Object-oriented constructs.
/// They can have a variety of members (methods, properties, events, etc.)
/// To learn more about Classes, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/classes
/// To learn more about Members, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/members
module DefiningClasses = 

    /// A simple two-dimensional Vector class.
    /// The class's constructor is on the first line,
    /// and takes two arguments: dx and dy, both of type 'double'.
    type Vector2D(dx : double, dy : double) =

        /// This internal field stores the length of the vector, computed when the 
        /// object is constructed
        let length = sqrt (dx*dx + dy*dy)

        // 'this' specifies a name for the object's self-identifier.
        // In instance methods, it must appear before the member name.
        member this.DX = dx

        member this.DY = dy

        member this.Length = length

        /// This member is a method.  The previous members were properties.
        member this.Scale(k) = Vector2D(k * this.DX, k * this.DY)
    /// This is how you instantiate the Vector2D class.
    let vector1 = Vector2D(3.0, 4.0)

    /// Get a new scaled vector object, without modifying the original object.
    let vector2 = vector1.Scale(10.0)

    printfn "Length of vector1: %f\nLength of vector2: %f" vector1.Length vector2.Length

定义泛型类也非常简单。Defining generic classes is also very straightforward.

/// Generic classes allow types to be defined with respect to a set of type parameters.
/// In the following, 'T is the type parameter for the class.
/// To learn more, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/generics/
module DefiningGenericClasses = 

    type StateTracker<'T>(initialElement: 'T) = 

        /// This internal field store the states in a list.
        let mutable states = [ initialElement ]

        /// Add a new element to the list of states.
        member this.UpdateState newState = 
            states <- newState :: states  // use the '<-' operator to mutate the value.

        /// Get the entire list of historical states.
        member this.History = states

        /// Get the latest state.
        member this.Current = states.Head

    /// 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

若要实现接口,可以使用 interface ... with 语法或对象表达式To implement an Interface, you can use either interface ... with syntax or an Object Expression.

/// Interfaces are object types with only 'abstract' members.
/// Object types and object expressions can implement interfaces.
/// To learn more, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/interfaces
module ImplementingInterfaces =

    /// This is a type that implements IDisposable.
    type ReadFile() =

        let file = new System.IO.StreamReader("readme.txt")

        member this.ReadLine() = file.ReadLine()

        // This is the implementation of IDisposable members.
        interface System.IDisposable with
            member this.Dispose() = file.Close()

    /// This is an object that implements IDisposable via an Object Expression
    /// Unlike other languages such as C# or Java, a new type definition is not needed 
    /// to implement an interface.
    let interfaceImplementation =
        { new System.IDisposable with
            member this.Dispose() = printfn "disposed" }

要使用的类型Which Types to Use

类、记录、可区分联合和元组的存在导致了重要问题:应使用哪一个?The presence of Classes, Records, Discriminated Unions, and Tuples leads to an important question: which should you use? 与生活中的大多数操作一样,答案取决于你的情况。Like most everything in life, the answer depends on your circumstances.

元组非常适合从函数返回多个值,并使用临时值聚合作为值本身。Tuples are great for returning multiple values from a function, and using an ad-hoc aggregate of values as a value itself.

记录是元组中的 "单步执行",其命名标签和对可选成员的支持。Records are a "step up" from Tuples, having named labels and support for optional members. 它们非常适用于通过您的程序传输的数据的低计划表示形式。They are great for a low-ceremony representation of data in-transit through your program. 由于它们具有结构相等性,因此它们可用于比较。Because they have structural equality, they are easy to use with comparison.

可区分联合具有许多用途,但核心好处是能够将它们与模式匹配一起使用,以考虑数据可以具有的所有可能的 "形状"。Discriminated Unions have many uses, but the core benefit is to be able to utilize them in conjunction with Pattern Matching to account for all possible "shapes" that a data can have.

类对于大量原因非常有用,例如,当你需要表示信息并将该信息绑定到功能时。Classes are great for a huge number of reasons, such as when you need to represent information and also tie that information to functionality. 作为经验法则,当你具有在概念上与某些数据关联的功能时,使用类和面向对象编程的原则是一项巨大的好处。As a rule of thumb, when you have functionality which is conceptually tied to some data, using Classes and the principles of Object-Oriented Programming is a big benefit. 与C#和 Visual Basic 互操作时,类也是首选的数据类型,因为这些语言几乎都使用类。Classes are also the preferred data type when interoperating with C# and Visual Basic, as these languages use classes for nearly everything.

后续步骤Next Steps

现在,你已了解了语言的一些主要功能,你应该已准备好编写你的第一个F#程序!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.

用于了解详细信息的后续步骤可以是你喜欢的任何内容,但建议你在中F#介绍函数编程,以熟悉核心功能编程概念。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. 这对于在中F#构建可靠的程序非常重要。These will be essential in building robust programs in F#.

另外,请查看 F#语言参考,查看上F#的概念内容的完整集合。Also, check out the F# Language Reference to see a comprehensive collection of conceptual content on F#.