FSharp.Core中`id`函数的目的是什么?

40

来自Operators.id<'T>函数(F#)

身份函数。

参数:x 类型:'T(输入值)

返回值:相同的值

F#核心库版本支持:2.0、4.0、Portable

为什么会有一个返回其输入的函数?


7
有趣的是,它还具有数学背景。拥有一个“什么也不做”的对象能够提供重要的结构。由于零是加法下的数字恒等元素,而一是乘法下的数字恒等元素,因此id是函数集合在组合下的恒等元素。在函数式编程语言中,您经常希望将函数作为对象进行操作、执行各种操作等。 - matt_t_gregg
如果您把那个作为答案发表,我相信您一定会得到很多赞。 - Fyodor Soikin
@FyodorSoikin 我很高兴你的回答涵盖了它为什么需要和使用的实际情况 - 我认为数学方面更像是一个有趣的旁白。 - matt_t_gregg
3个回答

44
在使用高阶函数(即返回其他函数和/或接受其他函数作为参数的函数)时,您必须始终提供某些内容作为参数,但并不总是存在实际的数据转换需要应用的情况。
例如,函数Seq.collect会展开一个序列中的序列,并采取一个函数来返回每个“外部”序列元素的“嵌套”序列。例如,以下是获取某种UI控件的所有孙子节点列表的方式:
let control = ...
let allGrandChildren = control.Children |> Seq.collect (fun c -> c.Children)

但是很多时候,序列的每个元素本身就是一个序列 - 例如,您可能有一个列表的列表:

let l = [ [1;2]; [3;4]; [5;6] ]

在这种情况下,您传递到Seq.collect的参数函数只需要返回该参数:

let flattened = [ [1;2]; [3;4]; [5;6] ] |> Seq.collect (fun x -> x)

这个表达式fun x -> x是一个函数,它只返回其参数,也被称为“恒等函数”。

let flattened = [ [1;2]; [3;4]; [5;6] ] |> Seq.collect id

在使用高阶函数(例如上面的Seq.collect)时,这种用法经常出现,因此它应该在标准库中有一席之地。

另一个引人注目的例子是Seq.choose - 这个函数会过滤Option值的序列并同时解包它们。例如,下面是将所有字符串解析为数字并丢弃无法解析的字符串的方法:

let tryParse s = match System.Int32.TryParse s with | true, x -> Some x | _ -> None
let strings = [ "1"; "2"; "foo"; "42" ]
let numbers = strings |> Seq.choose tryParse  // numbers = [1;2;42]

但是如果您已经有一个Option值列表怎么办?恒等函数来帮忙!

let toNumbers optionNumbers =
   optionNumbers |> Seq.choose id

5
“Seq.choose id”这个例子很好,但“Seq.collect id”只是一个不太易读的说法,与“Seq.concat”意思相同。(话虽如此,我相信我也写过几次“Seq.collect id”,它很好地说明了“id”的作用!) - Tomas Petricek
@TomasPetricek 经常会有人的思路引导到 Seq.collect id。我的思路通常无法迈出下一步到 Seq.concat,不过你的评论可能会增加下次这种情况发生的机会。无论如何,拥有灵活性是很好的。我有时希望 C# 中有一个 Id() 函数,并且有时会手动实现它,但类型推断的差异使其稍微不那么有用。 - phoog

17

对于某些高阶函数(接受函数作为参数的函数),这是很有用的,因此您可以将id作为参数传递,而不是编写lambda表达式(fun x -> x)

[[1;2]; [3]] |> List.collect id  // [1; 2; 3]

1
在这种情况下,您可以只使用List.concat,这样会更易读 :-). - Tomas Petricek

1

当处理选项时,它非常有用。

我编写了一个小的习惯用语JSON助手,将所有可选字段标识为Option,并在传递null字符串时抛出错误,如果不是“string option”类型,则会抛出错误。

现在有一个函数提供了一个盒装输出值,它可以是:

  1. 'a -> 任何类型但没有选项
  2. 'b -> 'x option

为了正确地包装该值,我使用

val |> if isOption then fnOptTransform else id

所以我正在应用高阶函数fnOptTransform,并通过调用id来避免编写单独的lambda表达式(我尽可能避免这样做)。发现它很有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接