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#安裝在您的電腦上,您可以執行所有使用線上的範例神鬼寓言 REPLIf you don't have F# installed on your machine, you can execute all of the samples online with the Fable REPL. 神鬼寓言是方言F#直接在您的瀏覽器中執行。Fable is a dialect of F# that executes directly in your browser. 若要檢視的範例,請遵循在 REPL 中,請參閱範例 > 深入了解 > 教學課程F# 神鬼寓言 REPL 的左側功能表列中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
        else 
            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 2nd 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

以下是哪些 basic字串操作如下所示: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

TupleTuples

Tuple是 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,您也可以建立structtuple。As of F# 4.1, you can also create struct tuples. 這些也與交互操作完整 C# 7/Visual Basic 15 元組,這也是structtuple: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)

請務必請注意,因為structtuple 是實值型別,它們無法以隱含方式轉換成參考 tuple,反之亦然。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.

管線和組合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
        result

    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 =
        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 =
        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 = [
        1
        2
        3
    ]

    /// 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 = 
        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. 它們支援的項目,快速隨機存取,且速度比 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 = 
        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 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# 提供 for 迴圈和命令式程式設計的支援,但遞迴建議,因為很容易就能保證正確性。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# 也有完整支援 Tail 呼叫最佳化,這是一種最佳化,使其只是最快的速度迴圈建構的遞迴呼叫。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,您也可以代表記錄當做structs。As 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.
[<Struct>]
type ContactCardStruct = 
    { Name     : string
      Phone    : string
      Verified : bool }

差別聯集 (DUs)是可能在多個具名的表單或案例的值。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)

您也可以使用為 DUs單一案例差別聯集,以協助進行基本類型的模型化的網域。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)

如上述範例所示,若要取得基礎值的單一案例差別聯集,您必須明確 unwrap。As the above sample demonstrates, to get the underlying value in a single-case Discriminated Union, you must explicitly unwrap it.

此外,DUs 也支援遞迴定義,可讓您輕鬆地代表樹狀結構和原本就是遞迴的資料。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.

DUs 可讓您代表遞迴結構的樹狀目錄中的資料類型,因為此遞迴結構上操作很簡單,可確保正確性。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.

此外,您可以在這裡表示為 DUsstruct[<Struct>]屬性:Additionally, 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.
[<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. 結構 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) ->
                0
            | 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>) =
        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.

您可能已經發現的項目,就是使用_模式。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. 這些通常在 Api 中使用其中null用來表示"nothing"相反的而無需擔心NullReferenceException在許多情況下。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.

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

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
    [<Measure>]
    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

類別、 記錄、 差別聯集和 Tuple 的存在會產生重要的問題: 您應該使用?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.

Tuple 是適用於從函式傳回多個值,以及使用特定彙總的值做為值本身。Tuples are great for returning multiple values from a function, and using an ad-hoc aggregate of values as a value itself.

記錄會 「 步驟向上 」 的標籤和支援選擇性的成員具有名為的 Tuple。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#.