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
精彩评论