// 单行注释以双斜杠开头 (* 多行注释以包裹在 (* , *) 之间 多行注释至此结束- *) // ================================================ // 基础语法 // ================================================ // ------ "变量" (实际上默认不可变) ------ // "let" 关键字可定义一个(不可变的)变量 let myInt = 5 let myFloat = 3.14 let myString = "hello" // 注意,并不需要指定类型 // 可变变量用 "mutable" 标注 let mutable a=3 a <- 4 // a现在的值是4 // 稍有不同的可变变量 // Reference cell是一种容器,允许您使用引用语义来创建可变变量(create mutable values with reference semantics) // 译者注:和Rust的RefCell类似,提供容器内部的可变性 // 详见 https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/reference-cells let xRef = ref 10 printfn "%d" xRef.Value // 10 xRef.Value <- 11 printfn "%d" xRef.Value // 11 let a = [ref 0; ref 1] // 装有可变元素的列表 a[0].Value <- 2 // ------ 列表Lists ------ let twoToFive = [2; 3; 4; 5] // 使用方括号创建列表 // 元素间使用**分号**分隔 let oneToFive = 1 :: twoToFive // :: 创建新列表,并将新元素添加到开头 // 结果为 [1; 2; 3; 4; 5] let zeroToFive = [0; 1] @ twoToFive // @ 可合并两个列表 // 重要提示:是使用分号来分隔语句,而不是逗号!! // ------ 函数 ------ // "let" 关键字也可以用来定义函数 let square x = x * x // 注意,没有使用圆括号 square 3 // 注意,运行函数时也不需要圆括号 let add x y = x + y // 切勿将参数写成 (x, y)! // 这是元组Tuple的写法(稍后介绍), // 和函数参数完全不同 add 2 3 // 调用该函数 // 使用缩进来定义一个多行的复杂函数。不需要写分号 let evens list = let isEven x = x % 2 = 0 // 定义**子函数** "isEven"。 // 注意“相等”是使用单等号'='而不是双等号"==" List.filter isEven list // List.filter 是个 F# 库函数,它有两个参数: // - 返回值为boolean的函数 // - 待处理的列表 evens oneToFive // 调用该函数 // 圆括号可用于显式地标注计算优先级。在下述示例代码中: // 1. 首先进行 List.map 计算,该函数具有两个参数 // 2. 然后进行 List.sum 计算,将上一步的结果作为该步的参数 // 若不写圆括号,"List.map" 就会被当作参数传递给 List.sum let sumOfSquaresTo100 = List.sum ( List.map square [1..100] ) // 使用管道操作符 "|>" ,可以将上一步的输出(译者注:即返回值)输送给下一步作为输入 // 管道操作在 F# 中很常见,其语义也与 UNIX 操作系统中的管道操作非常相似。 // 使用管道操作符,可将 sumOfSquares 函数重构为如下形式: let sumOfSquaresTo100piped = [1..100] |> List.map square |> List.sum // "square" 函数就是之前定义的,求平方的函数 // 匿名函数(或作lambda函数)可用 "fun" 关键字定义 let sumOfSquaresTo100withFun = [1..100] |> List.map (fun x -> x * x) |> List.sum // F# 中没有"return" 关键字,因为函数总是返回最后一个表达式的值。 // ------ 模式匹配 ------ // 模式匹配 match..with.. 是个加强版的 case/switch let simplePatternMatch = let x = "a" match x with | "a" -> printfn "x is a" | "b" -> printfn "x is b" | _ -> printfn "x is something else" // 下划线可匹配任意值 // F# 默认不支持空值(null),您必须使用Option类型以便随后进行模式匹配 // Some(..) 和 None 均为Option类型的一种,用于表示"可能为空值"的两种情况 let validValue = Some(99) let invalidValue = None // 在下列示例中,match..with 不仅匹配了 "Some" 和 "None" 两种情况, // 还同时将 "Some" 中的内容给解包出来了 let optionPatternMatch input = match input with | Some i -> printfn "input is an int=%d" i | None -> printfn "input is missing" optionPatternMatch validValue optionPatternMatch invalidValue // ------ 打印内容 ------ // printf/printfn 函数与 C# 中的 Console.Write/WriteLine 函数类似 printfn "Printing an int %i, a float %f, a bool %b" 1 2.0 true printfn "A string %s, and something generic %A" "hello" [1; 2; 3; 4] // 若要按一定格式生成字符串,可以使用 sprintf/sprintfn 函数, // 它们与 C# 中的 String.Format 函数类似 // ================================================ // 函数漫谈 // ================================================ // F# 是函数式编程语言:函数是其一等公民,通过组合不同函数可以实现强大的功能 // 模块(Modules)可以将一系列函数组织到一起 // 每个模块使用标识符(Indentation)来唯一标记 module FunctionExamples = // 定义一个简单的求和函数 let add x y = x + y // 函数的基础使用 let a = add 1 2 printfn "1 + 2 = %i" a // 函数的“部分应用”(partial application)可事先“固化”参数 let add42 = add 42 let b = add42 1 printfn "42 + 1 = %i" b // 函数的组合(composition)可将多个函数“串接”起来 let add1 = add 1 let add2 = add 2 let add3 = add1 >> add2 let c = add3 7 printfn "3 + 7 = %i" c // 高阶函数 [1..10] |> List.map add3 |> printfn "new list is %A" // 函数列表,以及 List.reduce 函数 let add6 = [add1; add2; add3] |> List.reduce (>>) let d = add6 7 printfn "1 + 2 + 3 + 7 = %i" d // ================================================ // 列表与集合(collection) // ================================================ // F# 内置了三种有序集合: // * 列表(List)是最基本的、不可变的集合类型 // * 数组(Arrays)是可变的,按需使用可令程序更高效 // * 序列(Sequences)是惰性的、无限的集合类型(例如枚举器enumerator) // // 除此以外,还有诸如不可变哈希表、不可变哈希集合、 // 以及所有 .NET 中定义的标准集合类型等 module ListExamples = // 列表使用方括号 let list1 = ["a"; "b"] let list2 = "c" :: list1 // :: 将元素添加到开头 let list3 = list1 @ list2 // @ 将两个列表连接起来 // 列表推导式(list comprehensions),也被称为生成器(generators) let squares = [for i in 1..10 do yield i * i] // 素数生成器 // - 本例采用了模式匹配的简写语法 // - (p::xs) 匹配列表的 “第一个元素 :: 其余所有元素” ,写成 p :: xs也可以 // 此时,p匹配到列表的第一个元素,而其余元素被xs匹配到 // 这种写法被称为构造模式(cons pattern) // - 在写递归函数时,必须使用 "rec" 关键字 let rec sieve = function | (p::xs) -> p :: sieve [ for x in xs do if x % p > 0 then yield x ] | [] -> [] let primes = sieve [2..50] printfn "%A" primes // 对列表进行模式匹配 let listMatcher aList = match aList with | [] -> printfn "the list is empty" | [first] -> printfn "the list has one element %A " first | [first; second] -> printfn "list is %A and %A" first second | first :: _ -> printfn "the list has more than two elements, first element %A" first listMatcher [1; 2; 3; 4] listMatcher [1; 2] listMatcher [1] listMatcher [] // 接受列表作参数的递归函数 let rec sum aList = match aList with | [] -> 0 | x::xs -> x + sum xs sum [1..10] // ----------------------------------------- // 列表的标准库函数 // ----------------------------------------- // map let add3 x = x + 3 [1..10] |> List.map add3 // filter let even x = x % 2 = 0 [1..10] |> List.filter even // 还有很多,详见文档 module ArrayExamples = // 数组(Array)用 [| 和 |] 包裹 let array1 = [| "a"; "b" |] let first = array1.[0] // 用 . 来索引元素 // 数组和列表的模式匹配语法完全相同 let arrayMatcher aList = match aList with | [| |] -> printfn "the array is empty" | [| first |] -> printfn "the array has one element %A " first | [| first; second |] -> printfn "array is %A and %A" first second | _ -> printfn "the array has more than two elements" arrayMatcher [| 1; 2; 3; 4 |] // Array的标准库函数和List也几乎一样 [| 1..10 |] |> Array.map (fun i -> i + 3) |> Array.filter (fun i -> i % 2 = 0) |> Array.iter (printfn "value is %i. ") module SequenceExamples = // 序列(Sequence)用大括号包裹 let seq1 = seq { yield "a"; yield "b" } // 序列可以使用 "yield" 关键字,也可以包含子序列 let strange = seq { // "yield" 向序列中增添1个元素 yield 1; yield 2; // "yield!" 则是增添一个子序列 yield! [5..10] yield! seq { for i in 1..10 do if i % 2 = 0 then yield i }} // 试试看吧! strange |> Seq.toList // 序列可以通过 "Seq.unfold" 函数来创建 // 下例演示如何用这个函数创建斐波那契数列 let fib = Seq.unfold (fun (fst,snd) -> Some(fst + snd, (snd, fst + snd))) (0,1) // 试试看吧! let fib10 = fib |> Seq.take 10 |> Seq.toList printf "first 10 fibs are %A" fib10 // ================================================ // 数据类型 // ================================================ module DataTypeExamples = // 默认情况下,所有的数据类型均是不可变的 // 元组(Tuple)是一种简单快捷的匿名类型 // -- 使用逗号即可创建元组 let twoTuple = 1, 2 let threeTuple = "a", 2, true // 同样,使用模式匹配来解包元组 let x, y = twoTuple // x, y分别被赋值为1, 2 // ------------------------------------ // 记录(Record)类型由命名域构成(译者注:类似class的成员变量) // 译者注:由于“记录”一词听起来像动词,下文将以英文原文Record来指代 // ------------------------------------ // 使用 "type" 关键字和大括号来定义Record类型 type Person = {First:string; Last:string} // 使用 "let" 关键字和大括号来创建Record实例 let person1 = {First="John"; Last="Doe"} // 同样,使用模式匹配来解包Record实例 let {First = first} = person1 // first被赋值为"John" // ------------------------------------ // 联合类型(Union或Variants,类似于Rust中的枚举类型)拥有一系列的取值选项。其实例仅能从中取其一。 // 译者注:由于联合类型与Rust中的枚举类型(enum)很相似,故后文将以“枚举类型”指代之 // ------------------------------------ // 使用 "type" 关键字和竖线/管道符来定义枚举类型 type Temp = | DegreesC of float | DegreesF of float // 使用一个选项来创建枚举实例 let temp1 = DegreesF 98.6 let temp2 = DegreesC 37.0 // 模式匹配可以解包枚举实例 let printTemp = function | DegreesC t -> printfn "%f degC" t | DegreesF t -> printfn "%f degF" t printTemp temp1 printTemp temp2 // ------------------------------------ // 递归类型 // ------------------------------------ // 类型可以通过递归组合成复杂的类型,无需创建子类 type Employee = | Worker of Person | Manager of Employee list // 译者注:这儿发生了递归定义 let jdoe = {First="John"; Last="Doe"} let worker = Worker jdoe // ------------------------------------ // 使用枚举类型建模 // ------------------------------------ // 枚举类型非常适合用于表述某种状态,再也不需要用数字等标志位(flag)来表征状态啦! type EmailAddress = | ValidEmailAddress of string // 状态:合法邮件地址 | InvalidEmailAddress of string // 状态:不合法邮件地址 let trySendEmail email = match email with // 使用模式匹配 | ValidEmailAddress address -> () // 可以发送 | InvalidEmailAddress address -> () // 不能发送 // 组合使用枚举类型和Record类型为“域驱动的软件设计”(Domain Driven Design)提供了良好基础。 // 您可以定义数以百计的类型,每一种都精准地反映着一个域(Domain) type CartItem = { ProductCode: string; Qty: int } type Payment = Payment of float type ActiveCartData = { UnpaidItems: CartItem list } type PaidCartData = { PaidItems: CartItem list; Payment: Payment} type ShoppingCart = | EmptyCart // 空购物车,没有数据 | ActiveCart of ActiveCartData | PaidCart of PaidCartData // ------------------------------------ // 数据类型的内置行为 // ------------------------------------ // 核心数据类型提供了开箱即用的默认行为与性质,无需额外编码。 // * 不可变性 // * 漂亮的打印输出,在debug时尤其好用 // * 相等性与可比性 // * 可序列化性 // 使用 %A 来输出复杂数据类型 printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A" twoTuple person1 temp1 worker // 下列扑克牌示例展示了 F# 中内置的相等性与可比较性 type Suit = Club | Diamond | Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King | Ace let hand = [ Club, Ace; Heart, Three; Heart, Ace; Spade, Jack; Diamond, Two; Diamond, Ace ] // 排序 List.sort hand |> printfn "sorted hand is (low to high) %A" List.max hand |> printfn "high card is %A" List.min hand |> printfn "low card is %A" // ================================================ // 主动模式(Active patterns) // ================================================ module ActivePatternExamples = // F# 中,有一种被称为“主动模式”的特殊模式匹配 // 它可以在模式匹配中动态解析或检测模式(pattern)。 // 译者注:译者个人倾向于把此所谓“主动模式”理解为,自定义某个数据类型在接受模式匹配时,会返回哪些情况 // 主动匹配的语法形似 "banana clips" (译者注:确实不知道banana clips是啥意思) // 您可以用elif代替else if,它们在 F# 中完全等价 // 下列示例使用主动模式去匹配不同类型的字符... let (|Digit|Letter|Whitespace|Other|) ch = if System.Char.IsDigit(ch) then Digit elif System.Char.IsLetter(ch) then Letter elif System.Char.IsWhiteSpace(ch) then Whitespace else Other // ...然后使用它,可以看到解析逻辑十分清晰 let printChar ch = match ch with | Digit -> printfn "%c is a Digit" ch | Letter -> printfn "%c is a Letter" ch | Whitespace -> printfn "%c is a Whitespace" ch | _ -> printfn "%c is something else" ch // 用主动模式处理并打印一个列表 ['a'; 'b'; '1'; ' '; '-'; 'c'] |> List.iter printChar // ----------------------------------- // 用主动模式实现FizzBuzz // ----------------------------------- // 您也可以在主动模式中实现“部分匹配” // 只需在定义中使用下划线,并在匹配成功时返回 Some 即可 let (|MultOf3|_|) i = if i % 3 = 0 then Some MultOf3 else None let (|MultOf5|_|) i = if i % 5 = 0 then Some MultOf5 else None // fizzbuzz的主函数 let fizzBuzz i = match i with | MultOf3 & MultOf5 -> printf "FizzBuzz, " | MultOf3 -> printf "Fizz, " | MultOf5 -> printf "Buzz, " | _ -> printf "%i, " i // 试试看吧! [1..20] |> List.iter fizzBuzz // ================================================ // 简明的 F# // ================================================ module AlgorithmExamples = // F# 代码的“信噪比”很高,阅读代码时很容易就能弄明白算法的意图 // ------ 例子: 计算平方和 ------ let sumOfSquares n = [1..n] // 1) 取从 1 到 n 的所有整数 |> List.map square // 2) 给每个整数求平方 |> List.sum // 3) 将上一步的结果求和 // 试试看吧! sumOfSquares 100 |> printfn "Sum of squares = %A" // ------ 例子: 排序 ------ // 译者注:下列示例实现的是朴素的快速排序算法 let rec sort list = match list with // 若 list 是空表... | [] -> [] // ...则返回空表 // 否则... | firstElem::otherElements -> // 取其第一个元素 let smallerElements = // 从剩余元素中提取比它小的 otherElements |> List.filter (fun e -> e < firstElem) |> sort // 并排序 let largerElements = // 同理,从剩余元素中提取比它大的 otherElements |> List.filter (fun e -> e >= firstElem) |> sort // 并排序 // 最后,将这三部分组合起来,返回一个新的列表 List.concat [smallerElements; [firstElem]; largerElements] // 试试看吧! sort [1; 5; 23; 18; 9; 1; 3] |> printfn "Sorted = %A" // ================================================ // 异步编程 // ================================================ module AsyncExample = // F# 内置了对异步编程的支持 // 从而规避了缩进地狱问题("pyramid of doom") // 下列示例展示了如何使用异步编程,实现同时下载多个网页 open System.Net open System open System.IO open Microsoft.FSharp.Control.CommonExtensions // 异步地访问URL,并获取其内容 let fetchUrlAsync url = async { // "async" 关键词和大括号将创建一个异步对象 ("async" object) let req = WebRequest.Create(Uri(url)) use! resp = req.AsyncGetResponse() // use! 的意思是异步赋值,类似于JavaScript的await use stream = resp.GetResponseStream() // "use" 会让资源在当前作用域结束时自动 close() use reader = new IO.StreamReader(stream) let html = reader.ReadToEnd() printfn "finished downloading %s" url } // 准备以下网页喂给爬虫 let sites = ["http://www.bing.com"; "http://www.google.com"; "http://www.microsoft.com"; "http://www.amazon.com"; "http://www.yahoo.com"] // 来试试看吧! sites |> List.map fetchUrlAsync // 把每个URL都包装成一个异步任务 |> Async.Parallel // 令所有任务并发地运行 |> Async.RunSynchronously // 开始运行 // ================================================ // .NET 兼容性 // ================================================ module NetCompatibilityExamples = // C#能干的活,F# 基本上都能做。同时,F# 还无缝集成了 .Net 或 Mono 的库 // 译者注:Mono库(Mono Libraries)的含义不太确定 // ------- 使用已有的库函数 ------- let (i1success, i1) = System.Int32.TryParse("123"); if i1success then printfn "parsed as %i" i1 else printfn "parse failed" // ------- 简简单单实现接口(interface) ------- // 创建一个实现了 IDisposable 的对象 let makeResource name = { new System.IDisposable with member this.Dispose() = printfn "%s disposed" name } let useAndDisposeResources = use r1 = makeResource "first resource" printfn "using first resource" for i in [1..3] do let resourceName = sprintf "\tinner resource %d" i use temp = makeResource resourceName printfn "\tdo something with %s" resourceName use r2 = makeResource "second resource" printfn "using second resource" printfn "done." // ------- 面向对象编程 ------- // F# 对面向对象编程的支持也很成熟,支持类、接口、虚函数等 // 带有泛型的接口 type IEnumerator<'a> = abstract member Current : 'a abstract MoveNext : unit -> bool // 带有虚函数的抽象基类 [] type Shape() = // 只读的成员变量 abstract member Width : int with get abstract member Height : int with get // 非虚函数 member this.BoundingArea = this.Height * this.Width // 虚函数,且带有默认实现 abstract member Print : unit -> unit default this.Print () = printfn "I'm a shape" // 具象子类,继承上述抽象基类,并覆写(override)了其中的函数 type Rectangle(x:int, y:int) = inherit Shape() override this.Width = x override this.Height = y override this.Print () = printfn "I'm a Rectangle" // 试试看吧! let r = Rectangle(2, 3) printfn "The width is %i" r.Width printfn "The area is %i" r.BoundingArea r.Print() // ------- 扩展已有类的方法 ------- // 与 C# 一样,F# 也可以使用扩展语法来扩展已有类的方法 type System.String with member this.StartsWithA = this.StartsWith "A" // 试试看吧! let s = "Alice" printfn "'%s' starts with an 'A' = %A" s s.StartsWithA // ------- 事件 ------- type MyButton() = let clickEvent = new Event<_>() [] member this.OnClick = clickEvent.Publish member this.TestEvent(arg) = clickEvent.Trigger(this, arg) // 测试效果 let myButton = new MyButton() myButton.OnClick.Add(fun (sender, arg) -> printfn "Click event with arg=%O" arg) myButton.TestEvent("Hello World!")