При желании взять первый элемент в последовательности

Мне нужна функция типа 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 - тот, который я использую. Два вопроса:

  • Есть ли особенно идиоматический способ сделать это?
  • Поскольку нет встроенной функции Seq.tryHead, указывает ли это, что это не обязательно, является необычным или лучше реализовано без функции?

UPDATE

tryHead был добавлен в стандартную библиотеку в F # 4.0.

Ответы

Ответ 1

Я думаю, что (б), вероятно, самый идиоматический, по той же причине @Ramon дал.

Я думаю, что недостаток Seq.tryHead означает, что он не слишком распространен.

Я не уверен, но я предполагаю, что функциональные языки с выводами типа Хиндли-Милнера в целом редко используются для реализации таких конкретных функций в типах сбора, потому что перегрузка недоступна, а составные функции более высокого порядка могут выполняться кратко,

Например, расширения С# Linq гораздо более исчерпывающие, чем функции в модуле F # Seq (который сам по себе более исчерпывающий, чем функции для конкретных типов коллекций) и даже имеет IEnumerable.FirstOrDefault. Практически каждая перегрузка имеет вариацию, которая выполняет map.

Я думаю, что акцент на сопоставлении с образцом и конкретные типы, такие как list, также является причиной.

Теперь, большинство из вышеперечисленных предположений, но я думаю, что у меня может быть понятие ближе к объективному. Я думаю, что в большинстве случаев tryPick и tryFind можно использовать вместо 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

Ответ 2

Вы можете определить:

let seqTryHead s = Seq.tryPick Some s

Это тип seq<'a> -> 'a option. Обратите внимание, что я не бета-снижается из-за общего ограничения стоимости.