开发者

Accessing items in an IEnumerable in F#

I'm trying to use the Youtube .Net API in F# and I've come across a problem trying to access the IEnumerable returned to the userPlaylists.Entries property. Below is the c# code I've tested, however I can't seem to return individual items from the IEnumerable collection in f#.

 Feed<Playlist> userPlaylists = request.GetPlaylistsFeed("username");
 var p = userPlaylists.Entries.Single<Playlist>(x => x.Title == "playlistname");

Entries resolves to the type IEnumerable<Playlist> Feed<PlayList>.Entries

IEnumerable appears to be represented as a sequence in F#, but i can't seem to figure out how to return and individual item of the correct type, the closest i开发者_StackOverflow中文版 got was:

let UserPlaylists = Request.GetPlaylistsFeed("username")
let pl = UserPlaylists.Entries |>
Seq.tryPick(fun x -> if x.Title="playlistname" then Some(x) else None)

However this seems to return a type of 'Playlist option' instead of 'Playlist'. Can anyone suggest the correct way to retrieve an individual item from an IEnumerable?


The Single() extension method throws an exception if it doesn't find a match, and it also throws an exception if it finds more than one match. There is no direct equivalent in the F# Seq module. The closest would be Seq.find which like .Single() will throw an exception if it doesn't find a match, however unlike .Single() it will stop looking as soon as it finds a match, and will not throw an error if more than one match exists.

If you really need the "throw an error if more than one match" behavior, then the easiest thing would be to use that exact method from F#:

open System.Linq

let p = userPlaylists.Entries.Single(fun x -> x.Title = "playlistname")

If you don't need the "throw if more than one match" behavior, then you shouldn't be using .Single() in your C# code either - .First() will perform better, because it stops looking as soon as it finds a match.

If it were me, I would use use Seq.tryFind over Seq.tryPick for brevity, and handle the "not found" case rather than throwing an error.

let userPlaylists = request.GetPlaylistsFeed("username")
let p = userPlaylists.Entries |> Seq.tryFind (fun x -> x.Title = "playlistname")
match p with
| Some pl ->
    // do something with the list
| None ->
    // do something else because we didn't find the list

Or alternately, I might do it without creating intermediate values that only get referenced once...

request.GetPlaylistsFeed("username").Entries
|> Seq.tryFind (fun x -> x.Title = "playlistname")
|> function
   | Some pl ->
       // do something with the list
   | None ->
       // do something else because we didn't find the list

I do love the pipeline operator...


Seq.tryPick is akin to IEnumerable.FirstOrDefault followed by a type transformation, which does not have the semantics you're looking for. Seq.pick is akin to IEnumerable.First followed by a type transformation, which is a step in the right direction (assuming you want an exception to be thrown upon failure to find any matching item, as with IEnumerable.Single), but as you don't actually need a type transformation, I think what you really want is Seq.find, which is semantically identical to IEnumerable.First:

let UserPlaylists = Request.GetPlaylistsFeed "username"
let pl = UserPlaylists.Entries |> Seq.find (fun x -> x.Title = "playlistname")

That said, if you really need the semantics of IEnumerable.Single rather than IEnumerable.First, ignore this and see @JoelMueller's answer.


Use the Option.get method to get the value out of the option

Seq.tryPick(fun x -> if x.Title="playlistname" then Some(x) else None)
|> Option.get

Although in F# i believe it's more idiomatic to explicitly handle the empty case instead of using exception propagation

let value = Seq.tryPick(fun x -> if x.Title="playlistname" then Some(x) else None)
match value with
| None ->
  // No matching elements.  Take corrective action
| Some value ->
  // The value i'm looking for
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜