哈Ha! 新年假期结束后,我们将继续提供有关函数式编程的系列文章。 今天,我们将讨论通过签名理解函数以及定义函数签名的类型。 细节剪下!

不明显,但是F#具有两种语法:用于正则(有意义)表达式和用于类型定义。 例如:
[1;2;3] // int list // Some 1 // int option // (1,"a") // int * string //
类型的表达式具有与普通表达式不同的特殊语法。 在使用FSI(FSharp Interactive)时,您可能已经注意到此语法的许多示例,例如 将显示每个表达式的类型以及其执行结果。
如您所知,F#使用类型推断算法,因此通常不需要在代码中显式地编写类型,尤其是在函数中。 但是要有效使用F#,您需要了解类型的语法,以便可以定义自己的类型,调试类型转换错误和读取函数签名。 在本文中,我将重点介绍函数签名中类型的使用。
以下是类型语法签名的一些示例:
// // let add1 x = x + 1 // int -> int let add xy = x + y // int -> int -> int let print x = printf "%A" x // 'a -> unit System.Console.ReadLine // unit -> string List.sum // 'a list -> 'a List.filter // ('a -> bool) -> 'a list -> 'a list List.map // ('a -> 'b) -> 'a list -> 'b list
通过签名了解功能
通常,即使仅研究函数的签名,您也可以对它的功能有所了解。 考虑一些示例,然后依次分析它们。
int -> int -> int
此函数接受两个int
参数,并返回另一个int
。 最有可能的是,它是一种数学函数,例如加,减,乘或乘。
int -> unit
此函数采用int
并返回unit
,这意味着该函数做了一些重要的副作用。 因为 它不会返回有用的值,副作用很可能会在IO中产生写入操作,例如日志记录,写入数据库或类似操作。
unit -> string
此函数不接受任何内容,但返回一个string
,这可能意味着该函数从空中接收了一个字符串。 由于没有显式输入,因此该函数可能在读取(例如从文件)或生成(例如随机字符串)时执行某些操作。
int -> (unit -> string)
该函数接受一个int
并返回另一个函数,该函数将在调用时返回一个字符串。 同样,该功能可能会执行读取或生成操作。 输入可能会以某种方式初始化返回函数。 例如,输入可以是文件的标识符,并且返回的函数类似于readline()
。 或者,输入可能是随机字符串生成器的起始值。 我们不能肯定地说,但可以得出一些结论。
'a list -> 'a
该函数接受任何类型的列表,但仅返回此类型的一个值。 这可能表明该函数汇总了列表或选择了其元素之一。 类似的签名是List.sum
, List.max
, List.head
等。
('a -> bool) -> 'a list -> 'a list
该函数有两个参数:第一个是将事物转换为bool
(谓词)的函数,第二个是列表。 返回值是相同类型的列表。 谓词用于确定对象是否满足特定条件,以及该函数是否似乎根据谓词从列表中选择元素-true或false。 之后,它将返回原始列表的子集。 具有此签名的函数的一个示例是List.filter
。
('a -> 'b) -> 'a list -> 'b list
该函数有两个参数:从类型'a
到类型'b
转换以及类型'a
的列表。 返回值是'b
类型的列表。 可以合理地假设该函数从列表'a
获取每个元素,然后使用作为第一个参数传递的函数将其转换为'b
,然后返回列表'b
。 实际上, List.map
是具有这种签名的函数的原型。
搜索带签名的库方法
函数签名对于查找库函数非常重要。 F#库包含数百个函数,一开始可能会造成混淆。 与面向对象的语言不同,您不能简单地通过点“输入对象”来查找所有相关方法。 但是,如果您知道所需功能的签名,则可以快速缩小搜索范围。
例如,您有两个列表,并且想要找到一个将它们组合为一个的函数。 所需功能将具有什么签名? 它必须将两个列表作为参数并返回第三个相同类型的列表:
'a list -> 'a list -> 'a list
现在,转到MSDN文档站点的“列表”模块 ,并查找类似的功能。 事实证明,只有一个具有这种签名的功能:
append : 'T list -> 'T list -> 'T list
你需要什么!
定义函数签名的本机类型
有一天,您将需要为所需的功能定义自己的类型。 可以使用关键字“ type”完成此操作:
type Adder = int -> int type AdderGenerator = int -> Adder
将来,您可以使用这些类型来限制函数参数的值。
例如,由于强制转换错误的限制,第二个声明将失效。 如果我们删除它(如第三个公告中所述),该错误将消失。
let a:AdderGenerator = fun x -> (fun y -> x + y) let b:AdderGenerator = fun (x:float) -> (fun y -> x + y) let c = fun (x:float) -> (fun y -> x + y)
测试功能标志理解
您对功能签名了解得很好吗? 检查您自己是否可以使用下面的签名创建简单的函数。 避免明确指定类型!
val testA = int -> int val testB = int -> int -> int val testC = int -> (int -> int) val testD = (int -> int) -> int val testE = int -> int -> int -> int val testF = (int -> int) -> (int -> int) val testG = int -> (int -> int) -> int val testH = (int -> int -> int) -> int
其他资源
F#的教程很多,包括那些具有C#或Java经验的人的材料。 当您深入了解F#时,以下链接可能会很有用:
还介绍了其他几种开始学习F#的方法 。
最后,F#社区非常适合初学者。 在Slack上,由F#Software Foundation支持的聊天非常活跃,您可以自由加入初学者室。 我们强烈建议您这样做!
不要忘记访问俄语社区F#的网站 ! 如果您对学习语言有任何疑问,我们将很乐意在聊天室中讨论这些问题:
关于翻译作者
由@kleidemos翻译
在F#开发人员的俄语社区的努力下进行了翻译和编辑更改。 我们也感谢@schvepsss和@shwars为本文准备发表。