I'm looking into F# for personal 'fun' reasons and to explore thinking in a functional way. I'm trying to duplicate the functionality of this other code snippet I wrote this morning: here.
I've written this code below, however it seems I can't really access items in my for ... in
statement.
open System
open System.Net
open System.Web.Helpers
let data = Json.Decode((new WebClient()).DownloadString("http://sw.cs.wwu.edu/~fugiera/matches"))
let myList = [for x : DynamicJsonArray in downcast data do if x?status != "complete" then yield x?home_team.goals ]
Console.ReadKey() |> ignore
I'm really just trying to wrap my head around everything right now, and could use a pointer or two!
Thanks
First, your for
loop is casting in the wrong place. You want to cast data
to DynamicJsonArray
, not x
. You also need to cast x
to DynamicJsonObject
.
let myList = [
for x in (data :?> DynamicJsonArray) do
let x = x :?> DynamicJsonObject
...
F# doesn't have built-in support for dynamic objects like this the way C# does, so you can't use a standard operator like ?
to get at the properties of your object.
I spent about 20 seconds attempting to figure out how one does extract dynamic properties using normal APIs, but got bored.
Luckily, F# has a feature called Type Providers which in some ways are like dynamic objects, but on super steroids. They would work perfectly here.
Add a reference to the NuGet package FSharp.Data
and try this code on for size:
open FSharp.Data
type WorldCup = JsonProvider<"http://worldcup.sfg.io/matches">
for game in WorldCup.GetSamples() do
match (game.Status, game.HomeTeam.Record, game.AwayTeam.Record) with
| ("completed", Some(ht), Some(at)) ->
printfn "%s %d vs %s %d" ht.Country ht.Goals at.Country at.Goals
| _ -> ()
If you just want working code, there ya go. For some more details, read on.
Some things to notice, comparing to your linked C# code:
In particular, notice that in the raw JSON, game.HomeTeam
is not always present (i.e. for not-yet-played games this is not populated). Your C# accesses game.home_team.country, game.home_team.goals
anyways, because you are filtering on status
. That works ok for now, but in general it's risky since that could blow up at runtime.
In F#, the JSON type provider notices that HomeTeam
and AwayTeam
are not always present, so it exposes them as option
types. You are required by the type system to consider what to do when those values are missing. That's a good thing!
So you see that the code checks for 3 things: 1. status = "completed"
2. HomeTeam
is defined (is Some(homeTeam)
, not None
) 3. AwayTeam
is defined. If so, we print the details. If not, skip it.