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

Мне нужна функция наподобие 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.


person Daniel    schedule 20.09.2011    source источник
comment
Существует Seq.tryHead в следующей библиотеке FSharpx.Collections fsprojects.github.io/FSharpx.Collections< /а>   -  person Frank Hale    schedule 05.06.2014


Ответы (2)


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

Я думаю, что отсутствие Seq.tryHead просто означает, что это не очень распространено.

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

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

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

Итак, большая часть вышеизложенного является предположением, но я думаю, что у меня может быть представление, более близкое к объективности. Я думаю, что в большинстве случаев вместо filter |> tryHead можно использовать tryPick и tryFind. Например, я довольно часто пишу код, подобный следующему:

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
person Stephen Swensen    schedule 20.09.2011
comment
Интересные предположения. :-) Хотя я вижу вашу точку зрения в вашем последнем абзаце, я обычно не фильтрую, когда мне нужен первый элемент. Вот почему использование tryPick кажется неудобным — оно не ясно выражает то, что я пытаюсь сделать. - person Daniel; 20.09.2011
comment
Я думаю, что добавление расширения Seq.tryHead было бы отличной идеей, люди сразу это поймут, и я обычно не вижу в этом ничего особенного. - person Stephen Swensen; 20.09.2011

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

let seqTryHead s = Seq.tryPick Some s

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

person Ramon Snir    schedule 20.09.2011
comment
Это явно не касается моих вопросов. Я уже упоминал, что я этим занимаюсь. - person Daniel; 20.09.2011
comment
Это наиболее идиоматично, так как не содержит точек (использует только высшие функции). Он не реализован, потому что он не очень распространен (хотя и используется). - person Ramon Snir; 20.09.2011
comment
Разве это не эта-редукция вместо бета-редукции? haskell.org/haskellwiki/Eta_conversion - person Mauricio Scheffer; 20.09.2011