在第一讲中,我们介绍了F#的基本语法,包括有些特殊的分支,循环等。
在本讲中,我们将来了解F#中的类型系统,以及一些C#中没有的特殊类型系统
常见类型
| 类型 | .NET 类型 | 说明 | 示例 |
|---|---|---|---|
bool | Boolean | 可能值为 | true,false |
byte | Byte | 从 0 到 255 之间的值。 | 1uy |
sbyte | SByte | 从 -128 到 127 之间的值。 | 1y |
int16 | Int16 | 从 -32768 到 32767 之间的值。 | 1s |
uint16 | UInt16 | 从 0 到 65535 之间的值。 | 1us |
int | Int32 | 值的范围是 -2,147,483,648 至 2,147,483,647。 | 1 |
uint | UInt32 | 从 0 到 4,294,967,295 之间的值。 | 1u |
int64 | Int64 | 从 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 之间的值。 | 1L |
uint64 | UInt64 | 从 0 到 18,446,744,073,709,551,615 之间的值。 | 1UL |
nativeint | IntPtr | 作为带符号整数的原生指针。 | nativeint 1 |
unativeint | UIntPtr | 作为无符号整数的原生指针。 | unativeint 1 |
decimal | Decimal | 至少具有 28 个有效数字的浮点数据类型。 | 1.0m |
float, | Double | 64 位浮点类型。 | 1.0 |
float32, | Single | 32 位浮点类型。 | 1.0f |
char | Char | Unicode 字符值。 | 'c' |
string | String | Unicode 文本。 | "str" |
特殊的unit类型
F#的表达式必须有一个值,unit则代表了这种情况(类似于void,但unit是一个实打实的类型),如果一个表达式返回了非unit类型,且值没有在代码中被使用,编译器便会发出警告。
集合类型
在F#中,有三种类似于数组/列表的类型,Array(数组), List(列表), Sequences(序列)
其中数组与列表的关系,与C#中的概念有一定差异,Array是固定长度,元素默认可变的数组(集合)。而List则是元素默认不可变的,当追加或删除元素时,会新生成一个list,而不会更改原有数据(但其内部元素依然是公用的),这也是F#对不可变性的推崇,不可变往往是默认行为。
let a1 = [|1; 2; 3|]
let a2 =
[|1
2
3|]
//使用[| |]定义数组, 请注意使用;或者换行分隔数组元素
//否则会认为是一个元组的元素的数组
let l1 = [1; 2]
// 同样的,使用[]定义列表,
let l2 = l1 @ [3; 4]
// 使用@连接两个列表
let l3 = 1 :: l2
// 使用::添加元素到列表头部
l3.[3] <- 5 // 这是不被允许的

如图 array的元素默认可以改变(这其实有违不可变性,必须谨慎使用!)

可以看到修改不会修改原来的列表,会生成一个新的列表,既然元素本就不可变,所以公用元素也不会造成任何的并发问题
(同时,推荐使用.[]的语法来访问数组或者列表元素,尽管最新语法中.不是必须的,但依然不推荐使用)
而Seq,则是一个惰性的序列,当我们有可能不会用到Seq的全部或是部分元素时,我们就可以采用Seq(类似于C#中的IEnumrable)
let s1 = seq { 0..10..100 }
// 使用序列表达式生成序列
let s2 = seq { for i in 0..10 -> i + 1 }
// 使用for循环->生成序列
let s3 = seq {
for i in 0..10 do
yield i + 1
}
// 通过yield关键字生成序列
let s4 = seq {
for i in 0..10 do
yield! seq {1; 2; 3; 4}
}
// 使用yield!关键字生成平铺后的序列
printfn "%A" (s1 |> Seq.toList)
printfn "%A" (s2 |> Seq.toList)
printfn "%A" (s3 |> Seq.toList)
printfn "%A" (s4 |> Seq.toList)
// 因为序列是惰性的,所以需要使用Seq.toList将序列转换为列表
惰性计算同样是在FP中非常重要的一个概念(尽管其他算法中也常会提到这个概念)
其余还有类似Map, Set的类型,同样的,默认也是不可变,即新增元素会重新生成一个新的实例
let m1 = Map.ofList [(1, "a"); (2, "b")]
let m2 = m1 |> Map.add 3 "c"
let set1 = Set.ofList [1; 2; 3]
let set2 = Set.add 4 set1

记录/匿名记录
与C#的记录类似(或是应该说C#记录与F#类似),记录是简单值的聚合。尽管在F#中,支持对记录做一定的功能扩展,但在程序中依然只是简单值的聚合,一般来说不推荐将记录复杂话,经作为数据存在在程序中(数据与逻辑分离)
type Point = { X: float; Y: float; Z: float }
// 定义一个Point的记录类型
let mypoint3D = { X = 1.0; Y = 1.0; Z = 0.0 }
// 通过记录表达式创建一个Point类型的值
let mypoint3D = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
let anonPoint3D = {| X1 = 1.0; Y1 = 1.0; Z1 = 0.0 |} // 匿名记录类型
类型推断系统,会自动推断出你使用的类型(若有字段定义完全相同的类型,则以后定义的为默认情况。这种情况下可以显式定义字段)
学会使用记录来定义模型,是一件非常重要的事情,这不仅仅在F#中有用,C#的记录也意图发挥同样的作用,关于这一点,我们会专门写明一讲去介绍相关内容。在此处,我们只需记住记录是在FP中非常常用的类型,FP中通常情况不会使用 类 这个东西,他是OOP的东西
可区分联合
来到了我们重点中的重点,可区分联合。这种类型提供了强大的抽象能力,是FP中非常重要的存在。C#也正在引入这个特性,我们有可能在C#14中见到他。
简单来说,可区分联合,可以将各种类型组合在一起形成一个大类型,在类型论中这个新的类型叫做和类型,能够清晰地表达复杂的数据结构和逻辑。可以用于替代OOP中复杂的继承系统,也是monad的基础。其强大的表达能力,配合模式匹配,在FP中发挥了极其重要的作用。
type Address =
| Email of string
| Phone of string
| QQ of int
let sendMessage addr msg =
match addr with
| Email email -> printfn "Send email to %s: %s" email msg
| Phone phone -> printfn "Send message to %s: %s" phone msg
| QQ qq -> printfn "Send message to %d: %s" qq msg
// 必须处理所有的情况
type Maybe<'a> =
| Just of 'a
| Nothing
type Option<'a> =
| Some of 'a
| None
面对对象的类型
F#作为多范式语言,自然也支持OOP那一套,但在最这里还是再次说明。一般只有与C#需要频繁交互的部分,才建议采用OOP的方式去编写代码,这些特性不是F#推荐去使用的。
简单例子
type Class(a) =
member _.a = a
member this.b = 1
member val c = 1
new() = Class(0)
let a = Class()
那么有关于类型系统的简介就到这里,再之后,就将真正步入函数式编程的核心领域了,敬请期待!
bilibili: @无聊的年 【F#初学者】第二讲 F#中的类型简介
csdn: @scixing


被折叠的 条评论
为什么被折叠?



