【问题标题】:Optionally taking the first item in a sequence可选地获取序列中的第一项
【发布时间】:2011-11-21 04:47:47
【问题描述】:

我需要一个类似Seq.head 的函数,但在序列为空时返回None 而不是抛出异常,即seq<'T> -> 'T option

有很多方法可以做到这一点。这里有几个:

let items = Seq.init 10 id
let a = Seq.tryFind (fun _ -> true) items
let b = Seq.tryPick Some items
let c = if Seq.isEmpty items then None else Some (Seq.head items)
let d = 
  use e = items.GetEnumerator()
  if e.MoveNext() then Some e.Current
  else None

b 是我使用的那个。两个问题:

  1. 有没有特别惯用的方法来做到这一点?
  2. 由于没有内置的 Seq.tryHead 函数,这是否表明这不是必需的、不常见的,或者没有函数更好地实现?

更新

tryHead has been added to the standard library in F# 4.0

【问题讨论】:

标签: f# sequence idioms


【解决方案1】:

我认为 (b) 可能是最惯用的,原因与@Ramon 给出的相同。

我认为缺少Seq.tryHead 只是意味着它不是很常见。

我不确定,但我的猜测是具有 Hindley-Milner 类型推断的函数式语言通常很少在集合类型上实现此类特定函数,因为重载不可用并且可以简洁地组合高阶函数.

例如,C# Linq 扩展比 F# 的 Seq 模块中的函数更详尽(它本身比具体集合类型上的函数更详尽),甚至还有 IEnumerable.FirstOrDefault。几乎每个重载都有一个变体,执行map

我认为强调模式匹配和像list 这样的具体类型也是一个原因。

现在,以上大部分都是猜测,但我想我可能有一个更接近客观的概念。我认为很多时候tryPicktryFind 可以首先使用而不是filter |> tryHead。例如,我发现自己经常编写如下代码:

open System.Reflection
let ty = typeof<System.String> //suppose this type is actually unknown at compile time
seq {
    for name in ["a";"b";"c"] do
        yield ty.GetMethod(name)
} |> Seq.tryFind((<>)null)

而不是喜欢

...
seq {
    for name in ["a";"b";"c"] do
        match ty.GetMethod(name) with
        | null -> ()
        | mi -> yield mi
} |> tryHead

【讨论】:

  • 有趣的猜测。 :-) 虽然我在最后一段中看到了你的观点,但当我想要第一个元素时,我通常不会过滤。这就是为什么使用tryPick 感觉很尴尬的原因——它并没有清楚地表达我想要做什么。
  • 我认为添加一个Seq.tryHead 扩展名会是一个好主意,人们会立即理解它,而且我通常看不出它有什么特别之处。
【解决方案2】:

你可以定义:

let seqTryHead s = Seq.tryPick Some s

它的类型是seq&lt;'a&gt; -&gt; 'a option。请注意,由于通用值限制,我不会进行 beta-reduce。

【讨论】:

  • 这并没有明确解决我的问题。我已经提到这就是我正在做的事情。
  • 这是最惯用的,因为它是无点的(仅使用更高的函数)。它没有被实现,因为它不是很常见(尽管它被使用)。
  • 不就是eta-reduction而不是beta-reduction吗? haskell.org/haskellwiki/Eta_conversion
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-07
  • 2013-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多