F# 快速指南



F# - 概述

F# 是一种函数式编程语言。要理解 F# 的结构,您需要阅读几行关于名为函数式编程的编程范式的介绍。

函数式编程将计算机程序视为数学函数。在函数式编程中,重点是常量和函数,而不是变量和状态。因为函数和常量是不变的。

在函数式编程中,您将编写模块化程序,即程序将由将其他函数作为输入的函数组成。

用函数式编程语言编写的程序往往简洁。

关于 F#

以下是关于 F# 的基本信息:

  • 它于 2005 年在微软研究院开发。
  • 它是微软 .Net 语言家族的一部分。
  • 它是一种函数式编程语言。
  • 它基于函数式编程语言 OCaml。

F# 的特性

  • 它是 OCaml 的 .Net 实现。

  • 它编译 .Net CLI(公共语言接口)字节码或 MSIL(Microsoft 中间语言),可在 CLR(公共语言运行时)上运行。

  • 它提供类型推断。

  • 它提供丰富的模式匹配结构。

  • 它具有交互式脚本和调试功能。

  • 它允许编写高阶函数。

  • 它提供完善的对象模型。

F# 的用途

F# 通常用于以下领域:

  • 创建科学模型
  • 数学问题求解
  • 人工智能研究工作
  • 金融建模
  • 图形设计
  • CPU 设计
  • 编译器编程
  • 电信

它也用于 CRUD 应用程序、网页、GUI 游戏和其他通用程序。

F# - 环境设置

本章将讨论 F# 编程所需的工具。

F# 的集成开发环境 (IDE)

Microsoft 提供 Visual Studio 2013 用于 F# 编程。

免费的 Visual Studio 2013 Community 版本可从 Microsoft 的官方网站获得。Visual Studio 2013 Community 及更高版本附带 Visual F# 工具。安装详情请访问 Asp.net 教程。Visual F# 工具包括命令行编译器 (fsc.exe) 和 F# 交互式 (fsi.exe)。

Visual Studio Installer

使用这些工具,您可以编写各种 F# 程序,从简单的命令行应用程序到更复杂的应用程序。您还可以使用基本的文本编辑器(如记事本)编写 F# 源代码文件,并使用命令行编译器将代码编译为程序集。

您可以从 Microsoft Visual Studio 下载它。它会自动安装到您的机器上。

在 Linux 上编写 F# 程序

请访问 F# 官方网站,获取有关将工具作为 Debian 包获取或直接从源代码编译的最新说明:https://fsharp.org/use/linux/.

F# - 程序结构

F# 是一种函数式编程语言。

在 F# 中,函数像数据类型一样工作。您可以像任何其他变量一样声明和使用函数。

一般来说,F# 应用程序没有任何特定的入口点。编译器从上到下执行文件中的所有顶级语句。

但是,为了遵循过程式编程风格,许多应用程序保留单个顶级语句来调用主循环。

以下代码显示了一个简单的 F# 程序:

open System
(* This is a multi-line comment *)
// This is a single-line comment

let sign num =
   if num > 0 then "positive"
   elif num < 0 then "negative"
   else "zero"

let main() =
   Console.WriteLine("sign 5: {0}", (sign 5))

main()

编译并执行程序后,将产生以下输出:

sign 5: positive

请注意:

  • F# 代码文件可能以多个open语句开头,用于导入命名空间。

  • 文件的主体包含实现应用程序业务逻辑的其他函数。

  • 主循环包含顶级可执行语句。

F# - 基本语法

您已经看到了 F# 程序的基本结构,因此很容易理解 F# 编程语言的其他基本构建块。

F# 中的标记

F# 程序由各种标记组成。标记可以是关键字、标识符、常量、字符串文字或符号。我们可以将 F# 标记分为两种类型:

  • 关键字
  • 符号和运算符

F# 关键字

下表显示了关键字及其简要说明。我们将在后续章节中讨论这些关键字的使用。

关键字 说明
abstract 指示一种方法,该方法在其声明的类型中没有实现,或者该方法是虚拟的并具有默认实现。
and 用于相互递归绑定、属性声明以及泛型参数上的多个约束。
as 用于为当前类对象指定对象名称。也用于为模式匹配中的整个模式命名。
assert 用于在调试期间验证代码。
base 用作基类对象的名称。
begin 在详细语法中,指示代码块的开始。
class 在详细语法中,指示类的定义的开始。
default 指示抽象方法的实现;与抽象方法声明一起使用以创建虚拟方法。
delegate 用于声明委托。
do 用于循环结构或执行命令式代码。
done 在详细语法中,指示循环表达式中代码块的结束。
downcast 用于转换为继承链中较低的类型。
downto for表达式中,用于反向计数。
elif 用于条件分支。else if 的简写形式。
else 用于条件分支。
end

在类型定义和类型扩展中,指示成员定义部分的结束。

在详细语法中,用于指定以 begin 关键字开始的代码块的结束。

exception 用于声明异常类型。
extern 指示已声明的程序元素是在另一个二进制文件或程序集中定义的。
false 用作布尔文字。
finally 与 try 一起使用以引入无论是否发生异常都执行的代码块。
for 用于循环结构。
fun 用于 lambda 表达式,也称为匿名函数。
function 用作 fun 关键字和 lambda 表达式中对单个参数进行模式匹配的 match 表达式的较短替代方案。
global 用于引用顶级 .NET 命名空间。
if 用于条件分支结构。
in 用于序列表达式,并在详细语法中用于将表达式与绑定分开。
inherit 用于指定基类或基接口。
inline 用于指示应直接集成到调用者代码中的函数。
interface 用于声明和实现接口。
internal 用于指定成员在程序集内可见,但在程序集外不可见。
lazy 用于指定仅在需要结果时才执行的计算。
let 用于将名称与值或函数关联或绑定。
let! 用于异步工作流中将名称绑定到异步计算的结果,或者在其他计算表达式中用于将名称绑定到计算类型的结果。
match 用于通过将值与模式进行比较来分支。
member 用于在对象类型中声明属性或方法。
module 用于将名称与一组相关的类型、值和函数关联,以将其与其他代码逻辑地分开。
mutable 用于声明变量,即可以更改的值。
namespace 用于将名称与一组相关的类型和模块关联,以将其与其他代码逻辑地分开。
new

用于声明、定义或调用创建或可以创建对象的构造函数。

还在泛型参数约束中使用,以指示类型必须具有特定的构造函数。

not 实际上不是关键字。但是,not struct 组合用作泛型参数约束。
null

指示对象不存在。

也用于泛型参数约束。

of 在判别联合中用于指示值的类别类型,以及在委托和异常声明中。
open 用于使命名空间或模块的内容无需限定即可使用。
or

用作布尔条件的布尔或运算符。等同于 ||。

也用于成员约束。

override 用于实现与基版本不同的抽象或虚方法版本。
private 将成员的访问权限限制在同一类型或模块中的代码。
public 允许从类型外部访问成员。
rec 用于指示函数是递归的。
return 用于指示作为计算表达式结果提供的值。
return! 用于指示计算表达式,在评估时,提供包含计算表达式的结果。
select 在查询表达式中用于指定要提取的字段或列。请注意,这是一个上下文关键字,这意味着它实际上不是保留字,它只在适当的上下文中充当关键字。
static 用于指示无需类型实例即可调用的方法或属性,或在类型的所有实例之间共享的值成员。
struct

用于声明结构类型。

也用于泛型参数约束。

在模块定义中用于OCaml兼容性。

then

用于条件表达式。

也用于在对象构造后执行副作用。

to 在for循环中用于指示范围。
true 用作布尔文字。
try 用于引入可能生成异常的代码块。与with或finally一起使用。
type 用于声明类、记录、结构、区分联合、枚举类型、度量单位或类型缩写。
upcast 用于转换为继承链中较高的类型。
use 用于代替let,用于需要调用Dispose来释放资源的值。
use! 在异步工作流和其他计算表达式中,用于代替let!,用于需要调用Dispose来释放资源的值。
val 在签名中用于指示值,或在类型中用于声明成员(在有限情况下)。
void 指示.NET void类型。在与其他.NET语言互操作时使用。
when 用于模式匹配上的布尔条件(when guards)以及为泛型类型参数引入约束子句。
while 引入循环构造。
with 与模式匹配表达式中的match关键字一起使用。也用于对象表达式、记录复制表达式和类型扩展中,以引入成员定义和引入异常处理程序。
yield 在序列表达式中用于为序列生成值。
yield! 在计算表达式中用于将给定计算表达式的结果附加到包含计算表达式的结果集合中。

一些保留关键字来自OCaml语言:

asr land lor lsl lsr lxor mod sig

其他一些保留关键字保留用于F#的未来扩展。

atomic break checked component const constraint constructor
continue eager event external fixed functor include
method mixin object parallel process protected pure
sealed tailcall trait virtual volatile

F#中的注释

F#提供两种类型的注释:

  • 单行注释以//符号开头。
  • 多行注释以(*开头,以*)结尾。

F#中的基本程序和应用程序入口点

通常,F#程序没有任何显式的入口点。编译F#应用程序时,提供给编译器的最后一个文件成为入口点,该文件中所有顶级语句都从上到下执行。

一个编写良好的程序应该只有一个顶级语句来调用程序的主循环。

一个非常简小的F#程序,它将在屏幕上显示“Hello World”:

(* This is a comment *)
(* Sample Hello World program using F# *)
printfn "Hello World!"

编译并执行程序后,将产生以下输出:

Hello World!

F# - 数据类型

F#中的数据类型可以分类如下:

  • 整数类型
  • 浮点类型
  • 文本类型
  • 其他类型

整数数据类型

下表提供了F#的整数数据类型。这些基本上是整数数据类型。

F#类型 大小 范围 示例 备注
sbyte 1字节 -128到127

42y

-11y

8位有符号整数
byte 1字节 0到255

42uy

200uy

8位无符号整数
int16 2字节 -32768到32767

42s

-11s

16位有符号整数
uint16 2字节 0到65,535

42us

200us

16位无符号整数
int/int32 4字节 -2,147,483,648到2,147,483,647

42

-11

32位有符号整数
uint32 4字节 0到4,294,967,295

42u

200u

32位无符号整数
int64 8字节 -9,223,372,036,854,775,808到9,223,372,036,854,775,807

42L

-11L

64位有符号整数
uint64 8字节 0到18,446,744,073,709,551,615

42UL

200UL

64位无符号整数
bigint 至少4字节 任何整数

42I

1499999

9999999

9999999

9999999

9999I

任意精度整数

示例

(* single byte integer *)
let x = 268.97f
let y = 312.58f
let z = x + y

printfn "x: %f" x
printfn "y: %f" y
printfn "z: %f" z

(* unsigned 8-bit natural number *)

let p = 2uy
let q = 4uy
let r = p + q

printfn "p: %i" p
printfn "q: %i" q
printfn "r: %i" r

(* signed 16-bit integer *)

let a = 12s
let b = 24s
let c = a + b

printfn "a: %i" a
printfn "b: %i" b
printfn "c: %i" c

(* signed 32-bit integer *)

let d = 212l
let e = 504l
let f = d + e

printfn "d: %i" d
printfn "e: %i" e
printfn "f: %i" f

编译并执行程序后,将产生以下输出:

x: 1
y: 2
z: 3
p: 2
q: 4
r: 6
a: 12
b: 24
c: 36
d: 212
e: 504
f: 716

浮点数据类型

下表提供了F#的浮点数据类型。

F#类型 大小 范围 示例 备注
float32 4字节 ±1.5e-45到±3.4e38

42.0F

-11.0F

32位有符号浮点数(7位有效数字)
float 8字节 ±5.0e-324到±1.7e308

42.0

-11.0

64位有符号浮点数(15-16位有效数字)
decimal 16字节 ±1.0e-28到±7.9e28

42.0M

-11.0M

128位有符号浮点数(28-29位有效数字)
BigRational 至少4字节 任何有理数。

42N

-11N

任意精度有理数。使用此类型需要引用FSharp.PowerPack.dll。

示例

(* 32-bit signed floating point number *)
(* 7 significant digits *)

let d = 212.098f
let e = 504.768f
let f = d + e

printfn "d: %f" d
printfn "e: %f" e
printfn "f: %f" f

(* 64-bit signed floating point number *)
(* 15-16 significant digits *)
let x = 21290.098
let y = 50446.768
let z = x + y

printfn "x: %g" x
printfn "y: %g" y
printfn "z: %g" z

编译并执行程序后,将产生以下输出:

d: 212.098000
e: 504.768000
f: 716.866000
x: 21290.1
y: 50446.8
z: 71736.9

文本数据类型

下表提供了F#的文本数据类型。

F#类型 大小 范围 示例 备注
char 2字节 U+0000到U+ffff

'x'

'\t'

单个Unicode字符
string 20 + (2 * 字符串长度)字节 0到大约20亿个字符

"Hello"

"World"

Unicode文本

示例

let choice = 'y'
let name = "Zara Ali"
let org = "Tutorials Point"

printfn "Choice: %c" choice
printfn "Name: %s" name
printfn "Organisation: %s" org

编译并执行程序后,将产生以下输出:

Choice: y
Name: Zara Ali
Organisation: Tutorials Point

其他数据类型

下表提供了F#的其他一些数据类型。

F#类型 大小 范围 示例 备注
bool 1字节 只有两个可能的值,true或false

true

false

存储布尔值

示例

let trueVal = true
let falseVal = false

printfn "True Value: %b" (trueVal)
printfn "False Value: %b" (falseVal)

编译并执行程序后,将产生以下输出:

True Value: true
False Value: false

F# - 变量

变量是赋予存储区域的名称,我们的程序可以操作它。每个变量都有一个特定的类型,它决定了变量内存的大小和布局;可以存储在该内存中的值的范围;以及可以应用于变量的操作集。

F#中的变量声明

let关键字用于变量声明:

例如:

let x = 10

它声明一个变量x并将其值赋值为10。

您还可以将表达式赋值给变量:

let x = 10
let y = 20
let z = x + y

以下示例说明了这个概念:

示例

let x = 10
let y = 20
let z = x + y

printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

编译并执行程序后,将产生以下输出:

x: 10
y: 20
z: 30

F#中的变量是不可变的,这意味着一旦变量绑定到一个值,就不能更改它。它们实际上被编译为静态只读属性。

以下示例演示了这一点。

示例

let x = 10
let y = 20
let z = x + y

printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

let x = 15
let y = 20
let z = x + y

printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

编译并执行程序时,它会显示以下错误消息:

Duplicate definition of value 'x'
Duplicate definition of value 'Y'
Duplicate definition of value 'Z'

带有类型声明的变量定义

变量定义告诉编译器应该在哪里以及为变量创建多少存储空间。变量定义可以指定数据类型,并包含该类型的一个或多个变量的列表,如下例所示。

示例

let x:int32 = 10
let y:int32 = 20
let z:int32 = x + y

printfn "x: %d" x
printfn "y: %d" y
printfn "z: %d" z

let p:float = 15.99
let q:float = 20.78
let r:float = p + q

printfn "p: %g" p
printfn "q: %g" q
printfn "r: %g" r

编译并执行程序时,它会显示以下错误消息:

x: 10
y: 20
z: 30
p: 15.99
q: 20.78
r: 36.77

可变变量

有时您需要更改存储在变量中的值。为了指定在程序的后面部分可能更改已声明和赋值的变量的值,F#提供了mutable关键字。您可以使用此关键字声明和赋值可变变量,其值将发生更改。

mutable关键字允许您声明和赋值可变变量的值。

您可以使用let关键字为可变变量赋值一些初始值。但是,要为其赋值新的后续值,您需要使用运算符。

例如:

let mutable x = 10
x ← 15

以下示例将阐明这个概念:

示例

let mutable x = 10
let y = 20
let mutable z = x + y

printfn "Original Values:"
printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

printfn "Let us change the value of x"
printfn "Value of z will change too."

x <- 15
z <- x + y

printfn "New Values:"
printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

编译并执行程序后,将产生以下输出:

Original Values:
x: 10
y: 20
z: 30
Let us change the value of x
Value of z will change too.
New Values:
x: 15
y: 20
z: 35

F# - 运算符

运算符是一个符号,它告诉编译器执行特定的数学或逻辑操作。F#富含内置运算符,并提供以下类型的运算符:

  • 算术运算符
  • 比较运算符
  • 布尔运算符
  • 按位运算符

算术运算符

下表显示了F#语言支持的所有算术运算符。假设变量A持有10,变量B持有20,则:

显示示例

运算符 说明 示例
+ 将两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 将两个操作数相乘 A * B 将得到 200
/ 将分子除以分母 B / A 将得到 2
% 模运算符和整数除法后的余数 B % A 将得到 0
** 幂运算符,将一个操作数提升到另一个操作数的幂 B**A 将得到 2010

比较运算符

下表显示了F#语言支持的所有比较运算符。这些二元比较运算符可用于整数和浮点类型。这些运算符返回bool类型的返回值。

假设变量A持有10,变量B持有20,则:

显示示例

运算符 说明 示例
= 检查两个操作数的值是否相等,如果相等,则条件为真。 (A == B) 为假。
<> 检查两个操作数的值是否相等,如果不相等,则条件为真。 (A <> B) 为真。
> 检查左操作数的值是否大于右操作数的值,如果是,则条件为真。 (A > B) 为假。
< 检查左操作数的值是否小于右操作数的值,如果是,则条件为真。 (A < B) 为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是,则条件为真。 (A >= B) 为假。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件为真。 (A <= B) 为真。

布尔运算符

下表显示了F#语言支持的所有布尔运算符。假设变量A持有true,变量B持有false,则:

显示示例

运算符 说明 示例
&& 称为布尔AND运算符。如果两个操作数都非零,则条件为真。 (A && B) 为假。
|| 称为布尔OR运算符。如果两个操作数中的任何一个非零,则条件为真。 (A || B) 为真。
not 称为布尔NOT运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑NOT运算符将变为假。 not (A && B) 为真。

按位运算符

按位运算符对位进行操作,并执行逐位运算。&&&(按位AND)、|||(按位OR)和^^^(按位异或)的真值表如下:

显示示例

p q p &&& q p ||| q p ^^^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

假设 A = 60;B = 13;则它们的二进制形式如下:

A = 0011 1100

B = 0000 1101

-----------------

A&&&B = 0000 1100

A|||B = 0011 1101

A^^^B = 0011 0001

~~~A = 1100 0011

F# 语言支持的位运算符如下表所示。假设变量 A 为 60,变量 B 为 13,则:

运算符 说明 示例
&&& 二进制与运算符:如果该位同时存在于两个操作数中,则将其复制到结果中。 (A &&& B) 将得到 12,即 0000 1100
||| 二进制或运算符:如果该位存在于任一操作数中,则将其复制到结果中。 (A ||| B) 将得到 61,即 0011 1101
^^^ 二进制异或运算符:如果该位仅在一个操作数中设置,则将其复制到结果中。 (A ^^^ B) 将得到 49,即 0011 0001
~~~ 二进制补码运算符:是一元运算符,作用是“反转”位。 (~~~A) 将得到 -61,在二进制补码形式下为 1100 0011。
<<< 二进制左移运算符:左操作数的值向左移动由右操作数指定的位数。 A <<< 2 将得到 240,即 1111 0000
>>> 二进制右移运算符:左操作数的值向右移动由右操作数指定的位数。 A >>> 2 将得到 15,即 0000 1111

运算符优先级

下表显示了 F# 语言中运算符和其他表达式关键字的优先级顺序,从最低优先级到最高优先级。

显示示例

运算符 结合性
as 右结合
when 右结合
| (管道) 左结合
; 右结合
let 非结合
function, fun, match, try 非结合
if 非结合
右结合
:= 右结合
, 非结合
or, || 左结合
&, && 左结合
< op, >op, =, |op, &op 左结合
&&& , |||, ^^^, ~~~, <<<, >>> 左结合
^ op 右结合
:: 右结合
:?>, :? 非结合
- op, +op, (二元) 左结合
* op, /op, %op 左结合
** op 右结合
f x (函数应用) 左结合
| (模式匹配) 右结合
前缀运算符 (+op, -op, %, %%, &, &&, !op, ~op) 左结合
. 左结合
f(x) 左结合
f<types> 左结合

F# - 决策

决策结构要求程序员指定一个或多个条件,由程序进行评估或测试。它应该与一个或多个语句一起使用,如果条件被确定为真,则执行这些语句;如果条件被确定为假,则可以选择执行其他语句。

以下是大多数编程语言中常见的决策结构的一般形式:

Decision Making

F# 编程语言提供以下类型的决策语句。

语句 说明
if /then 语句 一个if/then 语句由一个布尔表达式和一个或多个语句组成。
if/then/else 语句 一个if/then 语句可以后跟一个可选的else 语句,当布尔表达式为假时执行。
if/then/elif/else 语句 一个if/then/elif/else 语句允许您拥有多个 else 分支。
嵌套 if 语句 您可以在另一个ifelse if 语句内使用一个ifelse if 语句。

F# - 循环

编程语言提供各种控制结构,允许更复杂的执行路径。

循环语句允许我们多次执行一个语句或一组语句,以下是大多数编程语言中循环语句的一般形式:

Loop Statement

F# 提供以下类型的循环来处理循环需求。

循环类型 说明
for… to 和 for… downto 表达式 for...to 表达式用于在循环变量的一系列值上迭代循环。for… downto 表达式减少循环变量的值。
for … in 表达式 这种形式的 for 循环用于迭代项目集合,即遍历集合和序列。
While…do 循环 当给定条件为真时重复一个语句或一组语句。它在执行循环体之前测试条件。
嵌套循环 您可以在任何其他 for 或 while 循环内使用一个或多个循环。

F# - 函数

在 F# 中,函数像数据类型一样工作。您可以像任何其他变量一样声明和使用函数。

由于函数可以像其他变量一样使用,您可以:

  • 创建一个函数,并为其命名并将其名称与类型关联。
  • 为其赋值。
  • 对其值执行一些计算。
  • 将其作为参数传递给另一个函数或子例程。
  • 返回一个函数作为另一个函数的结果。

定义函数

函数使用let关键字定义。函数定义具有以下语法:

let [inline] function-name parameter-list [ : return-type ]
= function-body

其中:

  • 函数名是表示函数的标识符。

  • 参数列表给出用空格分隔的参数列表。您还可以为每个参数指定显式类型,如果未指定,编译器倾向于从函数体中推断出来(像变量一样)。

  • 函数体由一个表达式或由多个表达式组成的复合表达式组成。函数体中的最后一个表达式是返回值。

  • 返回类型是一个冒号后跟一个类型,是可选的。如果未指定返回类型,则编译器从函数体中的最后一个表达式确定它。

函数的参数

您在函数名之后列出参数的名称。您可以指定参数的类型。参数的类型应该跟在参数名称后面,用冒号分隔。

如果没有指定参数类型,则由编译器推断。

例如:

let doubleIt (x : int) = 2 * x

调用函数

通过指定函数名,然后是一个空格,然后是任何用空格分隔的参数来调用函数。

例如:

let vol = cylinderVolume 3.0 5.0

以下程序说明了这些概念。

示例 1

以下程序计算当半径和长度作为参数给出时圆柱体的体积

// the function calculates the volume of
// a cylinder with radius and length as parameters

let cylinderVolume radius length : float =

   // function body
   let pi = 3.14159
   length * pi * radius * radius

let vol = cylinderVolume 3.0 5.0
printfn " Volume: %g " vol

编译并执行程序后,将产生以下输出:

Volume: 141.372

示例 2

以下程序返回两个给定参数中较大的值:

// the function returns the larger value between two
// arguments

let max num1 num2 : int32 =
   // function body
   if(num1>num2)then
      num1
   else
      num2

let res = max 39 52
printfn " Max Value: %d " res

编译并执行程序后,将产生以下输出:

Max Value: 52

示例 3

let doubleIt (x : int) = 2 * x
printfn "Double 19: %d" ( doubleIt(19))

编译并执行程序后,将产生以下输出:

Double 19: 38

递归函数

递归函数是调用自身的函数。

使用let rec关键字组合定义递归。

定义递归函数的语法为:

//Recursive function definition
let rec function-name parameter-list = recursive-function-body

例如:

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

示例 1

以下程序返回斐波那契数列 1 到 10:

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)
for i = 1 to 10 do
   printfn "Fibonacci %d: %d" i (fib i)

编译并执行程序后,将产生以下输出:

Fibonacci 1: 1
Fibonacci 2: 2
Fibonacci 3: 3
Fibonacci 4: 5
Fibonacci 5: 8
Fibonacci 6: 13
Fibonacci 7: 21
Fibonacci 8: 34
Fibonacci 9: 55
Fibonacci 10: 89

示例 2

以下程序返回 8 的阶乘:

open System
let rec fact x =
   if x < 1 then 1
   else x * fact (x - 1)
Console.WriteLine(fact 8)

编译并执行程序后,将产生以下输出:

40320

F# 中的箭头表示法

F# 使用链式箭头表示法报告函数和值中的数据类型。让我们以一个函数为例,该函数接受一个int输入,并返回一个字符串。在箭头表示法中,它写为:

int -> string

数据类型从左到右读取。

让我们再举一个假设函数的例子,该函数接受两个 int 数据输入并返回一个字符串。

let mydivfunction x y = (x / y).ToString();;

F# 使用链式箭头表示法报告数据类型为:

val mydivfunction : x:int -> y:int -> string

返回类型由链式箭头表示法中最右边的数据类型表示。

更多示例:

表示法 含义
float → float → float 该函数接受两个float输入,返回另一个float
int → string → float 该函数接受一个int和一个string输入,返回一个float

Lambda 表达式

Lambda 表达式是匿名函数。

让我们举两个函数的例子:

let applyFunction ( f: int -> int -> int) x y = f x y
let mul x y = x * y
let res = applyFunction mul 5 7
printfn "%d" res

编译并执行程序后,将产生以下输出:

35

现在在上面的例子中,如果不用定义函数mul,我们可以使用 lambda 表达式:

let applyFunction ( f: int -> int -> int) x y = f x y
let res = applyFunction (fun x y -> x * y ) 5 7
printfn "%d" res

编译并执行程序后,将产生以下输出:

35

函数组合和管道

在 F# 中,一个函数可以由其他函数组合而成。

以下示例显示了从两个函数 function1 和 function2 组合名为 f 的函数:

let function1 x = x + 1
let function2 x = x * 5

let f = function1 >> function2
let res = f 10
printfn "%d" res

编译并执行程序后,将产生以下输出:

55

F# 还提供了一个称为函数管道的功能。管道允许将函数调用链接在一起作为连续的操作。

以下示例显示了:

let function1 x = x + 1
let function2 x = x * 5

let res = 10 |> function1 |> function2
printfn "%d" res

编译并执行程序后,将产生以下输出:

55

F# - 字符串

在 F# 中,字符串类型表示不可变文本,作为 Unicode 字符的序列。

字符串字面量

字符串字面量由引号(")字符分隔。

有一些特殊字符用于特殊用途,例如换行符、制表符等。它们使用反斜杠(\)字符编码。反斜杠字符和相关的字符构成转义序列。下表显示了 F# 支持的转义序列。

字符 转义序列
退格 \b
换行 \n
回车 \r
制表符 \t
反斜杠 \\
引号 \"
撇号 \'
Unicode 字符 \uXXXX 或 \UXXXXXXXX (其中 X 表示十六进制数字)

忽略转义序列的方法

以下两种方法使编译器忽略转义序列:

  • 使用 @ 符号。
  • 用三个引号括起来。

当字符串字面量前面带有 @ 符号时,它被称为逐字字符串。这样,字符串中的所有转义序列都被忽略,除了两个引号字符被解释为一个引号字符。

当字符串用三个引号括起来时,所有转义序列也被忽略,包括双引号字符。

示例

以下示例演示了这种技术,展示了如何处理包含嵌入式引号的 XML 或其他结构:

// Using a verbatim string
let xmldata = @"<book author=""Lewis, C.S"" title=""Narnia"">"
printfn "%s" xmldata

编译并执行程序后,将产生以下输出:

<book author="Lewis, C.S" title="Narnia">

字符串的基本运算符

下表显示了字符串的基本运算:

说明
collect : (char → string) → string → string 创建一个新字符串,其字符是将指定函数应用于输入字符串的每个字符并将结果字符串连接起来的结果。
concat : string → seq<string> → string 返回一个新字符串,该字符串通过使用分隔符连接给定的字符串。
exists : (char → bool) → string → bool 测试字符串的任何字符是否满足给定的谓词。
forall : (char → bool) → string → bool 测试字符串中所有字符是否都满足给定的谓词。
init : int → (int → string) → string 创建一个新的字符串,其字符是由将指定函数应用于每个索引并连接结果字符串生成的。
iter : (char → unit) → string → unit 将指定函数应用于字符串中的每个字符。
iteri : (int → char → unit) → string → unit 将指定函数应用于字符串中每个字符的索引和字符本身。
length : string → int 返回字符串的长度。
map : (char → char) → string → string 创建一个新的字符串,其字符是由将指定函数应用于输入字符串的每个字符生成的。
mapi : (int → char → char) → string → string 创建一个新的字符串,其字符是由将指定函数应用于输入字符串的每个字符及其索引生成的。
replicate : int → string → string 通过连接指定数量的字符串实例来返回一个字符串。

以下示例演示了上述某些功能的用法:

示例 1

String.collect 函数构建一个新的字符串,其字符是由将指定函数应用于输入字符串的每个字符并连接结果字符串生成的。

let collectTesting inputS =
   String.collect (fun c -> sprintf "%c " c) inputS
printfn "%s" (collectTesting "Happy New Year!")

编译并执行程序后,将产生以下输出:

H a p p y N e w Y e a r !

示例 2

String.concat 函数使用分隔符连接给定的字符串序列并返回一个新字符串。

let strings = [ "Tutorials Point"; "Coding Ground"; "Absolute Classes" ]
let ourProducts = String.concat "\n" strings
printfn "%s" ourProducts

编译并执行程序后,将产生以下输出:

Tutorials Point
Coding Ground
Absolute Classes

示例 3

String.replicate 方法通过连接指定数量的字符串实例来返回一个字符串。

printfn "%s" <| String.replicate 10 "*! "

编译并执行程序后,将产生以下输出:

*! *! *! *! *! *! *! *! *! *!

F# - 可选项

F# 中的option 类型用于在计算中可能存在或不存在变量或函数值的场景。Option 类型用于表示计算中的可选值。它们可以有两个可能的值:Some(x)None

例如,执行除法的函数在正常情况下会返回值,但在分母为零的情况下会抛出异常。在此使用 option 可以帮助指示函数是否成功。

Option 具有底层类型,可以保存该类型的 value,或者可能没有 value。

使用 Options

让我们以除法函数为例。下面的程序解释了这一点:

让我们编写一个 div 函数,并向其发送两个参数 20 和 5:

let div x y = x / y
let res = div 20 5
printfn "Result: %d" res

编译并执行程序后,将产生以下输出:

Result: 4

如果第二个参数为零,则程序会抛出异常:

let div x y = x / y
let res = div 20 0
printfn "Result: %d" res

编译并执行程序后,将产生以下输出:

Unhandled Exception:
System.DivideByZeroException: Division by zero

在这种情况下,我们可以使用 option 类型,在操作成功时返回 Some(value),在操作失败时返回 None。

以下示例演示了 option 的用法:

示例

let div x y =
   match y with
   | 0 -> None
   | _ -> Some(x/y)

let res : int option = div 20 4
printfn "Result: %A " res

编译并执行程序后,将产生以下输出:

Result: Some 5

Option 属性和方法

option 类型支持以下属性和方法:

属性或方法 类型 说明
None 'T option 一个静态属性,使您可以创建一个具有None 值的 option 值。
IsNone bool 如果 option 具有None 值,则返回true
IsSome bool 如果 option 具有非None 值,则返回true
Some 'T option 一个静态成员,创建一个具有非None 值的 option。
'T 返回底层值,如果值为None,则抛出 NullReferenceException。

示例 1

let checkPositive (a : int) =
   if a > 0 then
      Some(a)
   else
      None

let res : int option = checkPositive(-31)
printfn "Result: %A " res

编译并执行程序后,将产生以下输出:

Result: <null>

示例 2

let div x y =
   match y with
   | 0 -> None
   | _ -> Some(x/y)

let res : int option = div 20 4
printfn "Result: %A " res
printfn "Result: %A " res.Value

编译并执行程序后,将产生以下输出:

Result: Some 5
Result: 5

示例 3

let isHundred = function
   | Some(100) -> true
   | Some(_) | None -> false

printfn "%A" (isHundred (Some(45)))
printfn "%A" (isHundred (Some(100)))
printfn "%A" (isHundred None)

编译并执行程序后,将产生以下输出:

false
true
false

F# - 元组

元组是由逗号分隔的值的集合。这些用于创建临时数据结构,将相关值组合在一起。

例如,(“Zara Ali”,“Hyderabad”,10)是一个包含两个字符串值和一个 int 值的 3 元组,其类型为 (string * string * int)。

元组可以是相同类型或不同类型的对、三元组等等。

这里提供一些示例:

// Tuple of two integers.
( 4, 5 )

// Triple of strings.
( "one", "two", "three" )

// Tuple of unknown types.
( a, b )

// Tuple that has mixed types.
( "Absolute Classes", 1, 2.0 )

// Tuple of integer expressions.
( a * 4, b + 7)

示例

此程序有一个函数,该函数采用四个浮点值的元组并返回平均值:

let averageFour (a, b, c, d) =
   let sum = a + b + c + d
   sum / 4.0

let avg:float = averageFour (4.0, 5.1, 8.0, 12.0)
printfn "Avg of four numbers: %f" avg

编译并执行程序后,将产生以下输出:

Avg of four numbers: 7.275000

访问单个元组成员

可以使用模式匹配来评估和打印元组的各个成员。

以下示例说明了这个概念:

示例

let display tuple1 =
   match tuple1 with
   | (a, b, c) -> printfn "Detail Info: %A %A %A" a b c

display ("Zara Ali", "Hyderabad", 10 )

编译并执行程序后,将产生以下输出:

Detail Info: "Zara Ali" "Hyderabad" 10

F# 有两个内置函数fstsnd,它们返回 2 元组中的第一项和第二项。

以下示例说明了这个概念:

示例

printfn "First member: %A" (fst(23, 30))
printfn "Second member: %A" (snd(23, 30))

printfn "First member: %A" (fst("Hello", "World!"))
printfn "Second member: %A" (snd("Hello", "World!"))

let nameTuple = ("Zara", "Ali")

printfn "First Name: %A" (fst nameTuple)
printfn "Second Name: %A" (snd nameTuple)

编译并执行程序后,将产生以下输出:

First member: 23
Second member: 30
First member: "Hello"
Second member: "World!"
First Name: "Zara"
Second Name: "Ali"

F# - 记录

记录类似于元组,但它包含命名字段。例如,

type website =
   { title : string;
      url : string }

定义记录

使用type 关键字定义记录类型,并使用分号分隔的列表定义记录的字段。

定义记录的语法为:

type recordName =
   { [ fieldName : dataType ] + }

创建记录

您可以通过指定记录的字段来创建记录。例如,让我们创建一个名为homepagewebsite 记录:

let homepage = { Title = "TutorialsPoint"; Url = "www.tutorialspoint.com" }

以下示例将解释这些概念:

示例 1

此程序定义了一个名为 website 的记录类型。然后它创建一些 website 类型的记录并打印这些记录。

(* defining a record type named website *)
type website =
   { Title : string;
      Url : string }

(* creating some records *)
let homepage = { Title = "TutorialsPoint"; Url = "www.tutorialspoint.com" }
let cpage = { Title = "Learn C"; Url = "www.tutorialspoint.com/cprogramming/index.htm" }
let fsharppage = { Title = "Learn F#"; Url = "www.tutorialspoint.com/fsharp/index.htm" }
let csharppage = { Title = "Learn C#"; Url = "www.tutorialspoint.com/csharp/index.htm" }

(*printing records *)
(printfn "Home Page: Title: %A \n \t URL: %A") homepage.Title homepage.Url
(printfn "C Page: Title: %A \n \t URL: %A") cpage.Title cpage.Url
(printfn "F# Page: Title: %A \n \t URL: %A") fsharppage.Title fsharppage.Url
(printfn "C# Page: Title: %A \n \t URL: %A") csharppage.Title csharppage.Url

编译并执行程序后,将产生以下输出:

Home Page: Title: "TutorialsPoint"
       URL: "www.tutorialspoint.com"
C Page: Title: "Learn C"
      URL: "www.tutorialspoint.com/cprogramming/index.htm"
F# Page: Title: "Learn F#"
      URL: "www.tutorialspoint.com/fsharp/index.htm"
C# Page: Title: "Learn C#"
      URL: "www.tutorialspoint.com/csharp/index.htm"

示例 2

type student =
   { Name : string;
      ID : int;
      RegistrationText : string;
      IsRegistered : bool }

let getStudent name id =
   { Name = name; ID = id; RegistrationText = null; IsRegistered = false }

let registerStudent st =
   { st with
      RegistrationText = "Registered";
      IsRegistered = true }

let printStudent msg st =
   printfn "%s: %A" msg st

let main() =
   let preRegisteredStudent = getStudent "Zara" 10
   let postRegisteredStudent = registerStudent preRegisteredStudent

   printStudent "Before Registration: " preRegisteredStudent
   printStudent "After Registration: " postRegisteredStudent

main()

编译并执行程序后,将产生以下输出:

Before Registration: : {Name = "Zara";
   ID = 10;
   RegistrationText = null;
   IsRegistered = false;}
After Registration: : {Name = "Zara";
   ID = 10;
   RegistrationText = "Registered";
   IsRegistered = true;}

F# - 列表

在 F# 中,列表是相同类型元素的有序、不可变序列。它在某种程度上等同于链表数据结构。

F# 模块Microsoft.FSharp.Collections.List 包含列表的常用操作。但是,F# 会自动导入此模块并使其可供每个 F# 应用程序使用。

创建和初始化列表

以下是创建列表的各种方法:

  • 使用列表字面量

  • 使用cons (::) 运算符。

  • 使用 List 模块的List.init 方法。

  • 使用一些称为列表推导式语法结构

列表字面量

在这种方法中,您只需在方括号中指定分号分隔的值序列。例如:

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

cons (::) 运算符

使用此方法,您可以通过使用 :: 运算符将一些值添加到现有列表的前面或cons 到现有列表中。例如:

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

[] 表示空列表。

List init 方法

List 模块的 List.init 方法通常用于创建列表。此方法的类型为:

val init : int -> (int -> 'T) -> 'T list

第一个参数是新列表的所需长度,第二个参数是初始化函数,它生成列表中的项。

例如:

let list5 = List.init 5 (fun index -> (index, index * index, index * index * index))

在这里,索引函数生成列表。

列表推导式

列表推导式是用于生成列表的特殊语法结构。

F# 列表推导式语法有两种形式:范围和生成器。

范围具有结构:[start .. end] 和 [start .. step .. end]

例如:

let list3 = [1 .. 10]

生成器具有结构:[for x in collection do ... yield expr]

例如:

let list6 = [ for a in 1 .. 10 do yield (a * a) ]

由于yield 关键字将单个值推入列表,因此yield! 关键字将值集合推入列表。

以下函数演示了上述方法:

示例

(* using list literals *)
let list1 = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
printfn "The list: %A" list1

(*using cons operator *)
let list2 = 1 :: 2 :: 3 :: []
printfn "The list: %A" list2

(* using range constructs*)
let list3 = [1 .. 10]
printfn "The list: %A" list3

(* using range constructs *)
let list4 = ['a' .. 'm']
printfn "The list: %A" list4

(* using init method *)
let list5 = List.init 5 (fun index -> (index, index * index, index * index * index))
printfn "The list: %A" list5

(* using yield operator *)
let list6 = [ for a in 1 .. 10 do yield (a * a) ]
printfn "The list: %A" list6

(* using yield operator *)
let list7 = [ for a in 1 .. 100 do if a % 3 = 0 && a % 5 = 0 then yield a]
printfn "The list: %A" list7

(* using yield! operator *)
let list8 = [for a in 1 .. 3 do yield! [ a .. a + 3 ] ]
printfn "The list: %A" list8

编译并执行程序后,将产生以下输出:

The list: [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
The list: [1; 2; 3]
The list: [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
The list: ['a'; 'b'; 'c'; 'd'; 'e'; 'f'; 'g'; 'h'; 'i'; 'j'; 'k'; 'l'; 'm']
The list: [(0, 0, 0); (1, 1, 1); (2, 4, 8); (3, 9, 27); (4, 16, 64)]
The list: [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
The list: [15; 30; 45; 60; 75; 90]
The list: [1; 2; 3; 4; 2; 3; 4; 5; 3; 4; 5; 6]

列表数据类型的属性

下表显示了列表数据类型的各种属性:

属性 类型 说明
Head 'T 第一个元素。
Empty 'T list 返回适当类型空列表的静态属性。
IsEmpty bool 如果列表没有元素,则为true
Item 'T 指定索引(基于零)处的元素。
Length int 元素数量。
Tail 'T list 没有第一个元素的列表。

以下示例显示了这些属性的用法:

示例

let list1 = [ 2; 4; 6; 8; 10; 12; 14; 16 ]

// Use of Properties
printfn "list1.IsEmpty is %b" (list1.IsEmpty)
printfn "list1.Length is %d" (list1.Length)
printfn "list1.Head is %d" (list1.Head)
printfn "list1.Tail.Head is %d" (list1.Tail.Head)
printfn "list1.Tail.Tail.Head is %d" (list1.Tail.Tail.Head)
printfn "list1.Item(1) is %d" (list1.Item(1))

编译并执行程序后,将产生以下输出:

list1.IsEmpty is false
list1.Length is 8
list1.Head is 2
list1.Tail.Head is 4
list1.Tail.Tail.Head is 6
list1.Item(1) is 4

列表的基本运算符

下表显示了列表数据类型的基本操作:

说明
append : 'T list → 'T list → 'T list 返回一个新列表,其中包含第一个列表的元素,然后是第二个列表的元素。
average : 'T list → ^T 返回列表中元素的平均值。
averageBy : ('T → ^U) → 'T list → ^U 返回通过将函数应用于列表的每个元素生成的元素的平均值。
choose : ('T → 'U option) → 'T list → 'U list 将给定函数应用于列表的每个元素。返回由函数返回Some 的每个元素的结果组成的列表。
collect : ('T → 'U list) → 'T list → 'U list 对于列表的每个元素,应用给定函数。连接所有结果并返回组合列表。
concat : seq<'T list> → 'T list 返回一个新列表,其中按顺序包含每个列表的元素。
empty : 'T list 返回给定类型的空列表。
exists : ('T → bool) → 'T list → bool 测试列表中的任何元素是否满足给定的谓词。
exists2 : ('T1 → 'T2 → bool) → 'T1 list → 'T2 list → bool 测试列表的任何一对对应元素是否满足给定的谓词。
filter : ('T → bool) → 'T list → 'T list 返回一个新集合,其中只包含给定谓词返回true 的集合的元素。
find : ('T → bool) → 'T list → 'T 返回给定函数返回true 的第一个元素。
findIndex : ('T → bool) → 'T list → int 返回列表中满足给定谓词的第一个元素的索引。
fold : ('State → 'T → 'State) → 'State → 'T list → 'State 将函数应用于集合的每个元素,将累加器参数贯穿计算。此函数采用第二个参数,并将函数应用于它和列表的第一个元素。然后,它将此结果与第二个元素一起传递给函数,依此类推。最后,它返回最终结果。如果输入函数是 f,元素是 i0...iN,则此函数计算 f (... (f s i0) i1 ...) iN。
fold2 : ('State → 'T1 → 'T2 → 'State) → 'State → 'T1 list → 'T2 list → 'State 将函数应用于两个集合的对应元素,将累加器参数贯穿计算。集合的大小必须相同。如果输入函数是 f,元素是 i0...iN 和 j0...jN,则此函数计算 f (... (f s i0 j0)...) iN jN。
foldBack : ('T → 'State → 'State) → 'T list → 'State → 'State 将函数应用于集合的每个元素,将累加器参数贯穿计算。如果输入函数是 f,元素是 i0...iN,则计算 f i0 (...(f iN s))。
foldBack2 : ('T1 → 'T2 → 'State → 'State) → 'T1 list → 'T2 list → 'State → 'State 将函数应用于两个集合的对应元素,将累加器参数贯穿计算。集合的大小必须相同。如果输入函数是 f,元素是 i0...iN 和 j0...jN,则此函数计算 f i0 j0 (...(f iN jN s))。
forall : ('T → bool) → 'T list → bool 测试集合的所有元素是否都满足给定的谓词。
forall2 : ('T1 → 'T2 → bool) → 'T1 list → 'T2 list → bool 检查集合中所有对应元素是否都满足给定的成对谓词。
head : 'T list → 'T 返回列表的第一个元素。
init : int → (int → 'T) → 'T list 通过对每个索引调用给定的生成器来创建一个列表。
isEmpty : 'T list → bool 如果列表不包含任何元素,则返回true,否则返回false
iter : ('T → unit) → 'T list → unit 将给定函数应用于集合的每个元素。
iter2 : ('T1 → 'T2 → unit) → 'T1 list → 'T2 list → unit 同时将给定函数应用于两个集合。这两个集合必须大小相同。
iteri : (int → 'T → unit) → 'T list → unit 将给定函数应用于集合的每个元素。传递给函数的整数表示元素的索引。
iteri2 : (int → 'T1 → 'T2 → unit) → 'T1 list → 'T2 list → unit 同时将给定函数应用于两个集合。这两个集合必须大小相同。传递给函数的整数表示元素的索引。
length : 'T list → int 返回列表的长度。
map : ('T → 'U) → 'T list → 'U list 创建一个新的集合,其元素是将给定函数应用于集合的每个元素的结果。
map2 : ('T1 → 'T2 → 'U) → 'T1 list → 'T2 list → 'U list 创建一个新的集合,其元素是将给定函数成对应用于两个集合的对应元素的结果。
map3 : ('T1 → 'T2 → 'T3 → 'U) → 'T1 list → 'T2 list → 'T3 list → 'U list 创建一个新的集合,其元素是将给定函数同时应用于三个集合的对应元素的结果。
mapi : (int → 'T → 'U) → 'T list → 'U list 创建一个新的集合,其元素是将给定函数应用于集合的每个元素的结果。传递给函数的整数索引表示正在转换的元素的索引(从 0 开始)。
mapi2 : (int → 'T1 → 'T2 → 'U) → 'T1 list → 'T2 list → 'U list 类似于 List.mapi,但是映射两个等长列表的对应元素。
max : 'T list → 'T 返回列表中所有元素中最大的元素,使用 Operators.max 进行比较。
maxBy : ('T → 'U) → 'T list → 'T 返回列表中所有元素中最大的元素,使用 Operators.max 对函数结果进行比较。
min : 'T list → 'T 返回列表中所有元素中最小的元素,使用 Operators.min 进行比较。
minBy : ('T → 'U) → 'T list → 'T 返回列表中所有元素中最小的元素,使用 Operators.min 对函数结果进行比较。
nth : 'T list → int → 'T 索引到列表中。第一个元素的索引为 0。
ofArray : 'T [] → 'T list 根据给定的数组创建一个列表。
ofSeq : seq<'T> → 'T list 根据给定的可枚举对象创建一个新的列表。
partition : ('T → bool) → 'T list * 'T list 将集合拆分为两个集合,分别包含给定谓词返回truefalse的元素。
permute : (int → int) → 'T list → 'T list 返回一个列表,其中所有元素都根据指定的排列进行排列。
pick : ('T → 'U option) → 'T list → 'U 将给定函数应用于连续元素,返回函数对某个值返回Some的第一个结果。
reduce : ('T → 'T → 'T) → 'T list → 'T 将函数应用于集合的每个元素,通过计算传递累加器参数。此函数将指定的函数应用于列表的前两个元素。然后,它将此结果与第三个元素一起传递到函数中,依此类推。最后,它返回最终结果。如果输入函数为 f,元素为 i0...iN,则此函数计算 f (... (f i0 i1) i2 ...) iN。
reduceBack : ('T → 'T → 'T) → 'T list → 'T 将函数应用于集合的每个元素,通过计算传递累加器参数。如果输入函数为 f,元素为 i0...iN,则此函数计算 f i0 (...(f iN-1 iN))。
replicate : (int → 'T → 'T list) 通过对每个索引调用给定的生成器来创建一个列表。
rev : 'T list → 'T list 返回一个新的列表,其中元素顺序相反。
scan : ('State → 'T → 'State) → 'State → 'T list → 'State list 将函数应用于集合的每个元素,通过计算传递累加器参数。此函数采用第二个参数,并将指定的函数应用于它和列表的第一个元素。然后,它将此结果与第二个元素一起传递到函数中,依此类推。最后,它返回中间结果和最终结果的列表。
scanBack : ('T → 'State → 'State) → 'T list → 'State → 'State list 类似于 foldBack,但是返回中间结果和最终结果。
sort : 'T list → 'T list 使用 Operators.compare 对给定列表进行排序。
sortBy : ('T → 'Key) → 'T list → 'T list 使用给定投影给出的键对给定列表进行排序。使用 Operators.compare 比较键。
sortWith : ('T → 'T → int) → 'T list → 'T list 使用给定的比较函数对给定列表进行排序。
sum : ^T list → ^T 返回列表中元素的总和。
sumBy : ('T → ^U) → 'T list → ^U 返回通过将函数应用于列表的每个元素生成的总和。
tail : 'T list → 'T list 返回输入列表,但不包含第一个元素。
toArray : 'T list → 'T [] 根据给定的列表创建一个数组。
toSeq : 'T list → seq<'T> 将给定列表视为一个序列。
tryFind : ('T → bool) → 'T list → 'T option 返回给定函数返回true的第一个元素。如果不存在这样的元素,则返回None
tryFindIndex : ('T → bool) → 'T list → int option 返回列表中满足给定谓词的第一个元素的索引。如果不存在这样的元素,则返回None
tryPick : ('T → 'U option) → 'T list → 'U option 将给定函数应用于连续元素,返回函数对某个值返回Some的第一个结果。如果不存在这样的元素,则返回None
unzip : ('T1 * 'T2) list → 'T1 list * 'T2 list 将一对列表拆分为两个列表。
unzip3 : ('T1 * 'T2 * 'T3) list → 'T1 list * 'T2 list * 'T3 list 将三元组列表拆分为三个列表。
zip : 'T1 list → 'T2 list → ('T1 * 'T2) list 将两个列表组合成一个对列表。这两个列表必须长度相等。
zip3 : 'T1 list → 'T2 list → 'T3 list → ('T1 * 'T2 * 'T3) list 将三个列表组合成一个三元组列表。这些列表必须长度相等。

以下示例演示了上述功能的用法:

示例 1

此程序演示了递归反转列表:

let list1 = [ 2; 4; 6; 8; 10; 12; 14; 16 ]
printfn "The original list: %A" list1

let reverse lt =
   let rec loop acc = function
      | [] -> acc
      | hd :: tl -> loop (hd :: acc) tl
   loop [] lt

printfn "The reversed list: %A" (reverse list1)

编译并执行程序后,将产生以下输出:

The original list: [2; 4; 6; 8; 10; 12; 14; 16]
The reversed list: [16; 14; 12; 10; 8; 6; 4; 2]

但是,您可以为此目的使用模块的rev函数:

let list1 = [ 2; 4; 6; 8; 10; 12; 14; 16 ]
printfn "The original list: %A" list1
printfn "The reversed list: %A" (List.rev list1)

编译并执行程序后,将产生以下输出:

The original list: [2; 4; 6; 8; 10; 12; 14; 16]
The reversed list: [16; 14; 12; 10; 8; 6; 4; 2]

示例 2

此程序演示了使用List.filter方法过滤列表:

let list1 = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
printfn "The list: %A" list1
let list2 = list1 |> List.filter (fun x -> x % 2 = 0);;
printfn "The Filtered list: %A" list2

编译并执行程序后,将产生以下输出:

The list: [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
The Filtered list: [2; 4; 6; 8; 10]

示例 3

List.map方法将列表从一种类型映射到另一种类型:

let list1 = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
printfn "The list: %A" list1
let list2 = list1 |> List.map (fun x -> (x * x).ToString());;
printfn "The Mapped list: %A" list2

编译并执行程序后,将产生以下输出:

The list: [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
The Mapped list: ["1"; "4"; "9"; "16"; "25"; "36"; "49"; "64"; "81"; "100"]

示例 4

List.append方法和@运算符将一个列表附加到另一个列表:

let list1 = [1; 2; 3; 4; 5 ]
let list2 = [6; 7; 8; 9; 10]
let list3 = List.append list1 list2

printfn "The first list: %A" list1
printfn "The second list: %A" list2
printfn "The appened list: %A" list3

let lt1 = ['a'; 'b';'c' ]
let lt2 = ['e'; 'f';'g' ]
let lt3 = lt1 @ lt2

printfn "The first list: %A" lt1
printfn "The second list: %A" lt2
printfn "The appened list: %A" lt3

编译并执行程序后,将产生以下输出:

The first list: [1; 2; 3; 4; 5]
The second list: [6; 7; 8; 9; 10]
The appened list: [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
The first list: ['a'; 'b'; 'c']
The second list: ['e'; 'f'; 'g']
The appened list: ['a'; 'b'; 'c'; 'e'; 'f'; 'g']

示例 5

List.sort方法对列表进行排序。List.sum方法给出列表中元素的总和,List.average方法给出列表中元素的平均值:

let list1 = [9.0; 0.0; 2.0; -4.5; 11.2; 8.0; -10.0]
printfn "The list: %A" list1

let list2 = List.sort list1
printfn "The sorted list: %A" list2

let s = List.sum list1
let avg = List.average list1
printfn "The sum: %f" s
printfn "The average: %f" avg

编译并执行程序后,将产生以下输出:

The list: [9.0; 0.0; 2.0; -4.5; 11.2; 8.0; -10.0]
The sorted list: [-10.0; -4.5; 0.0; 2.0; 8.0; 9.0; 11.2]
The sum: 15.700000
The average: 2.242857

“fold”操作将函数应用于列表中的每个元素,将函数的结果聚合到累加器变量中,并将累加器作为fold操作的结果返回。

示例 6

List.fold方法从左到右将函数应用于每个元素,而List.foldBack方法从右到左将函数应用于每个元素。

let sumList list = List.fold (fun acc elem -> acc + elem) 0 list
printfn "Sum of the elements of list %A is %d." [ 1 .. 10 ] (sumList [ 1 .. 10 ])

编译并执行程序后,将产生以下输出:

Sum of the elements of list [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] is 55.

F# - 序列

序列,像列表一样,也表示值的排序集合。但是,序列或序列表达式中的元素在需要时计算。它们不是一次性计算的,因此它们用于表示无限数据结构。

定义序列

序列使用以下语法定义:

seq { expr }

例如:

let seq1 = seq { 1 .. 10 }

创建序列和序列表达式

与列表类似,您可以使用范围和推导式创建序列。

序列表达式是可以用来创建序列的表达式。这些可以:

  • 通过指定范围。
  • 通过指定带递增或递减的范围。
  • 通过使用yield关键字来生成成为序列一部分的值。
  • 通过使用→运算符。

以下示例演示了这个概念:

示例 1

(* Sequences *)
let seq1 = seq { 1 .. 10 }

(* ascending order and increment*)
printfn "The Sequence: %A" seq1
let seq2 = seq { 1 .. 5 .. 50 }

(* descending order and decrement*)
printfn "The Sequence: %A" seq2
let seq3 = seq {50 .. -5 .. 0}
printfn "The Sequence: %A" seq3

(* using yield *)
let seq4 = seq { for a in 1 .. 10 do yield a, a*a, a*a*a }
printfn "The Sequence: %A" seq4

编译并执行程序后,将产生以下输出:

The Sequence: seq [1; 2; 3; 4; ...]
The Sequence: seq [1; 6; 11; 16; ...]
The Sequence: seq [50; 45; 40; 35; ...]
The Sequence: seq [(1, 1, 1); (2, 4, 8); (3, 9, 27); (4, 16, 64); ...]

示例 2

以下程序打印 1 到 50 之间的素数:

(* Recursive isprime function. *)
let isprime n =
   let rec check i =
      i > n/2 || (n % i <> 0 && check (i + 1))
   check 2

let primeIn50 = seq { for n in 1..50 do if isprime n then yield n }
for x in primeIn50 do
   printfn "%d" x

编译并执行程序后,将产生以下输出:

1
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47

序列的基本操作

下表显示了序列数据类型上的基本操作:

说明
append : seq<'T> → seq<'T> → seq<'T> 将两个给定的枚举包装为单个连接的枚举。
average : seq<^T> → ^T 返回序列中元素的平均值。
averageBy : ('T → ^U) → seq<'T> → ^U 返回通过将函数应用于序列的每个元素生成的平均值。
cache : seq<'T> → seq<'T> 返回一个与输入序列的缓存版本对应的序列。
cast : IEnumerable → seq<'T> 将松散类型的 System.Collections 序列包装为类型化序列。
choose : ('T → 'U option) → seq<'T> → seq<'U> 将给定函数应用于列表的每个元素。返回由每个元素的结果组成的列表,其中函数返回Some
collect : ('T → 'Collection) → seq<'T> → seq<'U> 将给定函数应用于序列的每个元素并连接所有结果。
compareWith : ('T → 'T → int) → seq<'T> → seq<'T> → int 使用给定的比较函数逐元素比较两个序列。
concat : seq<'Collection> → seq<'T> 将给定的枚举-枚举组合为单个连接的枚举。
countBy : ('T → 'Key) → seq<'T> → seq<'Key * int> 将键生成函数应用于序列的每个元素,并返回一个序列,该序列包含唯一的键及其在原始序列中的出现次数。
delay : (unit → seq<'T>) → seq<'T> 返回一个根据给定的延迟序列规范构建的序列。
distinct : seq<'T> → seq<'T> 返回一个序列,该序列根据元素上的泛型哈希和相等性比较不包含重复项。如果某个元素在序列中出现多次,则丢弃后续出现。
distinctBy : ('T → 'Key) → seq<'T> → seq<'T> 返回一个序列,该序列根据给定键生成函数返回的键上的泛型哈希和相等性比较不包含重复项。如果某个元素在序列中出现多次,则丢弃后续出现。
empty : seq<'T> 创建一个空序列。
exactlyOne : seq<'T> → 'T 返回序列中唯一的元素。
exists : ('T → bool) → seq<'T> → bool 测试序列中的任何元素是否满足给定的谓词。
exists2 : ('T1 → 'T2 → bool) → seq<'T1> → seq<'T2> → bool 测试输入序列的任何一对对应元素是否满足给定的谓词。
filter : ('T → bool) → seq<'T> → seq<'T> 返回一个新集合,其中只包含给定谓词返回true 的集合的元素。
find : ('T → bool) → seq<'T> → 'T 返回给定函数返回true 的第一个元素。
findIndex : ('T → bool) → seq<'T> → int 返回第一个使给定函数返回 **true** 的元素的索引。
fold : ('State → 'T → 'State) → 'State → seq<'T> → 'State 将函数应用于集合的每个元素,并将累加器参数贯穿计算。如果输入函数为 f,元素为 i0...iN,则此函数计算 f (... (f s i0)...) iN。
forall : ('T → bool) → seq<'T> → bool 测试序列中的所有元素是否满足给定的谓词。
forall2 : ('T1 → 'T2 → bool) → seq<'T1> → seq<'T2> → bool 测试从两个序列中提取的所有元素对是否满足给定的谓词。如果一个序列比另一个序列短,则忽略较长序列的剩余元素。
groupBy : ('T → 'Key) → seq<'T> → seq<'Key * seq<'T>> 将键生成函数应用于序列的每个元素,并产生唯一的键序列。每个唯一键还包含与该键匹配的所有元素的序列。
head : seq<'T> → 'T 返回序列的第一个元素。
init : int → (int → 'T) → seq<'T> 生成一个新的序列,当迭代时,通过调用给定函数返回连续的元素,直到给定的计数。调用函数的结果不会保存,也就是说,函数会根据需要重新应用以重新生成元素。该函数将传递正在生成的项目的索引。
initInfinite : (int → 'T) → seq<'T> 生成一个新的序列,当迭代时,将通过调用给定函数返回连续的元素。调用函数的结果不会保存,也就是说,函数将根据需要重新应用以重新生成元素。该函数将传递正在生成的项目的索引。
isEmpty : seq<'T> → bool 测试序列是否包含任何元素。
iter : ('T → unit) → seq<'T> → unit 将给定函数应用于集合的每个元素。
iter2 : ('T1 → 'T2 → unit) → seq<'T1> → seq<'T2> → unit 同时将给定函数应用于两个集合。如果一个序列比另一个序列短,则忽略较长序列的剩余元素。
iteri : (int → 'T → unit) → seq<'T> → unit 将给定函数应用于集合的每个元素。传递给函数的整数表示元素的索引。
last : seq<'T> → 'T 返回序列的最后一个元素。
length : seq<'T> → int 返回序列的长度。
map : ('T → 'U) → seq<'T> → seq<'U> 创建一个新的集合,其元素是将给定函数应用于集合的每个元素的结果。给定函数将在使用从对象检索到的枚举器的MoveNext方法按需请求元素时应用。
map2 : ('T1 → 'T2 → 'U) → seq<'T1> → seq<'T2> → seq<'U> 创建一个新的集合,其元素是将给定函数应用于来自两个序列的对应元素对的结果。如果一个输入序列比另一个序列短,则忽略较长序列的剩余元素。
mapi : (int → 'T → 'U) → seq<'T> → seq<'U> 创建一个新的集合,其元素是将给定函数应用于集合的每个元素的结果。传递给函数的整数索引表示正在转换的元素的索引(从 0 开始)。
max : seq<'T> → 'T 返回序列中所有元素中最大的元素,使用 Operators.max 进行比较。
maxBy : ('T → 'U) → seq<'T> → 'T 返回序列中所有元素中最大的元素,使用 Operators.max 对函数结果进行比较。
min : seq<'T> → 'T 返回序列中所有元素中最小的元素,使用 Operators.min 进行比较。
minBy : ('T → 'U) → seq<'T> → 'T 返回序列中所有元素中最小的元素,使用 Operators.min 对函数结果进行比较。
nth : int → seq<'T> → 'T 计算集合中的第 n 个元素。
ofArray : 'T array → seq<'T> 将给定数组视为序列。
ofList : 'T list → seq<'T> 将给定列表视为一个序列。
pairwise : seq<'T> → seq<'T * 'T> 返回输入序列中每个元素及其前驱的序列,第一个元素除外,它只作为第二个元素的前驱返回。
pick : ('T → 'U option) → seq<'T> → 'U 将给定函数应用于连续元素,返回函数返回 **Some** 值的第一个值。
readonly : seq<'T> → seq<'T> 创建一个新的序列对象,该对象委托给给定的序列对象。这确保了原始序列不能通过类型转换被重新发现和变异。例如,如果给定一个数组,返回的序列将返回数组的元素,但不能将返回的序列对象转换为数组。
reduce : ('T → 'T → 'T) → seq<'T> → 'T 将函数应用于序列的每个元素,并将累加器参数贯穿计算。首先将函数应用于前两个元素。然后将此结果与第三个元素一起输入函数,依此类推。返回最终结果。
scan : ('State → 'T → 'State) → 'State → seq<'T> → seq<'State> 类似于 Seq.fold,但按需计算并返回中间结果和最终结果的序列。
singleton : 'T → seq<'T> 返回仅产生一个项目的序列。
skip : int → seq<'T> → seq<'T> 返回一个序列,该序列跳过底层序列的指定数量的元素,然后产生序列的其余元素。
skipWhile : ('T → bool) → seq<'T> → seq<'T> 返回一个序列,当迭代时,跳过底层序列的元素,而给定的谓词返回 **true**,然后产生序列的其余元素。
sort : seq<'T> → seq<'T> 产生按键排序的序列。
sortBy : ('T → 'Key) → seq<'T> → seq<'T> 将键生成函数应用于序列的每个元素,并产生按键排序的序列。使用 Operators.compare 实现的泛型比较来比较键。
sum : seq<^T> → ^T 返回序列中元素的总和。
sumBy 返回通过将函数应用于序列的每个元素而生成的总和。
take : int → seq<'T> → seq<'T> 返回序列中直到指定计数的第一个元素。
takeWhile : ('T → bool) → seq<'T> → seq<'T> 返回一个序列,当迭代时,产生底层序列的元素,而给定的谓词返回 **true**,然后不返回任何其他元素。
toArray : seq<'T> → 'T[] 从给定集合创建一个数组。
toList : seq<'T> → 'T list 从给定集合创建一个列表。
truncate : int → seq<'T> → seq<'T> 返回一个序列,当枚举时,返回的元素不超过指定数量。
tryFind : ('T → bool) → seq<'T> → 'T option 返回第一个使给定函数返回 **true** 的元素,如果不存在这样的元素,则返回 **None**。
tryFindIndex : ('T → bool) → seq<'T> → int option 返回序列中满足给定谓词的第一个元素的索引,如果不存在这样的元素,则返回 **None**。
tryPick : ('T → 'U option) → seq<'T> → 'U option 将给定函数应用于连续元素,返回函数返回 **Some** 值的第一个值。
unfold : ('State → 'T * 'State option) → 'State → seq<'T> 返回一个包含给定计算生成的元素的序列。
where : ('T → bool) → seq<'T> → seq<'T> 返回一个新集合,其中只包含给定谓词返回 **true** 的集合的元素。Seq.filter 的同义词。
windowed : int → seq<'T> → seq<'T []> 返回一个序列,该序列产生包含从输入序列中提取的元素的滑动窗口。每个窗口都作为新的数组返回。
zip : seq<'T1> → seq<'T2> → seq<'T1 * 'T2> 将两个序列组合成一个配对列表。这两个序列不必长度相等——当一个序列用尽时,忽略另一个序列中的任何剩余元素。
zip3 : seq<'T1> → seq<'T2> → seq<'T3> → seq<'T1 * 'T2 * 'T3> 将三个序列组合成一个三元组列表。这些序列不必长度相等——当一个序列用尽时,忽略其他序列中的任何剩余元素。

以下示例演示了上述某些功能的用法:

示例 1

此程序创建一个空序列,稍后将其填充——

(* Creating sequences *)
let emptySeq = Seq.empty
let seq1 = Seq.singleton 20

printfn"The singleton sequence:"
printfn "%A " seq1
printfn"The init sequence:"

let seq2 = Seq.init 5 (fun n -> n * 3)
Seq.iter (fun i -> printf "%d " i) seq2
printfn""

(* converting an array to sequence by using cast *)
printfn"The array sequence 1:"
let seq3 = [| 1 .. 10 |] :> seq<int>
Seq.iter (fun i -> printf "%d " i) seq3
printfn""

(* converting an array to sequence by using Seq.ofArray *)
printfn"The array sequence 2:"
let seq4 = [| 2..2.. 20 |] |> Seq.ofArray
Seq.iter (fun i -> printf "%d " i) seq4
printfn""

编译并执行程序后,将产生以下输出:

The singleton sequence:
seq [20]
The init sequence:
0 3 6 9 12
The array sequence 1:
1 2 3 4 5 6 7 8 9 10
The array sequence 2:
2 4 6 8 10 12 14 16 18 20

请注意:

  • Seq.empty 方法创建一个空序列。

  • Seq.singleton 方法创建一个仅包含一个指定元素的序列。

  • Seq.init 方法创建一个序列,其元素是通过使用给定函数创建的。

  • Seq.ofArray 和 Seq.ofList<'T> 方法从数组和列表创建序列。

  • Seq.iter 方法允许迭代序列。

示例 2

Seq.unfold 方法根据一个计算函数生成一个序列,该函数接受一个状态并将其转换以生成序列中的每个后续元素。

以下函数生成前 20 个自然数:

let seq1 = Seq.unfold (fun state -> if (state > 20) then None else Some(state, state + 1)) 0
printfn "The sequence seq1 contains numbers from 0 to 20."
for x in seq1 do printf "%d " x
printfn" "

编译并执行程序后,将产生以下输出:

The sequence seq1 contains numbers from 0 to 20.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

示例 3

Seq.truncate 方法根据另一个序列创建一个序列,但将序列限制为指定数量的元素。

Seq.take 方法创建一个新序列,其中包含从序列开头开始的指定数量的元素。

let mySeq = seq { for i in 1 .. 10 -> 3*i }
let truncatedSeq = Seq.truncate 5 mySeq
let takeSeq = Seq.take 5 mySeq

printfn"The original sequence"
Seq.iter (fun i -> printf "%d " i) mySeq
printfn""

printfn"The truncated sequence"
Seq.iter (fun i -> printf "%d " i) truncatedSeq
printfn""

printfn"The take sequence"
Seq.iter (fun i -> printf "%d " i) takeSeq
printfn""

编译并执行程序后,将产生以下输出:

The original sequence
3 6 9 12 15 18 21 24 27 30
The truncated sequence
3 6 9 12 15
The take sequence
3 6 9 12 15

F# - 集合

F# 中的集合是一种数据结构,充当项目的集合,但不保留插入项目的顺序。集合不允许将重复的条目插入集合中。

创建集合

集合可以通过以下方式创建:

  • 使用 Set.empty 创建空集合,并使用 add 函数添加项目。
  • 将序列和列表转换为集合。

以下程序演示了这些技术:

(* creating sets *)
let set1 = Set.empty.Add(3).Add(5).Add(7). Add(9)
printfn"The new set: %A" set1

let weekdays = Set.ofList ["mon"; "tues"; "wed"; "thurs"; "fri"]
printfn "The list set: %A" weekdays

let set2 = Set.ofSeq [ 1 .. 2.. 10 ]
printfn "The sequence set: %A" set2

编译并执行程序后,将产生以下输出:

The new set: set [3; 5; 7; 9]
The list set: set ["fri"; "mon"; "thurs"; "tues"; "wed"]
The sequence set: set [1; 3; 5; 7; 9]

集合的基本操作

下表显示了集合的基本操作:

说明
add : 'T → Set<'T> → Set<'T> 返回一个新集合,其中添加了一个元素。如果集合已包含给定元素,则不会引发异常。
contains : 'T → Set<'T> → bool 如果给定元素在给定集合中,则评估为**true**。
count : Set<'T> → int 返回集合中元素的数量。
difference : Set<'T> → Set<'T> → Set<'T> 返回一个新集合,其中从第一个集合中删除了第二个集合的元素。
empty : Set<'T> 指定类型的空集。
exists : ('T → bool) → Set<'T> → bool 测试集合中的任何元素是否满足给定的谓词。如果输入函数是谓词,元素是 i0...iN,则此函数计算谓词 i0 或 ... 或谓词 iN。
filter : ('T → bool) → Set<'T> → Set<'T> 返回一个新集合,其中只包含给定谓词返回true 的集合的元素。
fold : ('State → 'T → 'State) → 'State → Set<'T> → 'State 将给定的累加函数应用于集合的所有元素。
foldBack : ('T → 'State → 'State) → Set<'T> → 'State → 'State 将给定的累加函数应用于集合的所有元素。
forall : ('T → bool) → Set<'T> → bool 测试集合中的所有元素是否满足给定的谓词。如果输入函数是 p,元素是 i0...iN,则此函数计算 p i0 && ... && p iN。
intersect : Set<'T> → Set<'T> → Set<'T> 计算两个集合的交集。
intersectMany : seq<Set<'T>> → Set<'T> 计算一系列集合的交集。序列必须是非空的。
isEmpty : Set<'T> → bool 如果集合为空,则返回**true**。
isProperSubset : Set<'T> → Set<'T> → bool 如果第一个集合的所有元素都在第二个集合中,并且第二个集合中至少有一个元素不在第一个集合中,则评估为**true**。
isProperSuperset : Set<'T> → Set<'T> → bool 如果第二个集合的所有元素都在第一个集合中,并且第一个集合中至少有一个元素不在第二个集合中,则评估为**true**。
isSubset : Set<'T> → Set<'T> → bool 如果第一个集合的所有元素都在第二个集合中,则评估为**true**。
isSuperset : Set<'T> → Set<'T> → bool 如果第二个集合的所有元素都在第一个集合中,则评估为**true**。
iter : ('T → unit) → Set<'T> → unit 根据比较函数,将给定函数应用于集合的每个元素。
map : ('T → 'U) → Set<'T> → Set<'U> 返回一个新集合,其中包含将给定函数应用于输入集合的每个元素的结果。
maxElement : Set<'T> → 'T 根据集合使用的排序返回集合中最高的元素。
minElement : Set<'T> → 'T 根据集合使用的排序返回集合中最低的元素。
ofArray : 'T array → Set<'T> 创建一个包含与给定数组相同元素的集合。
ofList : 'T list → Set<'T> 创建一个包含与给定列表相同元素的集合。
ofSeq : seq<'T> → Set<'T> 根据给定的可枚举对象创建一个新集合。
partition : ('T → bool) → Set<'T> → Set<'T> * Set<'T> 将集合拆分为两个集合,分别包含给定谓词返回 true 和 false 的元素。
remove : 'T → Set<'T> → Set<'T> 返回一个新集合,其中删除了给定元素。如果集合不包含给定元素,则不会引发异常。
singleton : 'T → Set<'T> 包含给定元素的集合。
toArray : Set<'T> → 'T array 创建一个数组,其中按顺序包含集合的元素。
toList : Set<'T> → 'T list 创建一个列表,其中按顺序包含集合的元素。
toSeq : Set<'T> → seq<'T> 将集合的排序视图作为可枚举对象返回。
union : Set<'T> → Set<'T> → Set<'T> 计算两个集合的并集。
unionMany : seq<Set<'T>> → Set<'T> 计算一系列集合的并集。

以下示例演示了上述一些功能的用法:

示例

let a = Set.ofSeq [ 1 ..2.. 20 ]
let b = Set.ofSeq [ 1 ..3 .. 20 ]
let c = Set.intersect a b
let d = Set.union a b
let e = Set.difference a b

printfn "Set a: "
Set.iter (fun x -> printf "%O " x) a
printfn""

printfn "Set b: "
Set.iter (fun x -> printf "%O " x) b
printfn""

printfn "Set c = set intersect of a and b : "
Set.iter (fun x -> printf "%O " x) c
printfn""

printfn "Set d = set union of a and b : "
Set.iter (fun x -> printf "%O " x) d
printfn""

printfn "Set e = set difference of a and b : "
Set.iter (fun x -> printf "%O " x) e
printfn""

编译并执行程序后,将产生以下输出:

Set a:
1 3 5 7 9 11 13 15 17 19
Set b:
1 4 7 10 13 16 19
Set c = set intersect of a and b :
1 7 13 19
Set d = set union of a and b :
1 3 4 5 7 9 10 11 13 15 16 17 19
Set e = set difference of a and b :
3 5 9 11 15 17

F# - 映射

在 F# 中,映射是一种特殊的集合,它将值与键关联。映射的创建方式与集合的创建方式类似。

创建映射

通过使用 Map.empty 创建空映射并使用 Add 函数添加项目来创建映射。以下示例演示了这一点:

示例

(* Create an empty Map *)
let students =
   Map.empty. (* Creating an empty Map *)
      Add("Zara Ali", "1501").
      Add("Rishita Gupta", "1502").
      Add("Robin Sahoo", "1503").
      Add("Gillian Megan", "1504");;
printfn "Map - students: %A" students

(* Convert a list to Map *)
let capitals =
   [ "Argentina", "Buenos Aires";
      "France ", "Paris";
      "Chili", "Santiago";
      "Malaysia", " Kuala Lumpur";
      "Switzerland", "Bern" ]
   |> Map.ofList;;
printfn "Map capitals : %A" capitals

编译并执行程序后,将产生以下输出:

Map - students: map
[("Gillian Megan", "1504"); ("Rishita Gupta", "1502"); ("Robin Sahoo", "1503
");
("Zara Ali", "1501")]
Map capitals : map
[("Argentina", "Buenos Aires"); ("Chili", "Santiago"); ("France ", "Paris");
("Malaysia", " Kuala Lumpur"); ("Switzerland", "Bern")]

可以使用键访问映射中的单个元素。

示例

(* Create an empty Map *)
let students =
   Map.empty. (* Creating an empty Map *)
      Add("Zara Ali", "1501").
      Add("Rishita Gupta", "1502").
      Add("Robin Sahoo", "1503").
      Add("Gillian Megan", "1504");;
printfn "Map - students: %A" students

(*Accessing an element using key *)
printfn "%A" students.["Zara Ali"]

编译并执行程序后,将产生以下输出:

Map - students: map
[("Gillian Megan", "1504"); ("Rishita Gupta", "1502"); ("Robin Sahoo", "1503
");
("Zara Ali", "1501")]
"1501"

映射的基本操作

添加模块名称

下表显示了映射的基本操作:

成员 说明
Add 返回一个新映射,其中将绑定添加到给定映射中。
ContainsKey 测试元素是否在映射的域中。
Count 映射中的绑定数。
IsEmpty 如果映射中没有绑定,则返回 true。
Item 查找映射中的元素。如果映射中不存在绑定,则引发 KeyNotFoundException。
Remove 从映射的域中删除元素。如果元素不存在,则不会引发异常。
TryFind 查找映射中的元素,如果元素在映射的域中则返回**Some**值,否则返回**None**。

以下示例演示了上述一些功能的用法:

示例

(* Create an empty Map *)
let students =
   Map.empty. (* Creating an empty Map *)
      Add("Zara Ali", "1501").
      Add("Rishita Gupta", "1502").
      Add("Robin Sahoo", "1503").
      Add("Gillian Megan", "1504").
      Add("Shraddha Dubey", "1505").
      Add("Novonil Sarker", "1506").
      Add("Joan Paul", "1507");;
printfn "Map - students: %A" students
printfn "Map - number of students: %d" students.Count

(* finding the registration number of a student*)
let found = students.TryFind "Rishita Gupta"
match found with
| Some x -> printfn "Found %s." x
| None -> printfn "Did not find the specified value."

编译并执行程序后,将产生以下输出:

Map - students: map
[("Gillian Megan", "1504"); ("Joan Paul", "1507"); ("Novonil Sarker", "1506"
);
("Rishita Gupta", "1502"); ("Robin Sahoo", "1503");
("Shraddha Dubey", "1505"); ("Zara Ali", "1501")]
Map - number of students: 7
Found 1502.

F# - 判别联合

联合,或区分联合允许您构建表示明确定义的选择集的复杂数据结构。例如,您需要构建一个 *选择* 变量的实现,该变量具有 yes 和 no 两个值。使用联合工具,您可以设计此变量。

语法

区分联合使用以下语法定义:

type type-name =
   | case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] 
type2 ...]
   | case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
...

我们对 *选择* 的简单实现如下所示:

type choice =
   | Yes
   | No

以下示例使用 choice 类型:

type choice =
   | Yes
   | No

let x = Yes (* creates an instance of choice *)
let y = No (* creates another instance of choice *)
let main() =
   printfn "x: %A" x
   printfn "y: %A" y
main()

编译并执行程序后,将产生以下输出:

x: Yes
y: No

示例 1

以下示例显示了电压状态的实现,该状态设置高或低位:

type VoltageState =
   | High
   | Low

let toggleSwitch = function (* pattern matching input *)
   | High -> Low
   | Low -> High

let main() =
   let on = High
   let off = Low
   let change = toggleSwitch off

   printfn "Switch on state: %A" on
   printfn "Switch off state: %A" off
   printfn "Toggle off: %A" change
   printfn "Toggle the Changed state: %A" (toggleSwitch change)

main()

编译并执行程序后,将产生以下输出:

Switch on state: High
Switch off state: Low
Toggle off: High
Toggle the Changed state: Low

示例 2

type Shape =
   // here we store the radius of a circle
   | Circle of float

   // here we store the side length.
   | Square of float

   // here we store the height and width.
   | Rectangle of float * float

let pi = 3.141592654

let area myShape =
   match myShape with
   | Circle radius -> pi * radius * radius
   | Square s -> s * s
   | Rectangle (h, w) -> h * w

let radius = 12.0
let myCircle = Circle(radius)
printfn "Area of circle with radius %g: %g" radius (area myCircle)

let side = 15.0
let mySquare = Square(side)
printfn "Area of square that has side %g: %g" side (area mySquare)

let height, width = 5.0, 8.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle with height %g and width %g is %g" height width (area myRectangle)

编译并执行程序后,将产生以下输出:

Area of circle with radius 12: 452.389
Area of square that has side 15: 225
Area of rectangle with height 5 and width 8 is 40

F# - 可变数据

F#中的变量是不可变的,这意味着一旦变量绑定到一个值,就不能更改它。它们实际上被编译为静态只读属性。

以下示例演示了这一点。

示例

let x = 10
let y = 20
let z = x + y

printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

let x = 15
let y = 20
let z = x + y

printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

编译并执行程序时,它会显示以下错误消息:

Duplicate definition of value 'x'
Duplicate definition of value 'Y'
Duplicate definition of value 'Z'

可变变量

有时需要更改存储在变量中的值。为了指定在程序的后面部分,声明和赋值的变量的值可能会发生变化,F# 提供了**mutable**关键字。您可以使用此关键字声明和赋值可变变量,其值将更改。

mutable关键字允许您声明和赋值可变变量的值。

您可以使用**let**关键字为可变变量赋值初始值。但是,要为其赋值新的后续值,您需要使用**<-**运算符。

例如:

let mutable x = 10
x <- 15

以下示例将阐明这个概念:

示例

let mutable x = 10
let y = 20
let mutable z = x + y

printfn "Original Values:"
printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

printfn "Let us change the value of x"
printfn "Value of z will change too."

x <- 15
z <- x + y

printfn "New Values:"
printfn "x: %i" x
printfn "y: %i" y
printfn "z: %i" z

编译并执行程序后,将产生以下输出:

Original Values:
x: 10
y: 20
z: 30
Let us change the value of x
Value of z will change too.
New Values:
x: 15
y: 20
z: 35

可变数据的用途

可变数据通常在数据处理中需要并使用,尤其是在记录数据结构中。以下示例演示了这一点:

open System

type studentData =
   { ID : int;
      mutable IsRegistered : bool;
      mutable RegisteredText : string; }

let getStudent id =
   { ID = id;
      IsRegistered = false;
      RegisteredText = null; }

let registerStudents (students : studentData list) =
   students |> List.iter(fun st ->
      st.IsRegistered <- true
      st.RegisteredText <- sprintf "Registered %s" (DateTime.Now.ToString("hh:mm:ss"))

      Threading.Thread.Sleep(1000) (* Putting thread to sleep for 1 second to simulate processing overhead. *))

let printData (students : studentData list) =
   students |> List.iter (fun x -> printfn "%A" x)

let main() =
   let students = List.init 3 getStudent

   printfn "Before Process:"
   printData students

   printfn "After process:"
   registerStudents students
   printData students

   Console.ReadKey(true) |> ignore

main()

编译并执行程序后,将产生以下输出:

Before Process:
{ID = 0;
IsRegistered = false;
RegisteredText = null;}
{ID = 1;
IsRegistered = false;
RegisteredText = null;}
{ID = 2;
IsRegistered = false;
RegisteredText = null;}
After process:
{ID = 0;
IsRegistered = true;
RegisteredText = "Registered 05:39:15";}
{ID = 1;
IsRegistered = true;
RegisteredText = "Registered 05:39:16";}
{ID = 2;
IsRegistered = true;
RegisteredText = "Registered 05:39:17";}

F# - 数组

数组是固定大小、基于零的、可变的连续数据元素集合,所有元素都是同一类型。

创建数组

您可以使用各种语法和方法,或使用 Array 模块中的函数来创建数组。在本节中,我们将讨论在不使用模块函数的情况下创建数组。

在不使用函数的情况下,创建数组有三种语法方式:

  • 通过在 [| 和 |] 之间列出连续的值,并用分号分隔。
  • 通过将每个元素放在单独一行,在这种情况下,分号分隔符是可选的。
  • 通过使用序列表达式。

您可以使用点运算符 (.) 和方括号 ([ 和 ]) 来访问数组元素。

以下示例演示了创建数组:

//using semicolon separator
let array1 = [| 1; 2; 3; 4; 5; 6 |]
for i in 0 .. array1.Length - 1 do
   printf "%d " array1.[i]
printfn" "

// without semicolon separator
let array2 =
   [|
      1
      2
      3
      4
      5
   |]
for i in 0 .. array2.Length - 1 do
   printf "%d " array2.[i]
printfn" "

//using sequence
let array3 = [| for i in 1 .. 10 -> i * i |]
for i in 0 .. array3.Length - 1 do
   printf "%d " array3.[i]
printfn" "

编译并执行程序后,将产生以下输出:

1 2 3 4 5 6
1 2 3 4 5
1 4 9 16 25 36 49 64 81 100

数组的基本操作

库模块 Microsoft.FSharp.Collections.Array 支持对一维数组的操作。

下表显示了数组的基本操作:

说明
append : 'T [] → 'T [] → 'T [] 创建一个数组,其中包含一个数组的元素,然后是另一个数组的元素。
average : ^T [] → ^T 返回数组中元素的平均值。
averageBy : ('T → ^U) → 'T [] → ^U 返回通过将函数应用于数组的每个元素生成的元素的平均值。
blit : 'T [] → int → 'T [] → int → int → unit 读取一个数组中的一个元素范围,并将它们写入另一个数组。
choose : ('T → U option) → 'T [] → 'U [] 将提供的函数应用于数组的每个元素。返回一个数组,其中包含每个元素的函数返回 Some(x) 的结果 x。
collect : ('T → 'U []) → T [] → 'U [] 将提供的函数应用于数组的每个元素,连接结果,并返回组合数组。
concat : seq<'T []> → 'T [] 创建一个数组,其中包含提供的数组序列中每个数组的元素。
copy : 'T → 'T [] 创建一个数组,其中包含提供的数组的元素。
create : int → 'T → 'T [] 创建一个数组,其元素最初都是提供的 value。
empty : 'T [] 返回给定类型的空数组。
exists : ('T → bool) → 'T [] → bool 测试数组中的任何元素是否满足提供的谓词。
exists2 : ('T1 → 'T2 → bool) → 'T1 [] → 'T2 [] → bool 测试两个数组的任何一对对应元素是否满足提供的条件。
fill : 'T [] → int → int → 'T → unit 用提供的 value 填充数组的元素范围。
filter : ('T → bool) → 'T [] → 'T [] 返回一个集合,其中仅包含提供的数组中提供的条件返回**true**的元素。
find : ('T → bool) → 'T [] → 'T 返回提供的函数返回**true**的第一个元素。如果没有这样的元素,则引发 KeyNotFoundException。
findIndex : ('T → bool) → 'T [] → int 返回数组中满足提供的条件的第一个元素的索引。如果没有任何元素满足该条件,则引发 KeyNotFoundException。
fold : ('State → 'T → 'State) → 'State → 'T [] → 'State 将函数应用于数组的每个元素,将累加器参数贯穿计算。如果输入函数是 f,数组元素是 i0...iN,则此函数计算 f (...(f s i0)...) iN。
fold2 : ('State → 'T1 → 'T2 → 'State) → 'State → 'T1 [] → 'T2 [] → 'State 将函数应用于来自两个提供的数组的元素对,从左到右,将累加器参数贯穿计算。两个输入数组必须具有相同的长度;否则,将引发 ArgumentException。
foldBack : ('T → 'State → 'State) → 'T [] → 'State → 'State 将一个函数应用于数组的每个元素,并将累加器参数贯穿计算过程。如果输入函数为 f,数组元素为 i0...iN,则此函数计算 f i0 (...(f iN s))。
foldBack2 : ('T1 → 'T2 → 'State → 'State) → 'T1 [] → 'T2 [] → 'State → 'State 将一个函数应用于两个提供的数组中元素对,从右到左,并将累加器参数贯穿计算过程。两个输入数组必须长度相同;否则,将引发 ArgumentException。
forall : ('T → bool) → 'T [] → bool 测试数组中的所有元素是否都满足提供的条件。
forall2 : ('T1 → 'T2 → bool) → 'T1 [] → 'T2 [] → bool 测试两个提供的数组中所有对应元素是否都满足提供的条件。
get : 'T [] → int → 'T 从数组中获取一个元素。
init : int → (int → 'T) → 'T [] 使用提供的函数创建一个指定维度的数组。
isEmpty : 'T [] → bool 测试数组是否包含任何元素。
iter : ('T → unit) → 'T [] → unit 将提供的函数应用于数组的每个元素。
iter2 : ('T1 → 'T2 → unit) → 'T1 [] → 'T2 [] → unit 将提供的函数应用于两个数组中对应索引的元素对。两个数组必须长度相同;否则,将引发 ArgumentException。
iteri : (int → 'T → unit) → 'T [] → unit 将提供的函数应用于数组的每个元素。传递给函数的整数表示元素的索引。
iteri2 : (int → 'T1 → 'T2 → unit) → 'T1 [] → 'T2 [] → unit 将提供的函数应用于两个数组中对应索引的元素对,并传递元素的索引。两个数组必须长度相同;否则,将引发 ArgumentException。
length : 'T [] → int 返回数组的长度。Length 属性具有相同的功能。
map : ('T → 'U) → 'T [] → 'U [] 创建一个数组,其元素是将提供的函数应用于提供的数组的每个元素的结果。
map2 : ('T1 → 'T2 → 'U) → 'T1 [] → 'T2 [] → 'U [] 创建一个数组,其元素是将提供的函数应用于两个提供的数组的对应元素的结果。两个输入数组必须长度相同;否则,将引发 ArgumentException。
mapi : (int → 'T → 'U) → 'T [] → 'U [] 创建一个数组,其元素是将提供的函数应用于提供的数组的每个元素的结果。传递给函数的整数索引指示正在转换的元素的索引。
mapi2 : (int → 'T1 → 'T2 → 'U) → 'T1 [] → 'T2 [] → 'U [] 创建一个数组,其元素是将提供的函数应用于两个集合的对应元素的成对结果,并传递元素的索引。两个输入数组必须长度相同;否则,将引发 ArgumentException。
max : 'T [] → 'T 返回数组中所有元素的最大值。使用 Operators.max 来比较元素。
maxBy : ('T → 'U) → 'T [] → 'T 返回数组中所有元素的最大值,通过函数结果上的 Operators.max 进行比较。
min : ('T [] → 'T 返回数组中所有元素的最小值。使用 Operators.min 来比较元素。
minBy : ('T → 'U) → 'T [] → 'T 返回数组中所有元素的最小值。使用 Operators.min 来比较元素。
ofList : 'T list → 'T [] 根据提供的列表创建一个数组。
ofSeq : seq<'T> → 'T [] 根据提供的可枚举对象创建一个数组。
partition : ('T → bool) → 'T [] → 'T [] * 'T [] 将数组拆分为两个数组,一个包含提供的条件返回 **true** 的元素,另一个包含返回 **false** 的元素。
permute : (int → int) → 'T [] → 'T [] 根据指定的排列对数组的元素进行排列。
pick : ('T → 'U option) → 'T [] → 'U 将提供的函数应用于提供的数组的连续元素,返回函数对某些 x 返回 Some(x) 的第一个结果。如果函数从未返回 Some(x),则引发 KeyNotFoundException。
reduce : ('T → 'T → 'T) → 'T [] → 'T 将一个函数应用于数组的每个元素,并将累加器参数贯穿计算过程。如果输入函数为 f,数组元素为 i0...iN,则此函数计算 f (...(f i0 i1)...) iN。如果数组大小为零,则引发 ArgumentException。
reduceBack : ('T → 'T → 'T) → 'T [] → 'T 将一个函数应用于数组的每个元素,并将累加器参数贯穿计算过程。如果输入函数为 f,元素为 i0...iN,则此函数计算 f i0 (...(f iN-1 iN))。如果数组大小为零,则引发 ArgumentException。
rev : 'T [] → 'T [] 反转提供的数组中元素的顺序。
scan : ('State → 'T → 'State) → 'State → 'T [] → 'State [] 类似于 fold,但会返回中间结果以及最终结果。
scanBack : ('T → 'State → 'State) → 'T [] → 'State → 'State [] 类似于 foldBack,但会返回中间结果以及最终结果。
set : 'T [] → int → 'T → unit 设置数组中的一个元素。
sort : 'T[] → 'T [] 对数组的元素进行排序并返回一个新数组。使用 Operators.compare 来比较元素。
sortBy : ('T → 'Key) → 'T [] → 'T [] 通过使用提供的函数将元素转换为进行排序操作的类型来对数组的元素进行排序,并返回一个新数组。使用 Operators.compare 来比较元素。
sortInPlace : 'T [] → unit 通过就地更改数组来对数组的元素进行排序,使用提供的比较函数。使用 Operators.compare 来比较元素。
sortInPlaceBy : ('T → 'Key) → 'T [] → unit 通过使用提供的键投影就地更改数组来对数组的元素进行排序。使用 Operators.compare 来比较元素。
sortInPlaceWith : ('T → 'T → int) → 'T [] → unit 使用提供的比较函数对数组的元素进行排序,并就地更改数组。
sortWith : ('T → 'T → int) → 'T [] → 'T [] 使用提供的比较函数对数组的元素进行排序,并返回一个新数组。
sub : 'T [] → int → int → 'T [] 创建一个包含提供的子范围的数组,该子范围由起始索引和长度指定。
sum : 'T [] → ^T 返回数组中元素的总和。
sumBy : ('T → ^U) → 'T [] → ^U 返回通过将函数应用于数组的每个元素生成的总和。
toList : 'T [] → 'T list 将提供的数组转换为列表。
toSeq : 'T [] → seq<'T> 将提供的数组视为一个序列。
tryFind : ('T → bool) → 'T [] → 'T option 返回提供的数组中提供的函数返回 **true** 的第一个元素。如果不存在这样的元素,则返回 **None**。
tryFindIndex : ('T → bool) → 'T [] → int option 返回数组中第一个满足提供的条件的元素的索引。
tryPick : ('T → 'U option) → 'T [] → 'U option 将提供的函数应用于提供的数组的连续元素,并返回函数对某些 x 返回 Some(x) 的第一个结果。如果函数从未返回 Some(x),则返回 **None**。
unzip : ('T1 * 'T2) [] → 'T1 [] * 'T2 [] 将元组对数组拆分为两个数组的元组。
unzip3 : ('T1 * 'T2 * 'T3) [] → 'T1 [] * 'T2 [] * 'T3 [] 将三个元素的元组数组拆分为三个数组的元组。
zeroCreate : int → 'T [] 创建一个数组,其元素最初设置为默认值 Unchecked.defaultof<'T>。
zip : 'T1 [] → 'T2 [] → ('T1 * 'T2) [] 将两个数组组合成一个包含两个元素的元组数组。两个数组必须长度相等;否则,将引发 ArgumentException。
zip3 : 'T1 [] → 'T2 [] → 'T3 [] → ('T1 * 'T2 * 'T3) [] 将三个数组组合成一个包含三个元素的元组数组。三个数组必须长度相等;否则,将引发 ArgumentException。

在下一节中,我们将了解这些功能的一些用法。

使用函数创建数组

Array 模块提供了一些从头创建数组的函数。

  • **Array.empty** 函数创建一个新的空数组。

  • **Array.create** 函数创建一个指定大小的数组,并将所有元素设置为给定值。

  • **Array.init** 函数根据维度和生成元素的函数创建一个数组。

  • **Array.zeroCreate** 函数创建一个所有元素都初始化为零值的数组。

  • **Array.copy** 函数创建一个包含从现有数组复制的元素的新数组。

  • **Array.sub** 函数根据数组的子范围生成一个新数组。

  • **Array.append** 函数通过组合两个现有数组创建一个新数组。

  • **Array.choose** 函数选择数组中的元素以包含在新数组中。

  • Array.collect 函数对现有数组的每个数组元素运行指定函数,然后收集函数生成的元素并将它们组合成一个新数组。

  • Array.concat 函数接受一系列数组并将它们组合成单个数组。

  • Array.filter 函数接受一个布尔条件函数,并生成一个新数组,该数组仅包含输入数组中满足条件的元素。

  • Array.rev 函数通过反转现有数组的顺序来生成一个新数组。

以下示例演示了这些函数:

示例 1

(* using create and set *)
let array1 = Array.create 10 ""
for i in 0 .. array1.Length - 1 do
   Array.set array1 i (i.ToString())
for i in 0 .. array1.Length - 1 do
   printf "%s " (Array.get array1 i)
printfn " "

(* empty array *)
let array2 = Array.empty
printfn "Length of empty array: %d" array2.Length

let array3 = Array.create 10 7.0
printfn "Float Array: %A" array3

(* using the init and zeroCreate *)
let array4 = Array.init 10 (fun index -> index * index)
printfn "Array of squares: %A" array4

let array5 : float array = Array.zeroCreate 10
let (myZeroArray : float array) = Array.zeroCreate 10
printfn "Float Array: %A" array5

编译并执行程序后,将产生以下输出:

0 1 2 3 4 5 6 7 8 9
Length of empty array: 0
Float Array: [|7.0; 7.0; 7.0; 7.0; 7.0; 7.0; 7.0; 7.0; 7.0; 7.0|]
Array of squares: [|0; 1; 4; 9; 16; 25; 36; 49; 64; 81|]
Float Array: [|0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0|]

示例 2

(* creating subarray from element 5 *)
(* containing 15 elements thereon *)

let array1 = [| 0 .. 50 |]
let array2 = Array.sub array1 5 15
printfn "Sub Array:"
printfn "%A" array2

(* appending two arrays *)
let array3 = [| 1; 2; 3; 4|]
let array4 = [| 5 .. 9 |]
printfn "Appended Array:"
let array5 = Array.append array3 array4
printfn "%A" array5

(* using the Choose function *)
let array6 = [| 1 .. 20 |]
let array7 = Array.choose (fun elem -> if elem % 3 = 0 then
                                             Some(float (elem))
                                          else
                                             None) array6
printfn "Array with Chosen elements:"
printfn "%A" array7

(*using the Collect function *)
let array8 = [| 2 .. 5 |]
let array9 = Array.collect (fun elem -> [| 0 .. elem - 1 |]) array8
printfn "Array with collected elements:"
printfn "%A" array9

编译并执行程序后,将产生以下输出:

Sub Array:
[|5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19|]
Appended Array:
[|1; 2; 3; 4; 5; 6; 7; 8; 9|]
Array with Chosen elements:
[|3.0; 6.0; 9.0; 12.0; 15.0; 18.0|]
Array with collected elements:
[|0; 1; 0; 1; 2; 0; 1; 2; 3; 0; 1; 2; 3; 4|]

数组搜索

Array.find 函数接受一个布尔函数,并返回该函数返回 true 的第一个元素,否则引发 KeyNotFoundException。

Array.findIndex 函数的工作方式类似,只是它返回元素的索引而不是元素本身。

以下示例演示了这一点。

Microsoft 提供了这个有趣的程序示例,它查找给定数字范围内第一个既是完全平方数又是完全立方数的元素:

let array1 = [| 2 .. 100 |]
let delta = 1.0e-10
let isPerfectSquare (x:int) =
   let y = sqrt (float x)
   abs(y - round y) < delta

let isPerfectCube (x:int) =
   let y = System.Math.Pow(float x, 1.0/3.0)
   abs(y - round y) < delta

let element = Array.find (fun elem -> isPerfectSquare elem && isPerfectCube elem) array1

let index = Array.findIndex (fun elem -> isPerfectSquare elem && isPerfectCube elem) array1

printfn "The first element that is both a square and a cube is %d and its index is %d." element index

编译并执行程序后,将产生以下输出:

The first element that is both a square and a cube is 64 and its index is 62.

F# - 可变列表

List<'T> 类表示可以通过索引访问的对象的强类型列表。

它是 List 类的可变对应物。它类似于数组,因为它可以通过索引访问,但是,与数组不同的是,列表可以调整大小。因此,您无需在声明期间指定大小。

创建可变列表

列表是使用 new 关键字并调用列表的构造函数创建的。以下示例演示了这一点:

(* Creating a List *)
open System.Collections.Generic

let booksList = new List<string>()
booksList.Add("Gone with the Wind")
booksList.Add("Atlas Shrugged")
booksList.Add("Fountainhead")
booksList.Add("Thornbirds")
booksList.Add("Rebecca")
booksList.Add("Narnia")

booksList |> Seq.iteri (fun index item -> printfn "%i: %s" index booksList.[index])

编译并执行程序后,将产生以下输出:

0: Gone with the Wind
1: Atlas Shrugged
2: Fountainhead
3: Thornbirds
4: Rebecca
5: Narnia

List(T) 类

List(T) 类表示可以通过索引访问的对象的强类型列表。它提供用于搜索、排序和操作列表的方法。

下表提供了 List(T) 类的属性、构造函数和方法:

属性

属性 说明
Capacity (容量) 获取或设置内部数据结构在无需调整大小的情况下可以容纳的元素总数。
Count 获取 List(T) 中包含的元素数量。
Item 获取或设置指定索引处的元素。

构造函数

构造函数 说明
List(T)() 初始化一个新的空 List(T) 类实例,并具有默认初始容量。
List(T)(IEnumerable(T)) 初始化一个新的 List(T) 类实例,其中包含从指定集合复制的元素,并且具有足够的容量来容纳复制的元素数量。
List(T)(Int32) 初始化一个新的空 List(T) 类实例,并具有指定的初始容量。

方法

方法 说明
Add 将对象添加到 List(T) 的末尾。
AddRange 将指定集合的元素添加到 List(T) 的末尾。
AsReadOnly 返回当前集合的只读 IList(T) 包装器。
BinarySearch(T) 使用默认比较器搜索整个已排序的 List(T) 中的元素,并返回该元素的基于零的索引。
BinarySearch(T, IComparer(T)) 使用指定的比较器搜索整个已排序的 List(T) 中的元素,并返回该元素的基于零的索引。
BinarySearch(Int32, Int32, T, IComparer(T)) 使用指定的比较器搜索已排序 List(T) 中的元素范围,并返回该元素的基于零的索引。
Clear 删除 List(T) 中的所有元素。
Contains 确定 List(T) 中是否包含某个元素。
ConvertAll(TOutput) 将当前 List(T) 中的元素转换为另一种类型,并返回包含已转换元素的列表。
CopyTo(T[]) 将整个 List(T) 复制到兼容的一维数组中,从目标数组的开头开始。
CopyTo(T[], Int32) 将整个 List(T) 复制到兼容的一维数组中,从目标数组的指定索引开始。
CopyTo(Int32, T[], Int32, Int32) 将 List(T) 中的元素范围复制到兼容的一维数组中,从目标数组的指定索引开始。
Equals(Object) 确定指定的 Object 是否等于当前 Object。(继承自 Object。)
Exists 确定 List(T) 是否包含与指定谓词定义的条件匹配的元素。
Finalize 允许对象在被垃圾回收之前尝试释放资源并执行其他清理操作(继承自 Object)。
Find 搜索与指定谓词定义的条件匹配的元素,并返回整个 List(T) 中的第一次出现。
FindAll 检索与指定谓词定义的条件匹配的所有元素。
FindIndex(Predicate(T)) 搜索与指定谓词定义的条件匹配的元素,并返回整个 List(T) 中第一次出现的基于零的索引。
FindIndex(Int32, Predicate(T)) 搜索与指定谓词定义的条件匹配的元素,并返回 List(T) 中从指定索引扩展到最后一个元素的元素范围内第一次出现的基于零的索引。
FindIndex(Int32, Int32, Predicate(T)) 搜索与指定谓词定义的条件匹配的元素,并返回 List(T) 中从指定索引开始并包含指定数量元素的元素范围内第一次出现的基于零的索引。
FindLast 搜索与指定谓词定义的条件匹配的元素,并返回整个 List(T) 中的最后一次出现。
FindLastIndex(Predicate(T)) 搜索与指定谓词定义的条件匹配的元素,并返回整个 List(T) 中最后一次出现的基于零的索引。
FindLastIndex(Int32, Predicate(T)) 搜索与指定谓词定义的条件匹配的元素,并返回 List(T) 中从第一个元素扩展到指定索引的元素范围内最后一次出现的基于零的索引。
FindLastIndex(Int32, Int32, Predicate(T)) 搜索与指定谓词定义的条件匹配的元素,并返回 List(T) 中包含指定数量元素并以指定索引结尾的元素范围内最后一次出现的基于零的索引。
ForEach 对 List(T) 的每个元素执行指定的动作。
GetEnumerator 返回一个迭代 List(T) 的枚举器。
GetHashCode 用作默认哈希函数。(继承自 Object。)
GetRange 创建源 List(T) 中的元素范围的浅表副本。
GetType 获取当前实例的 Type。(继承自 Object。)
IndexOf(T) 搜索指定的 Object 并返回整个 List(T) 中第一次出现的基于零的索引。
IndexOf(T, Int32) 搜索指定的 Object 并返回 List(T) 中从指定索引扩展到最后一个元素的元素范围内第一次出现的基于零的索引。
IndexOf(T, Int32, Int32) 搜索指定的 Object 并返回 List(T) 中从指定索引开始并包含指定数量元素的元素范围内第一次出现的基于零的索引。
Insert 在 List(T) 的指定索引处插入元素。
InsertRange 在 List(T) 的指定索引处插入集合的元素。
LastIndexOf(T) 搜索指定的 Object 并返回整个 List(T) 中最后一次出现的基于零的索引。
LastIndexOf(T, Int32) 搜索指定的 Object 并返回 List(T) 中从第一个元素扩展到指定索引的元素范围内最后一次出现的基于零的索引。
LastIndexOf(T, Int32, Int32) 搜索指定的 Object 并返回 List(T) 中包含指定数量元素并以指定索引结尾的元素范围内最后一次出现的基于零的索引。
MemberwiseClone 创建当前 Object 的浅表副本。(继承自 Object。)
Remove 从 List(T) 中删除特定对象的第一次出现。
RemoveAll 删除与指定谓词定义的条件匹配的所有元素。
RemoveAt 删除 List(T) 指定索引处的元素。
RemoveRange 从 List(T) 中删除一系列元素。
Reverse() 反转整个 List(T) 中元素的顺序。
Reverse(Int32, Int32) 反转指定范围内的元素顺序。
Sort() 使用默认比较器对整个 List(T) 中的元素进行排序。
Sort(Comparison(T)) 使用指定的 System.Comparison(T) 对整个 List(T) 中的元素进行排序。
Sort(IComparer(T)) 使用指定的比较器对整个 List(T) 中的元素进行排序。
Sort(Int32, Int32, IComparer(T)) 使用指定的比较器对 List(T) 中一系列元素进行排序。
ToArray 将 List(T) 的元素复制到一个新数组中。
ToString 返回表示当前对象的字符串。(继承自 Object。)
TrimExcess 如果该数字小于阈值,则将容量设置为 List(T) 中元素的实际数量。
TrueForAll 确定 List(T) 中的每个元素是否都与指定谓词定义的条件匹配。

示例

(* Creating a List *)
open System.Collections.Generic

let booksList = new List<string>()
booksList.Add("Gone with the Wind")
booksList.Add("Atlas Shrugged")
booksList.Add("Fountainhead")
booksList.Add("Thornbirds")
booksList.Add("Rebecca")
booksList.Add("Narnia")

printfn"Total %d books" booksList.Count
booksList |> Seq.iteri (fun index item -> printfn "%i: %s" index booksList.[index])
booksList.Insert(2, "Roots")

printfn("after inserting at index 2")
printfn"Total %d books" booksList.Count

booksList |> Seq.iteri (fun index item -> printfn "%i: %s" index booksList.[index])
booksList.RemoveAt(3)

printfn("after removing from index 3")
printfn"Total %d books" booksList.Count

booksList |> Seq.iteri (fun index item -> printfn "%i: %s" index booksList.[index])

编译并执行程序后,将产生以下输出:

Total 6 books
0: Gone with the Wind
1: Atlas Shrugged
2: Fountainhead
3: Thornbirds
4: Rebecca
5: Narnia
after inserting at index 2
Total 7 books
0: Gone with the Wind
1: Atlas Shrugged
2: Roots
3: Fountainhead
4: Thornbirds
5: Rebecca
6: Narnia
after removing from index 3
Total 6 books
0: Gone with the Wind
1: Atlas Shrugged
2: Roots
3: Thornbirds
4: Rebecca
5: Narnia

F# - 可变字典

Dictionary<'TKey, 'TValue> 类是 F# map 数据结构的可变模拟,并包含许多相同的函数。

从 F# 中的 Map 章节回顾一下,map 是一种特殊的集合,它将值与键关联。

创建可变字典

可变字典是使用 new 关键字并调用列表的构造函数创建的。以下示例演示了这一点:

open System.Collections.Generic
let dict = new Dictionary<string, string>()
dict.Add("1501", "Zara Ali")
dict.Add("1502","Rishita Gupta")
dict.Add("1503","Robin Sahoo")
dict.Add("1504","Gillian Megan")
printfn "Dictionary - students: %A" dict

编译并执行程序后,将产生以下输出:

Dictionary - students: seq
[[1501, Zara Ali]; [1502, Rishita Gupta]; [1503, Robin Sahoo];
[1504, Gillian Megan]]

Dictionary(TKey,TValue) 类

Dictionary(TKey, TValue) 类表示键和值的集合。

下表提供了 List(T) 类的属性、构造函数和方法:

属性

属性 说明
Comparer (比较器) 获取用于确定字典键相等的 IEqualityComparer(T)。
Count 获取 Dictionary(TKey, TValue) 中包含的键/值对的数量。
Item 获取或设置与指定键关联的值。
Keys (键) 获取包含 Dictionary(TKey, TValue) 中键的集合。
Values (值) 获取包含 Dictionary(TKey, TValue) 中值的集合。

构造函数

构造函数 说明
Dictionary(TKey, TValue)() 初始化一个新的空 Dictionary(TKey, TValue) 类实例,它具有默认初始容量,并使用键类型的默认相等比较器。
Dictionary(TKey, TValue)(IDictionary(TKey, TValue)) 初始化一个新的 Dictionary(TKey, TValue) 类实例,其中包含从指定的 IDictionary(TKey, TValue) 复制的元素,并使用键类型的默认相等比较器。
Dictionary(TKey, TValue)(IEqualityComparer(TKey)) 初始化一个新的 Dictionary(TKey, TValue) 类实例,它为空,具有默认初始容量,并使用指定的 IEqualityComparer(T)。
Dictionary(TKey, TValue)(Int32) 初始化一个新的 Dictionary(TKey, TValue) 类实例,它为空,具有指定的初始容量,并使用键类型的默认相等比较器。
Dictionary(TKey, TValue)(IDictionary(TKey, TValue), IEqualityComparer(TKey)) 初始化一个新的Dictionary(TKey, TValue)类实例,该实例包含从指定的IDictionary(TKey, TValue)复制的元素,并使用指定的IEqualityComparer(T)
Dictionary(TKey, TValue)(Int32, IEqualityComparer(TKey)) 初始化一个新的Dictionary(TKey, TValue)类实例,该实例为空,具有指定的初始容量,并使用指定的IEqualityComparer(T)
Dictionary(TKey, TValue)(SerializationInfo, StreamingContext) 使用序列化数据初始化一个新的Dictionary(TKey, TValue)类实例。

方法

方法 说明
Add 将指定的键和值添加到字典中。
Clear 移除Dictionary(TKey, TValue)中的所有键和值。
ContainsKey 确定Dictionary(TKey, TValue)是否包含指定的键。
ContainsValue 确定Dictionary(TKey, TValue)是否包含特定的值。
Equals(Object) 确定指定的 Object 是否等于当前 Object。(继承自 Object。)
Finalize 允许对象在被垃圾回收之前尝试释放资源并执行其他清理操作。(继承自Object。)
GetEnumerator 返回一个迭代器,用于遍历Dictionary(TKey, TValue)。
GetHashCode 用作默认哈希函数。(继承自 Object。)
GetObjectData 实现System.Runtime.Serialization.ISerializable接口,并返回序列化Dictionary(TKey, TValue)实例所需的数据。
GetType 获取当前实例的 Type。(继承自 Object。)
MemberwiseClone 创建当前 Object 的浅表副本。(继承自 Object。)
OnDeserialization 实现System.Runtime.Serialization.ISerializable接口,并在反序列化完成后引发反序列化事件。
Remove 从Dictionary(TKey, TValue)中移除具有指定键的值。
ToString 返回表示当前对象的字符串。(继承自 Object。)
TryGetValue 获取与指定键关联的值。

示例

open System.Collections.Generic
let dict = new Dictionary<string, string>()

dict.Add("1501", "Zara Ali")
dict.Add("1502","Rishita Gupta")
dict.Add("1503","Robin Sahoo")
dict.Add("1504","Gillian Megan")

printfn "Dictionary - students: %A" dict
printfn "Total Number of Students: %d" dict.Count
printfn "The keys: %A" dict.Keys
printf"The Values: %A" dict.Values

编译并执行程序后,将产生以下输出:

Dictionary - students: seq
[[1501, Zara Ali]; [1502, Rishita Gupta]; [1503, Robin Sahoo];
[1504, Gillian Megan]]
Total Number of Students: 4
The keys: seq ["1501"; "1502"; "1503"; "1504"]
The Values: seq ["Zara Ali"; "Rishita Gupta"; "Robin Sahoo"; "Gillian Megan"]

F# - 基本输入/输出

基本的输入输出包括:

  • 从控制台读取和写入数据。
  • 从文件读取和写入数据。

Core.Printf 模块

我们使用了printfprintfn函数向控制台写入数据。在本节中,我们将详细了解F#的Printf模块。

除了上述函数外,F#的Core.Printf模块还提供其他多种方法,可以使用%标记作为占位符进行打印和格式化。下表显示了这些方法及其简要说明:

说明
bprintf : StringBuilder → BuilderFormat<'T> → 'T 打印到StringBuilder。
eprintf : TextWriterFormat<'T> → 'T 将格式化后的输出打印到stderr。
eprintfn : TextWriterFormat<'T> → 'T 将格式化后的输出打印到stderr,并添加换行符。
failwithf : StringFormat<'T,'Result> → 'T 打印到字符串缓冲区,并引发带有给定结果的异常。
fprintf : TextWriter → TextWriterFormat<'T> → 'T 打印到文本写入器。
fprintfn : TextWriter → TextWriterFormat<'T> → 'T 打印到文本写入器,并添加换行符。
kbprintf : (unit → 'Result) → StringBuilder → BuilderFormat<'T,'Result> → 'T 类似于bprintf,但调用指定的函数生成结果。
kfprintf : (unit → 'Result) → TextWriter → TextWriterFormat<'T,'Result> → 'T 类似于fprintf,但调用指定的函数生成结果。
kprintf : (string → 'Result) → StringFormat<'T,'Result> → 'T 类似于printf,但调用指定的函数生成结果。例如,这些允许打印在所有输出都已输入通道后强制刷新,但在此之前不会。
ksprintf : (string → 'Result) → StringFormat<'T,'Result> → 'T 类似于sprintf,但调用指定的函数生成结果。
printf : TextWriterFormat<'T> → 'T 将格式化后的输出打印到stdout。
printfn : TextWriterFormat<'T> → 'T 将格式化后的输出打印到stdout,并添加换行符。
sprintf : StringFormat<'T> → 'T 使用内部字符串缓冲区打印到字符串,并将结果作为字符串返回。

格式说明符

格式说明符用于根据程序员的需求格式化输入或输出。

这些是带有%标记的字符串,表示格式占位符。

格式占位符的语法为:

%[flags][width][.precision][type]

类型的解释如下:

类型 说明
%b 格式化一个bool值,格式化为truefalse
%c 格式化一个字符。
%s 格式化一个string值,格式化为其内容,不解释任何转义字符。
%d, %i 格式化任何基本整数类型,格式化为十进制整数,如果基本整数类型为有符号类型,则为有符号整数。
%u 格式化任何基本整数类型,格式化为无符号十进制整数。
%x 格式化任何基本整数类型,格式化为无符号十六进制整数,使用小写字母a到f。
%X 格式化任何基本整数类型,格式化为无符号十六进制整数,使用大写字母A到F。
%o 格式化任何基本整数类型,格式化为无符号八进制整数。
%e, %E, %f, %F, %g, %G 格式化任何基本浮点类型(float, float32),使用C风格的浮点格式说明符进行格式化。
%e, %E 格式化一个有符号值,其形式为[-]d.dddde[sign]ddd,其中d是一个十进制数字,dddd是一个或多个十进制数字,ddd正好是三个十进制数字,sign是+或-。
%f 格式化一个有符号值,其形式为[-]dddd.dddd,其中dddd是一个或多个十进制数字。小数点前的数字个数取决于数字的大小,小数点后的数字个数取决于请求的精度。
%g, %G 格式化一个有符号值,以f或e格式打印,对于给定的值和精度,哪种格式更紧凑就使用哪种格式。
%M 格式化一个Decimal值。
%O 格式化任何值,通过装箱对象并使用其ToString方法进行打印。
%A, %+A 格式化任何值,使用默认布局设置进行打印。使用%+A打印具有内部和私有表示的辨别联合体的结构。
%a

一个通用格式说明符,需要两个参数。第一个参数是一个函数,它接受两个参数:第一个是给定格式化函数的适当类型的上下文参数(例如,TextWriter),第二个是要打印的值,并且该值要么输出要么返回适当的文本。

第二个参数是要打印的特定值。

%t 一个通用格式说明符,需要一个参数:一个函数,它接受给定格式化函数的适当类型的上下文参数(aTextWriter),并且该函数要么输出要么返回适当的文本。基本整数类型为byte, sbyte, int16, uint16, int32, uint32, int64, uint64, nativeint,unativeint。基本浮点类型为floatfloat32。

宽度是一个可选参数。它是一个整数,表示结果的最小宽度。例如,%5d打印一个至少有5个字符空格的整数。

有效的标志在下表中描述:

说明
0 指定添加零而不是空格以构成所需的宽度。
- 指定在指定的宽度内左对齐结果。
+ 如果数字为正,则指定添加+字符(以匹配负数的-号)。
' ' (空格) 如果数字为正,则指定添加一个额外的空格(以匹配负数的-号)。
# 无效。

示例

printf "Hello "
printf "World"
printfn ""
printfn "Hello "
printfn "World"
printf "Hi, I'm %s and I'm a %s" "Rohit" "Medical Student"

printfn "d: %f" 212.098f
printfn "e: %f" 504.768f

printfn "x: %g" 212.098f
printfn "y: %g" 504.768f

printfn "x: %e" 212.098f
printfn "y: %e" 504.768f
printfn "True: %b" true

编译并执行程序后,将产生以下输出:

Hello World
Hello
World
Hi, I'm Rohit and I'm a Medical Studentd: 212.098000
e: 504.768000
x: 212.098
y: 504.768
x: 2.120980e+002
y: 5.047680e+002
True: true

Console 类

此类是.NET框架的一部分。它表示控制台应用程序的标准输入、输出和错误流。

它提供各种方法用于从控制台读取和写入数据。下表显示了这些方法:

方法 说明
Beep() 通过控制台扬声器播放蜂鸣声。
Beep(Int32, Int32) 通过控制台扬声器播放指定频率和持续时间的蜂鸣声。
Clear 清除控制台缓冲区和相应的控制台窗口显示信息。
MoveBufferArea(Int32, Int32, Int32, Int32, Int32, Int32) 将屏幕缓冲区的指定源区域复制到指定的目标区域。
MoveBufferArea(Int32, Int32, Int32, Int32, Int32, Int32, Char, ConsoleColor, ConsoleColor) 将屏幕缓冲区的指定源区域复制到指定的目标区域。
OpenStandardError() 获取标准错误流。
OpenStandardError(Int32) 获取标准错误流,该流设置为指定的缓冲区大小。
OpenStandardInput() 获取标准输入流。
OpenStandardInput(Int32) 获取标准输入流,该流设置为指定的缓冲区大小。
OpenStandardOutput() 获取标准输出流。
OpenStandardOutput(Int32) 获取标准输出流,该流设置为指定的缓冲区大小。
Read 从标准输入流读取下一个字符。
ReadKey() 获取用户按下的下一个字符或功能键。按下的键将显示在控制台窗口中。
ReadKey(Boolean) 获取用户按下的下一个字符或功能键。按下的键可以选择性地在控制台窗口中显示。
ReadLine 从标准输入流读取下一行字符。
ResetColor 将前景色和背景色控制台颜色设置为其默认值。
SetBufferSize 将屏幕缓冲区区域的高度和宽度设置为指定的值。
SetCursorPosition 设置光标的位置。
SetError 将Error属性设置为指定的TextWriter对象。
SetIn 将In属性设置为指定的TextReader对象。
SetOut 将Out属性设置为指定的TextWriter对象。
SetWindowPosition 设置控制台窗口相对于屏幕缓冲区的位置。
SetWindowSize 将控制台窗口的高度和宽度设置为指定的值。
Write(Boolean) 将指定布尔值的文本表示形式写入标准输出流。
Write(Char) 将指定的Unicode字符值写入标准输出流。
Write(Char[]) 将指定的Unicode字符数组写入标准输出流。
Write(Decimal) 将指定Decimal值的文本表示形式写入标准输出流。
Write(Double) 将指定双精度浮点值的文本表示形式写入标准输出流。
Write(Int32) 将指定32位有符号整数值的文本表示形式写入标准输出流。
Write(Int64) 将指定64位有符号整数值的文本表示形式写入标准输出流。
Write(Object) 将指定对象的文本表示形式写入标准输出流。
Write(Single) 将指定单精度浮点值的文本表示形式写入标准输出流。
Write(String) 将指定的字符串值写入标准输出流。
Write(UInt32) 将指定32位无符号整数值的文本表示形式写入标准输出流。
Write(UInt64) 将指定64位无符号整数值的文本表示形式写入标准输出流。
Write(String, Object) 使用指定的格式信息将指定对象的文本表示形式写入标准输出流。
Write(String, Object[]) 使用指定的格式信息将指定对象数组的文本表示形式写入标准输出流。
Write(Char[], Int32, Int32) 将指定的 Unicode 字符子数组写入标准输出流。
Write(String, Object, Object) 使用指定的格式信息将指定对象的文本表示形式写入标准输出流。
Write(String, Object, Object, Object) 使用指定的格式信息将指定对象的文本表示形式写入标准输出流。
Write(String, Object, Object, Object, Object) 使用指定的格式信息将指定的对象和可变长度参数列表的文本表示形式写入标准输出流。
WriteLine() 将当前行终止符写入标准输出流。
WriteLine(Boolean) 将指定布尔值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(Char) 将指定的 Unicode 字符及其后的当前行终止符写入标准输出流。
WriteLine(Char[]) 将指定的 Unicode 字符数组及其后的当前行终止符写入标准输出流。
WriteLine(Decimal) 将指定 Decimal 值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(Double) 将指定的双精度浮点值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(Int32) 将指定的 32 位有符号整数值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(Int64) 将指定的 64 位有符号整数值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(Object) 将指定对象的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(Single) 将指定的单精度浮点值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(String) 将指定的字符串值及其后的当前行终止符写入标准输出流。
WriteLine(UInt32) 将指定的 32 位无符号整数值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(UInt64) 将指定的 64 位无符号整数值的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(String, Object) 使用指定的格式信息将指定对象的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(String, Object[]) 使用指定的格式信息将指定对象数组的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(Char[], Int32, Int32) 将指定的 Unicode 字符子数组及其后的当前行终止符写入标准输出流。
WriteLine(String, Object, Object) 使用指定的格式信息将指定对象的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(String, Object, Object, Object) 使用指定的格式信息将指定对象的文本表示形式及其后的当前行终止符写入标准输出流。
WriteLine(String, Object, Object, Object, Object) 使用指定的格式信息将指定的对象和可变长度参数列表的文本表示形式及其后的当前行终止符写入标准输出流。

以下示例演示了从控制台读取和写入数据:

示例

open System
let main() =
   Console.Write("What's your name? ")
   let name = Console.ReadLine()
   Console.Write("Hello, {0}\n", name)
   Console.WriteLine(System.String.Format("Big Greetings from {0} and {1}", "TutorialsPoint", "Absoulte Classes"))
   Console.WriteLine(System.String.Format("|{0:yyyy-MMM-dd}|", System.DateTime.Now))
main()

编译并执行程序后,将产生以下输出:

What's your name? Kabir
Hello, Kabir
Big Greetings from TutorialsPoint and Absoulte Classes
|2015-Jan-05|

System.IO 命名空间

System.IO 命名空间包含各种用于执行基本 I/O 操作的有用类。

它包含允许读取和写入文件和数据流的类型或类,以及提供基本文件和目录支持的类型。

用于处理文件系统的有用类:

  • System.IO.File 类用于创建、追加和删除文件。
  • System.IO.Directory 类用于创建、移动和删除目录。
  • System.IO.Path 类对表示文件路径的字符串执行操作。
  • System.IO.FileSystemWatcher 类允许用户监听目录的更改。

用于处理流(字节序列)的有用类:

  • System.IO.StreamReader 类用于从流中读取字符。
  • System.IO.StreamWriter 类用于向流中写入字符。
  • System.IO.MemoryStream 类创建一个内存中的字节流。

下表显示了命名空间中提供的所有类及其简要说明:

说明
BinaryReader 以二进制值的形式读取特定编码中的原始数据类型。
BinaryWriter 以二进制形式将原始类型写入流,并支持以特定编码写入字符串。
BufferedStream 为另一个流上的读取和写入操作添加缓冲层。
Directory 公开用于创建、移动和枚举目录和子目录的静态方法。
DirectoryInfo 公开用于创建、移动和枚举目录和子目录的实例方法。
DirectoryNotFoundException 当找不到文件或目录的一部分时引发的异常。
DriveInfo 提供对驱动器信息的访问。
DriveNotFoundException 尝试访问不可用的驱动器或共享时引发的异常。
EndOfStreamException 尝试读取超过流末尾时引发的异常。
ErrorEventArgs 为 FileSystemWatcher.Error 事件提供数据。
File 提供用于创建、复制、删除、移动和打开单个文件的静态方法,并帮助创建 FileStream 对象。
FileFormatException 当输入文件或应符合特定文件格式规范的数据流格式错误时引发的异常。
FileInfo 提供用于创建、复制、删除、移动和打开文件的属性和实例方法,并帮助创建 FileStream 对象。
FileLoadException 当找到托管程序集但无法加载时引发的异常。
FileNotFoundException 当尝试访问磁盘上不存在的文件失败时引发的异常。
FileStream 公开围绕文件的流,支持同步和异步读取和写入操作。
FileSystemEventArgs 为目录事件(已更改、已创建、已删除)提供数据。
FileSystemInfo 为 FileInfo 和 DirectoryInfo 对象提供基类。
FileSystemWatcher 侦听文件系统更改通知,并在目录或目录中的文件更改时引发事件。
InternalBufferOverflowException 内部缓冲区溢出时引发的异常。
InvalidDataException 当数据流格式无效时引发的异常。
IODescriptionAttribute 设置引用事件、扩展程序或属性时视觉设计器可以显示的描述。
IOException 发生 I/O 错误时引发的异常。
MemoryStream 创建一个其后备存储为内存的流。
Path 对包含文件或目录路径信息的 String 实例执行操作。这些操作以跨平台的方式执行。
PathTooLongException 当路径或文件名长于系统定义的最大长度时引发的异常。
PipeException 命名管道中发生错误时引发。
RenamedEventArgs 为 Renamed 事件提供数据。
Stream 提供字节序列的通用视图。这是一个抽象类。
StreamReader 实现一个 TextReader,它以特定编码从字节流中读取字符。
StreamWriter 实现一个 TextWriter,用于以特定编码向流中写入字符。要浏览此类型的 .NET Framework 源代码,请参阅参考源。
StringReader 实现一个从字符串中读取的 TextReader。
StringWriter 实现一个 TextWriter,用于将信息写入字符串。信息存储在底层 StringBuilder 中。
TextReader 表示可以读取一系列连续字符的读取器。
TextWriter 表示可以写入一系列连续字符的写入器。此类是抽象的。
UnmanagedMemoryAccessor 提供对托管代码中非托管内存块的随机访问。
UnmanagedMemoryStream 提供对托管代码中非托管内存块的访问。
WindowsRuntimeStorageExtensions 在开发 Windows 应用商店应用时,包含 Windows 运行时中 IStorageFile 和 IStorageFolder 接口的扩展方法。
WindowsRuntimeStreamExtensions 包含用于在 Windows 运行时中的流和 .NET 中的托管流之间进行转换的扩展方法,用于 Windows 应用商店应用。

示例

以下示例创建一个名为 test.txt 的文件,向其中写入消息,从文件读取文本并将其打印到控制台。

注意 - 完成此操作所需的代码量出乎意料地少!

open System.IO // Name spaces can be opened just as modules
File.WriteAllText("test.txt", "Hello There\n Welcome to:\n Tutorials Point")
let msg = File.ReadAllText("test.txt")
printfn "%s" msg

编译并执行程序后,将产生以下输出:

Hello There
Welcome to:
Tutorials Point

F# - 泛型

泛型允许您延迟指定类或方法中编程元素的数据类型,直到它实际在程序中使用。换句话说,泛型允许您编写可以处理任何数据类型的类或方法。

您编写类或方法的规范,并使用替代参数表示数据类型。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理特定的数据类型。

在 F# 中,函数值、方法、属性和聚合类型(例如类、记录和辨别联合)都可以是泛型的。

泛型构造至少包含一个类型参数。泛型函数和类型使您可以编写适用于各种类型的代码,而无需为每种类型重复代码。

语法

编写泛型构造的语法如下:

// Explicitly generic function.
let function-name<type-parameters> parameter-list =
   function-body

// Explicitly generic method.
[ static ] member object-identifer.method-name<type-parameters> parameter-list [ return-type ] =
   method-body

// Explicitly generic class, record, interface, structure,
// or discriminated union.
type type-name<type-parameters> type-definition

示例

(* Generic Function *)
let printFunc<'T> x y =
   printfn "%A, %A" x y

printFunc<float> 10.0 20.0

编译并执行程序后,将产生以下输出:

10.0, 20.0

您还可以使用单引号语法使函数泛型:

(* Generic Function *)
let printFunction (x: 'a) (y: 'a) =
   printfn "%A %A" x y

printFunction 10.0 20.0

编译并执行程序后,将产生以下输出:

10.0 20.0

请注意,当您使用泛型函数或方法时,您可能不必指定类型参数。但是,如果出现歧义,您可以像在第一个示例中那样在尖括号中提供类型参数。

如果您有多个类型,则用逗号分隔多个类型参数。

泛型类

像泛型函数一样,您也可以编写泛型类。以下示例演示了这一点:

type genericClass<'a> (x: 'a) =
   do printfn "%A" x

let gr = new genericClass<string>("zara")
let gs = genericClass( seq { for i in 1 .. 10 -> (i, i*i) } )

编译并执行程序后,将产生以下输出:

"zara"
seq [(1, 1); (2, 4); (3, 9); (4, 16); ...]

F# - 委托

委托是一个引用类型变量,它保存对方法的引用。可以在运行时更改引用。F# 委托类似于 C 或 C++ 中的函数指针。

声明委托

委托声明确定委托可以引用的方法。委托可以引用与委托签名相同的方法。

委托声明的语法如下:

type delegate-typename = delegate of type1 -> type2

例如,考虑以下委托:

// Delegate1 works with tuple arguments.
type Delegate1 = delegate of (int * int) -> int
// Delegate2 works with curried arguments.
type Delegate2 = delegate of int * int -> int

这两个委托都可以用来引用任何具有两个int参数并返回int类型变量的方法。

在语法中:

  • type1表示参数类型。

  • type2表示返回类型。

请注意:

  • 参数类型会自动柯里化。

  • 委托可以附加到函数值以及静态或实例方法。

  • F# 函数值可以直接作为参数传递给委托构造函数。

  • 对于静态方法,通过使用类名和方法名来调用委托。对于实例方法,则使用对象实例名和方法名。

  • 委托类型的 Invoke 方法调用封装的函数。

  • 此外,可以通过引用 Invoke 方法名(不带括号)将委托作为函数值传递。

以下示例演示了这个概念:

示例

type Myclass() =
   static member add(a : int, b : int) =
      a + b
   static member sub (a : int) (b : int) =
      a - b
   member x.Add(a : int, b : int) =
      a + b
   member x.Sub(a : int) (b : int) =
      a - b

// Delegate1 works with tuple arguments.
type Delegate1 = delegate of (int * int) -> int
// Delegate2 works with curried arguments.
type Delegate2 = delegate of int * int -> int

let InvokeDelegate1 (dlg : Delegate1) (a : int) (b: int) =
   dlg.Invoke(a, b)
let InvokeDelegate2 (dlg : Delegate2) (a : int) (b: int) =
   dlg.Invoke(a, b)

// For static methods, use the class name, the dot operator, and the
// name of the static method.
let del1 : Delegate1 = new Delegate1( Myclass.add )
let del2 : Delegate2 = new Delegate2( Myclass.sub )

let mc = Myclass()
// For instance methods, use the instance value name, the dot operator, and the instance method name.

let del3 : Delegate1 = new Delegate1( mc.Add )
let del4 : Delegate2 = new Delegate2( mc.Sub )

for (a, b) in [ (400, 200); (100, 45) ] do
   printfn "%d + %d = %d" a b (InvokeDelegate1 del1 a b)
   printfn "%d - %d = %d" a b (InvokeDelegate2 del2 a b)
   printfn "%d + %d = %d" a b (InvokeDelegate1 del3 a b)
   printfn "%d - %d = %d" a b (InvokeDelegate2 del4 a b)

编译并执行程序后,将产生以下输出:

400 + 200 = 600
400 - 200 = 200
400 + 200 = 600
400 - 200 = 200
100 + 45 = 145
100 - 45 = 55
100 + 45 = 145
100 - 45 = 55

F# - 枚举

枚举是一组命名的整型常量。

在 F# 中,枚举(也称为枚举)是整型类型,其中标签分配给值的子集。您可以使用它们代替字面量,使代码更易读且更易维护。

声明枚举

声明枚举的通用语法如下:

type enum-name =
   | value1 = integer-literal1
   | value2 = integer-literal2
...

以下示例演示了枚举的使用:

示例

// Declaration of an enumeration.
type Days =
   | Sun = 0
   | Mon = 1
   | Tues = 2
   | Wed = 3
   | Thurs = 4
   | Fri = 5
   | Sat = 6

// Use of an enumeration.
let weekend1 : Days = Days.Sat
let weekend2 : Days = Days.Sun
let weekDay1 : Days = Days.Mon

printfn "Monday: %A" weekDay1
printfn "Saturday: %A" weekend1
printfn "Sunday: %A" weekend2

编译并执行程序后,将产生以下输出:

Monday: Mon
Saturday: Sat
Sunday: Sun

F# - 模式匹配

模式匹配允许您“将数据与逻辑结构或结构进行比较,将数据分解成组成部分,或以各种方式从数据中提取信息”。

换句话说,它提供了一种更灵活、更强大的方法来测试数据是否符合一系列条件,并根据满足的条件执行一些计算。

从概念上讲,它就像一系列 if… then 语句。

语法

从高级层面来看,F# 中的模式匹配遵循以下语法:

match expr with
| pat1 - result1
| pat2 -> result2
| pat3 when expr2 -> result3
| _ -> defaultResult

其中:

  • 每个 | 符号定义一个条件。
  • -> 符号表示“如果条件为真,则返回此值…”。
  • _ 符号提供默认模式,这意味着它像通配符一样匹配所有其他内容。

示例 1

以下示例使用模式匹配语法计算斐波那契数:

let rec fib n =
   match n with
   | 0 -> 0
   | 1 -> 1
   | _ -> fib (n - 1) + fib (n - 2)
for i = 1 to 10 do
   printfn "Fibonacci %d: %d" i (fib i)

编译并执行程序后,将产生以下输出:

Fibonacci 1: 1
Fibonacci 2: 1
Fibonacci 3: 2
Fibonacci 4: 3
Fibonacci 5: 5
Fibonacci 6: 8
Fibonacci 7: 13
Fibonacci 8: 21
Fibonacci 9: 34
Fibonacci 10: 55

您还可以将多个返回相同值的条件链接在一起。例如:

示例 2

let printSeason month =
   match month with
   | "December" | "January" | "February" -> printfn "Winter"
   | "March" | "April" -> printfn "Spring"
   | "May" | "June" -> printfn "Summer"
   | "July" | "August" -> printfn "Rainy"
   | "September" | "October" | "November" -> printfn "Autumn"
   | _ -> printfn "Season depends on month!"

printSeason "February"
printSeason "April"
printSeason "November"
printSeason "July"

编译并执行程序后,将产生以下输出:

Winter
Spring
Autumn
Rainy

模式匹配函数

F# 允许您使用function关键字编写模式匹配函数:

let getRate = function
   | "potato" -> 10.00
   | "brinjal" -> 20.50
   | "cauliflower" -> 21.00
   | "cabbage" -> 8.75
   | "carrot" -> 15.00
   | _ -> nan (* nan is a special value meaning "not a number" *)

printfn "%g"(getRate "potato")
printfn "%g"(getRate "brinjal")
printfn "%g"(getRate "cauliflower")
printfn "%g"(getRate "cabbage")
printfn "%g"(getRate "carrot")

编译并执行程序后,将产生以下输出:

10
20.5
21
8.75
15

向模式添加过滤器或保护

您可以使用when关键字向模式添加过滤器或保护。

示例 1

let sign = function
   | 0 -> 0
   | x when x < 0 -> -1
   | x when x > 0 -> 1

printfn "%d" (sign -20)
printfn "%d" (sign 20)
printfn "%d" (sign 0)

编译并执行程序后,将产生以下输出:

-1
1
0

示例 2

let compareInt x =
   match x with
   | (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
   | (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
   | (var1, var2) -> printfn "%d equals %d" var1 var2

compareInt (11,25)
compareInt (72, 10)
compareInt (0, 0)

编译并执行程序后,将产生以下输出:

11 is less than 25
72 is greater than 10
0 equals 0

使用元组进行模式匹配

以下示例演示了使用元组进行模式匹配:

let greeting (name, subject) =
   match (name, subject) with
   | ("Zara", _) -> "Hello, Zara"
   | (name, "English") -> "Hello, " + name + " from the department of English"
   | (name, _) when subject.StartsWith("Comp") -> "Hello, " + name + " from the department of Computer Sc."
   | (_, "Accounts and Finance") -> "Welcome to the department of Accounts and Finance!"
   | _ -> "You are not registered into the system"

printfn "%s" (greeting ("Zara", "English"))
printfn "%s" (greeting ("Raman", "Computer Science"))
printfn "%s" (greeting ("Ravi", "Mathematics"))

编译并执行程序后,将产生以下输出:

Hello, Zara
Hello, Raman from the department of Computer Sc.
You are not registered into the system

使用记录进行模式匹配

以下示例演示了使用记录进行模式匹配:

type Point = { x: float; y: float }
let evaluatePoint (point: Point) =
   match point with
   | { x = 0.0; y = 0.0 } -> printfn "Point is at the origin."
   | { x = xVal; y = 0.0 } -> printfn "Point is on the x-axis. Value is %f." xVal
   | { x = 0.0; y = yVal } -> printfn "Point is on the y-axis. Value is %f." yVal
   | { x = xVal; y = yVal } -> printfn "Point is at (%f, %f)." xVal yVal

evaluatePoint { x = 0.0; y = 0.0 }
evaluatePoint { x = 10.0; y = 0.0 }
evaluatePoint { x = 0.0; y = 10.0 }
evaluatePoint { x = 10.0; y = 10.0 }

编译并执行程序后,将产生以下输出:

Point is at the origin.
Point is on the x-axis. Value is 10.000000.
Point is on the y-axis. Value is 10.000000.
Point is at (10.000000, 10.000000).

F# - 异常处理

异常是在程序执行期间出现的错误。F# 异常是对程序运行期间出现的异常情况的响应,例如尝试除以零。

异常提供了一种将控制权从程序的一个部分转移到另一个部分的方法。F# 异常处理提供以下结构:

结构 说明
raise expr 引发给定的异常。
failwith expr 引发System.Exception异常。
try expr with rules 捕获与模式规则匹配的表达式。
try expr finally expr 在计算成功和引发异常时都执行finally表达式。
| :? ArgumentException 与给定的 .NET 异常类型匹配的规则。
| :? ArgumentException as e 与给定的 .NET 异常类型匹配的规则,将名称e绑定到异常对象值。
| Failure(msg) → expr 与给定的携带数据的 F# 异常匹配的规则。
| exn → expr 与任何异常匹配的规则,将名称exn绑定到异常对象值。
| exn when expr → expr 在给定条件下与异常匹配的规则,将名称exn绑定到异常对象值。

让我们从异常处理的基本语法开始。

语法

F# 异常处理块的基本语法如下:

exception exception-type of argument-type

其中:

  • exception-type是新的 F# 异常类型的名称。

  • argument-type表示在引发此类型的异常时可以提供的参数的类型。

  • 可以通过使用元组类型作为argument-type来指定多个参数。

try...with表达式用于F#语言中的异常处理。

try … with表达式的语法如下:

try
   expression1
with
   | pattern1 -> expression2
   | pattern2 -> expression3
...

try...finally表达式允许您即使代码块引发异常也能执行清理代码。

try … finally表达式的语法如下:

try
   expression1
finally
   expression2

raise函数用于指示已发生错误或异常情况。它还会在异常对象中捕获有关错误的信息。

raise函数的语法如下:

raise (expression)

failwith函数生成 F# 异常。

failwith函数的语法如下:

failwith error-message-string

invalidArg函数生成参数异常。

invalidArg parameter-name error-message-string

异常处理示例

示例 1

以下程序显示了使用简单的 try… with 块进行基本异常处理:

let divisionprog x y =
   try
      Some (x / y)
   with
      | :? System.DivideByZeroException -> printfn "Division by zero!"; None

let result1 = divisionprog 100 0

编译并执行程序后,将产生以下输出:

Division by zero!

示例 2

F# 提供了一个exception类型来声明异常。您可以直接在try...with表达式中的过滤器中使用异常类型。

以下示例演示了这一点:

exception Error1 of string
// Using a tuple type as the argument type.
exception Error2 of string * int

let myfunction x y =
   try
      if x = y then raise (Error1("Equal Number Error"))
      else raise (Error2("Error Not detected", 100))
   with
      | Error1(str) -> printfn "Error1 %s" str
      | Error2(str, i) -> printfn "Error2 %s %d" str i
myfunction 20 10
myfunction 5 5

编译并执行程序后,将产生以下输出:

Error2 Error Not detected 100
Error1 Equal Number Error

示例 3

以下示例演示了嵌套异常处理:

exception InnerError of string
exception OuterError of string

let func1 x y =
   try
      try
         if x = y then raise (InnerError("inner error"))
         else raise (OuterError("outer error"))
      with
         | InnerError(str) -> printfn "Error:%s" str
   finally
      printfn "From the finally block."

let func2 x y =
   try
      func1 x y
   with
      | OuterError(str) -> printfn "Error: %s" str

func2 100 150
func2 100 100
func2 100 120

编译并执行程序后,将产生以下输出:

From the finally block.
Error: outer error
Error:inner error
From the finally block.
From the finally block.
Error: outer error

示例 4

以下函数演示了failwith函数:

let divisionFunc x y =
   if (y = 0) then failwith "Divisor cannot be zero."
   else
      x / y

let trydivisionFunc x y =
   try
      divisionFunc x y
   with
      | Failure(msg) -> printfn "%s" msg; 0

let result1 = trydivisionFunc 100 0
let result2 = trydivisionFunc 100 4
printfn "%A" result1
printfn "%A" result2

编译并执行程序后,将产生以下输出:

Divisor cannot be zero.
0
25

示例 5

invalidArg函数生成参数异常。以下程序演示了这一点:

let days = [| "Sunday"; "Monday"; "Tuesday"; "Wednesday"; "Thursday"; "Friday"; "Saturday" |]
let findDay day =
   if (day > 7 || day < 1)
      then invalidArg "day" (sprintf "You have entered %d." day)
   days.[day - 1]

printfn "%s" (findDay 1)
printfn "%s" (findDay 5)
printfn "%s" (findDay 9)

编译并执行程序后,将产生以下输出:

Sunday
Thursday
Unhandled Exception:
System.ArgumentException: You have entered 9.
…

根据系统不同,还会显示有关导致系统错误的文件和变量的其他信息。

F# - 类

类是表示可以具有属性、方法和事件的对象的类型。“它们用于对应用程序中的动作、流程和任何概念实体进行建模”。

语法

定义类类型的语法如下:

// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
   [ class ]
      [ inherit base-type-name(base-constructor-args) ]
      [ let-bindings ]
      [ do-bindings ]
      member-list
      ...
   [ end ]

// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...

其中:

  • type-name是任何有效的标识符。默认访问修饰符为public

  • type-params描述可选的泛型类型参数。

  • parameter-list描述构造函数参数。主构造函数的默认访问修饰符为public

  • 带可选as关键字的标识符为实例变量或self-identifier命名,它可以在类型定义中用于引用类型的实例。

  • inherit关键字允许您指定类的基类。

  • let绑定允许您声明对类局部字段或函数值。

  • do-bindings部分包括在对象构造时要执行的代码。

  • member-list由附加的构造函数、实例和静态方法声明、接口声明、抽象绑定以及属性和事件声明组成。

  • 标记定义开始和结束的关键字classend是可选的。

类的构造函数

构造函数是创建类类型实例的代码。

在 F# 中,构造函数的工作方式与其他 .Net 语言略有不同。在类定义中,主构造函数的参数被描述为 parameter-list。

构造函数的主体由letdo绑定组成。

您可以使用 new 关键字添加成员来添加其他构造函数:

new (argument-list) = constructor-body

以下示例说明了这个概念:

示例

以下程序创建了一个线类以及一个构造函数,该构造函数在创建类的对象时计算线的长度:

type Line = class
   val X1 : float
   val Y1 : float
   val X2 : float
   val Y2 : float

   new (x1, y1, x2, y2) as this =
      { X1 = x1; Y1 = y1; X2 = x2; Y2 = y2;}
      then
         printfn " Creating Line: {(%g, %g), (%g, %g)}\nLength: %g"
            this.X1 this.Y1 this.X2 this.Y2 this.Length

   member x.Length =
      let sqr x = x * x
      sqrt(sqr(x.X1 - x.X2) + sqr(x.Y1 - x.Y2) )
end
let aLine = new Line(1.0, 1.0, 4.0, 5.0)

编译并执行程序后,将产生以下输出:

Creating Line: {(1, 1), (4, 5)}
Length: 5

Let 绑定

类定义中的 let 绑定允许您为 F# 类定义私有字段和私有函数。

type Greetings(name) as gr =
   let data = name
   do
      gr.PrintMessage()
   member this.PrintMessage() =
      printf "Hello %s\n" data
let gtr = new Greetings("Zara")

编译并执行程序后,将产生以下输出:

Hello Zara

请注意对 Greetings 类的 self-identifier gr 的使用。

F# - 结构体

F# 中的结构是一种值类型数据类型。它可以帮助您使单个变量保存各种数据类型的相关数据。struct关键字用于创建结构。

语法

定义结构的语法如下:

[ attributes ]
type [accessibility-modifier] type-name =
   struct
      type-definition-elements
   end
// or
[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
   type-definition-elements

有两种语法。第一种语法最常用,因为如果您使用structend关键字,则可以省略StructAttribute属性。

结构定义元素提供:

  • 成员声明和定义。
  • 构造函数以及可变和不可变字段。
  • 成员和接口实现。

与类不同,结构不能被继承,也不能包含 let 或 do 绑定。由于结构没有 let 绑定;您必须使用val关键字在结构中声明字段。

当您使用val关键字定义字段及其类型时,无法初始化字段值,它们将初始化为零或空。因此,对于具有隐式构造函数的结构,val声明需要使用DefaultValue属性进行注释。

示例

下面的程序创建一个线结构以及一个构造函数。程序使用该结构计算线的长度:

type Line = struct
   val X1 : float
   val Y1 : float
   val X2 : float
   val Y2 : float

   new (x1, y1, x2, y2) =
      {X1 = x1; Y1 = y1; X2 = x2; Y2 = y2;}
end
let calcLength(a : Line)=
   let sqr a = a * a
   sqrt(sqr(a.X1 - a.X2) + sqr(a.Y1 - a.Y2) )

let aLine = new Line(1.0, 1.0, 4.0, 5.0)
let length = calcLength aLine
printfn "Length of the Line: %g " length

编译并执行程序后,将产生以下输出:

Length of the Line: 5

F# - 运算符重载

您可以重新定义或重载F#中提供的大多数内置运算符。因此,程序员也可以将运算符与用户定义类型一起使用。

运算符是用括号括起来的特殊名称的函数。它们必须定义为静态类成员。与任何其他函数一样,重载运算符具有返回类型和参数列表。

以下示例显示了复数上的 + 运算符:

//overloading + operator
static member (+) (a : Complex, b: Complex) =
Complex(a.x + b.x, a.y + b.y)

上述函数实现了用户定义类Complex的加法运算符(+)。它添加两个对象的属性并返回结果Complex对象。

运算符重载的实现

下面的程序显示了完整的实现:

//implementing a complex class with +, and - operators
//overloaded
type Complex(x: float, y : float) =
   member this.x = x
   member this.y = y
   //overloading + operator
   static member (+) (a : Complex, b: Complex) =
      Complex(a.x + b.x, a.y + b.y)

   //overloading - operator
   static member (-) (a : Complex, b: Complex) =
      Complex(a.x - b.x, a.y - b.y)

   // overriding the ToString method
   override this.ToString() =
      this.x.ToString() + " " + this.y.ToString()

//Creating two complex numbers
let c1 = Complex(7.0, 5.0)
let c2 = Complex(4.2, 3.1)

// addition and subtraction using the
//overloaded operators
let c3 = c1 + c2
let c4 = c1 - c2

//printing the complex numbers
printfn "%s" (c1.ToString())
printfn "%s" (c2.ToString())
printfn "%s" (c3.ToString())
printfn "%s" (c4.ToString())

编译并执行程序后,将产生以下输出:

7 5
4.2 3.1
11.2 8.1
2.8 1.9

F# - 继承

面向对象编程中最重要概念之一是继承。继承允许我们根据另一个类来定义一个类,这使得创建和维护应用程序更容易。这也提供了重用代码功能和加快实现时间的机会。

创建类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的数据成员和成员函数。这个现有类称为基类,新类称为派生类。

继承的概念实现了IS-A关系。例如,哺乳动物是一种动物,狗是一种哺乳动物,因此狗也是一种动物,依此类推。

基类和子类

子类派生自已定义的基类。子类继承基类的成员,并拥有自己的成员。

子类使用inherit关键字定义,如下所示:

type MyDerived(...) =
   inherit MyBase(...)

在F#中,一个类最多只能有一个直接基类。如果不使用inherit关键字指定基类,则该类隐式地继承自Object。

请注意:

  • 基类的 方法和成员对派生类的用户可用,如同派生类的直接成员一样。

  • let绑定和构造函数参数对类是私有的,因此无法从派生类访问。

  • 关键字base指的是基类实例。它像 self 标识符一样使用。

示例

type Person(name) =
   member x.Name = name
   member x.Greet() = printfn "Hi, I'm %s" x.Name

type Student(name, studentID : int) =
   inherit Person(name)
   let mutable _GPA = 0.0
   member x.StudentID = studentID
   member x.GPA
      with get() = _GPA
      and set value = _GPA <- value

type Teacher(name, expertise : string) =
   inherit Person(name)

   let mutable _salary = 0.0
   member x.Salary
      with get() = _salary
      and set value = _salary <- value
   member x.Expertise = expertise

//using the subclasses
let p = new Person("Mohan")
let st = new Student("Zara", 1234)
let tr = new Teacher("Mariam", "Java")

p.Greet()
st.Greet()
tr.Greet()

编译并执行程序后,将产生以下输出:

Hi, I'm Mohan
Hi, I'm Zara
Hi, I'm Mariam

重写方法

您可以重写基类方法的默认行为,并在子类或派生类中以不同的方式实现它。

默认情况下,F#中的方法不能被重写。

要在派生类中重写方法,必须使用abstractdefault关键字将方法声明为可重写,如下所示:

type Person(name) =
   member x.Name = name
   abstract Greet : unit -> unit
   default x.Greet() = printfn "Hi, I'm %s" x.Name

现在,Person类的Greet方法可以在派生类中被重写。以下示例演示了这一点:

示例

type Person(name) =
   member x.Name = name
   abstract Greet : unit -> unit
   default x.Greet() = printfn "Hi, I'm %s" x.Name

type Student(name, studentID : int) =
   inherit Person(name)

   let mutable _GPA = 0.0

   member x.StudentID = studentID
   member x.GPA
      with get() = _GPA
      and set value = _GPA <- value
   override x.Greet() = printfn "Student %s" x.Name

type Teacher(name, expertise : string) =
   inherit Person(name)
   let mutable _salary = 0.0
   member x.Salary
      with get() = _salary
      and set value = _salary <- value

   member x.Expertise = expertise
   override x.Greet() = printfn "Teacher %s." x.Name

//using the subclasses
let p = new Person("Mohan")
let st = new Student("Zara", 1234)
let tr = new Teacher("Mariam", "Java")

//default Greet
p.Greet()

//Overriden Greet
st.Greet()
tr.Greet()

编译并执行程序后,将产生以下输出:

Hi, I'm Mohan
Student Zara
Teacher Mariam.

抽象类

有时,您需要提供对象的非完整实现,这在现实中不应实现。稍后,其他程序员应该创建抽象类的子类来完成实现。

例如,在学校管理系统中不需要Person类。但是,需要Student或Teacher类。在这种情况下,您可以将Person类声明为抽象类。

AbstractClass属性告诉编译器该类有一些抽象成员。

无法创建抽象类的实例,因为该类未完全实现。

以下示例演示了这一点:

示例

[<AbstractClass>]
type Person(name) =
   member x.Name = name
   abstract Greet : unit -> unit

type Student(name, studentID : int) =
   inherit Person(name)
   let mutable _GPA = 0.0
   member x.StudentID = studentID
   member x.GPA
      with get() = _GPA
      and set value = _GPA <- value
   override x.Greet() = printfn "Student %s" x.Name

type Teacher(name, expertise : string) =
   inherit Person(name)
   let mutable _salary = 0.0
   member x.Salary
      with get() = _salary
      and set value = _salary <- value
   member x.Expertise = expertise
   override x.Greet() = printfn "Teacher %s." x.Name

let st = new Student("Zara", 1234)
let tr = new Teacher("Mariam", "Java")

//Overriden Greet
st.Greet()
tr.Greet()

编译并执行程序后,将产生以下输出:

Student Zara
Teacher Mariam.

F# - 接口

接口提供了一种抽象的方式来编写类的实现细节。它是一个模板,声明类必须实现并公开的方法。

语法

接口指定其他类实现的相关成员集。它具有以下语法:

// Interface declaration:
[ attributes ]
type interface-name =
   [ interface ]
      [ inherit base-interface-name ...]
      abstract member1 : [ argument-types1 -> ] return-type1
      abstract member2 : [ argument-types2 -> ] return-type2
      ...
   [ end ]
	
// Implementing, inside a class type definition:
interface interface-name with
   member self-identifier.member1 argument-list = method-body1
   member self-identifier.member2 argument-list = method-body2
// Implementing, by using an object expression:
[ attributes ]
let class-name (argument-list) =
   { new interface-name with
      member self-identifier.member1 argument-list = method-body1
      member self-identifier.member2 argument-list = method-body2
      [ base-interface-definitions ]
   }
member-list

请注意:

  • 在接口声明中,成员未实现。

  • 成员是抽象的,由abstract关键字声明。但是,您可以使用default关键字提供默认实现。

  • 您可以使用对象表达式或类类型来实现接口。

  • 在类或对象实现中,需要为接口的抽象方法提供方法体。

  • 标记定义开始和结束的关键字interfaceend是可选的。

例如:

type IPerson =
   abstract Name : string
   abstract Enter : unit -> unit
   abstract Leave : unit -> unit

调用接口方法

接口方法是通过接口调用的,而不是通过实现接口的类的实例或类型调用的。要调用接口方法,您可以使用:>运算符(向上转型运算符)向上转型为接口类型。

例如:

(s :> IPerson).Enter()
(s :> IPerson).Leave()

以下示例说明了这个概念:

示例

type IPerson =
   abstract Name : string
   abstract Enter : unit -> unit
   abstract Leave : unit -> unit

type Student(name : string, id : int) =
   member this.ID = id
   interface IPerson with
      member this.Name = name
      member this.Enter() = printfn "Student entering premises!"
      member this.Leave() = printfn "Student leaving premises!"

type StuffMember(name : string, id : int, salary : float) =
   let mutable _salary = salary

   member this.Salary
      with get() = _salary
      and set(value) = _salary <- value

   interface IPerson with
      member this.Name = name
      member this.Enter() = printfn "Stuff member entering premises!"
      member this.Leave() = printfn "Stuff member leaving premises!"

let s = new Student("Zara", 1234)
let st = new StuffMember("Rohit", 34, 50000.0)

(s :> IPerson).Enter()
(s :> IPerson).Leave()
(st :> IPerson).Enter()
(st :> IPerson).Leave()

编译并执行程序后,将产生以下输出:

Student entering premises!
Student leaving premises!
Stuff member entering premises!
Stuff member leaving premises!

接口继承

接口可以继承一个或多个基接口。

以下示例显示了该概念:

type Interface1 =
   abstract member doubleIt: int -> int

type Interface2 =
   abstract member tripleIt: int -> int

type Interface3 =
   inherit Interface1
   inherit Interface2
   abstract member printIt: int -> string

type multiplierClass() =
   interface Interface3 with
      member this.doubleIt(a) = 2 * a
      member this.tripleIt(a) = 3 * a
      member this.printIt(a) = a.ToString()

let ml = multiplierClass()
printfn "%d" ((ml:>Interface3).doubleIt(5))
printfn "%d" ((ml:>Interface3).tripleIt(5))
printfn "%s" ((ml:>Interface3).printIt(5))

编译并执行程序后,将产生以下输出:

10
15
5

F# - 事件

事件允许类彼此之间发送和接收消息。

在GUI中,事件是用户操作,例如按键、点击、鼠标移动等,或某些事件,例如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。事件用于进程间通信。

对象通过同步消息传递相互通信。

事件附加到其他函数;对象将回调函数注册到事件,并且当(如果)某个对象触发事件时,将执行这些回调。

事件类和事件模块

Control.Event<'T>类有助于创建可观察对象或事件。

它具有以下实例成员来处理事件:

成员 说明
Publish 将观察结果发布为一等值。
Trigger 使用给定参数触发观察结果。

Control.Event模块提供用于管理事件流的函数:

说明
add : ('T → unit) → Event<'Del,'T> → unit 每次触发给定事件时运行给定函数。
choose : ('T → 'U option) → IEvent<'Del,'T> → IEvent<'U> 返回一个新事件,该事件在从原始事件选择的邮件上触发。选择函数将原始消息转换为可选的新消息。
filter : ('T → bool) → IEvent<'Del,'T> → IEvent<'T> 返回一个新事件,该事件侦听原始事件,并且仅当事件的参数通过给定函数时才触发结果事件。
map : ('T → 'U) → IEvent<'Del, 'T> → IEvent<'U> 返回一个新事件,该事件传递由给定函数转换的值。
merge : IEvent<'Del1,'T> → IEvent<'Del2,'T> → IEvent<'T> 当任一输入事件触发时触发输出事件。
pairwise : IEvent<'Del,'T> → IEvent<'T * 'T> 返回一个新事件,该事件在输入事件的第二次及后续触发时触发。输入事件的第 N 次触发将第 N-1 次和第 N 次触发的参数作为一对传递。传递给第 N-1 次触发的参数将保存在隐藏的内部状态中,直到发生第 N 次触发。
partition : ('T → bool) → IEvent<'Del,'T> → IEvent<'T> * IEvent<'T> 返回一个新事件,该事件侦听原始事件,如果将谓词应用于事件参数返回 true,则触发第一个结果事件;如果返回 false,则触发第二个事件。
scan : ('U → 'T → 'U) → 'U → IEvent<'Del,'T> → IEvent<'U> 返回一个新事件,该事件由将给定的累加函数应用于输入事件上触发的连续值的结果组成。内部状态的一个项目记录状态参数的当前值。在累加函数执行期间不会锁定内部状态,因此应注意,输入 IEvent 不应由多个线程同时触发。
split : ('T → Choice<'U1,'U2>) → IEvent<'Del,'T> → IEvent<'U1> * IEvent<'U2> 返回一个新事件,该事件侦听原始事件,如果将函数应用于事件参数返回 Choice1Of2,则触发第一个结果事件;如果返回 Choice2Of2,则触发第二个事件。

创建事件

事件是通过Event类创建和使用的。Event构造函数用于创建事件。

示例

type Worker(name : string, shift : string) =
   let mutable _name = name;
   let mutable _shift = shift;
   let nameChanged = new Event<unit>() (* creates event *)
   let shiftChanged = new Event<unit>() (* creates event *)

   member this.Name
      with get() = _name
      and set(value) = _name <- value

   member this.Shift
      with get() = _shift
      and set(value) = _shift <- value

之后,您需要将nameChanged字段公开为公共成员,以便侦听器可以挂接到事件上,为此,您可以使用事件的Publish属性:

type Worker(name : string, shift : string) =
   let mutable _name = name;
   let mutable _shift = shift;

   let nameChanged = new Event<unit>() (* creates event *)
   let shiftChanged = new Event<unit>() (* creates event *)

   member this.NameChanged = nameChanged.Publish (* exposed event handler *)
   member this.ShiftChanged = shiftChanged.Publish (* exposed event handler *)

   member this.Name
      with get() = _name
      and set(value) = _name <- value
      nameChanged.Trigger() (* invokes event handler *)

   member this.Shift
      with get() = _shift
      and set(value) = _shift <- value
   shiftChanged.Trigger() (* invokes event handler *)

接下来,您将回调添加到事件处理程序。每个事件处理程序都具有类型IEvent<'T>,它提供多种方法:

方法说明
val Add : event:('T → unit) → unit 将侦听器函数连接到事件。触发事件时将调用侦听器。
val AddHandler : 'del → unit 将处理程序委托对象连接到事件。稍后可以使用RemoveHandler删除处理程序。触发事件时将调用侦听器。
val RemoveHandler : 'del → unit 从事件侦听器存储中删除侦听器委托。

以下部分提供完整的示例。

示例

以下示例演示了上面讨论的概念和技术:

type Worker(name : string, shift : string) =
   let mutable _name = name;
   let mutable _shift = shift;

   let nameChanged = new Event<unit>() (* creates event *)
   let shiftChanged = new Event<unit>() (* creates event *)

   member this.NameChanged = nameChanged.Publish (* exposed event handler *)
   member this.ShiftChanged = shiftChanged.Publish (* exposed event handler *)

   member this.Name
      with get() = _name
      and set(value) = 
         _name <- value
         nameChanged.Trigger() (* invokes event handler *)

   member this.Shift
      with get() = _shift
      and set(value) = 
         _shift <- value
         shiftChanged.Trigger() (* invokes event handler *)

let wk = new Worker("Wilson", "Evening")
wk.NameChanged.Add(fun () -> printfn "Worker changed name! New name: %s" wk.Name)
wk.Name <- "William"
wk.NameChanged.Add(fun () -> printfn "-- Another handler attached to NameChanged!")
wk.Name <- "Bill"

wk.ShiftChanged.Add(fun () -> printfn "Worker changed shift! New shift: %s" wk.Shift)
wk.Shift <- "Morning"
wk.ShiftChanged.Add(fun () -> printfn "-- Another handler attached to ShiftChanged!")
wk.Shift <- "Night"

编译并执行程序后,将产生以下输出:

Worker changed name! New name: William
Worker changed name! New name: Bill
-- Another handler attached to NameChanged!
Worker changed shift! New shift: Morning
Worker changed shift! New shift: Night
-- Another handler attached to ShiftChanged!

F# - 模块

根据MSDN库,F#模块是F#代码结构(例如类型、值、函数值和do绑定中的代码)的组合。它实现为公共语言运行时 (CLR) 类,该类只有静态成员。

根据整个文件是否包含在模块中,模块声明有两种类型:

  • 顶级模块声明
  • 局部模块声明

在顶级模块声明中,整个文件都包含在模块中。在这种情况下,文件中的第一个声明是模块声明。您不必缩进顶级模块中的声明。

在局部模块声明中,只有在该模块声明下缩进的声明才是模块的一部分。

语法

模块声明的语法如下:

// Top-level module declaration.
module [accessibility-modifier] [qualified-namespace.]module-name
   declarations
// Local module declaration.
module [accessibility-modifier] module-name =
   declarations

请注意,访问修饰符可以是以下之一:public、private、internal。默认为public

以下示例将演示这些概念:

示例 1

模块文件Arithmetic.fs:

module Arithmetic
let add x y =
   x + y

let sub x y =
   x - y
	
let mult x y =
   x * y
	
let div x y =
   x / y

程序文件main.fs:

// Fully qualify the function name.
open Arithmetic
let addRes = Arithmetic.add 25 9
let subRes = Arithmetic.sub 25 9
let multRes = Arithmetic.mult 25 9
let divRes = Arithmetic.div 25 9

printfn "%d" addRes
printfn "%d" subRes
printfn "%d" multRes
printfn "%d" divRes

编译并执行程序后,将产生以下输出:

34
16
225
2
110
90
1000
10

示例 2

// Module1
module module1 =
   // Indent all program elements within modules that are declared with an equal sign.
   let value1 = 100
   let module1Function x =
      x + value1

// Module2
module module2 =
   let value2 = 200

   // Use a qualified name to access the function.
   // from module1.
   let module2Function x =
      x + (module1.module1Function value2)

let result = module1.module1Function 25
printfn "%d" result

let result2 = module2.module2Function 25
printfn "%d" result2

编译并执行程序后,将产生以下输出:

125
325

F# - 命名空间

命名空间旨在提供一种方法来使一组名称与另一组名称分开。在一个命名空间中声明的类名不会与在另一个命名空间中声明的相同类名冲突。

根据MSDN库,命名空间允许您通过将名称附加到程序元素组来组织代码到相关的功能区域。

声明命名空间

要在命名空间中组织代码,必须将命名空间声明为文件中的第一个声明。然后,整个文件的内容都成为命名空间的一部分。

namespace [parent-namespaces.]identifier

以下示例说明了这个概念:

示例

namespace testing

module testmodule1 =
   let testFunction x y =
      printfn "Values from Module1: %A %A" x y
module testmodule2 =
   let testFunction x y =
      printfn "Values from Module2: %A %A" x y

module usermodule =
   do
      testmodule1.testFunction ( "one", "two", "three" ) 150
      testmodule2.testFunction (seq { for i in 1 .. 10 do yield i * i }) 200

编译并执行程序后,将产生以下输出:

Values from Module1: ("one", "two", "three") 150
Values from Module2: seq [1; 4; 9; 16; ...] 200
广告
© . All rights reserved.