United Kingdom: +44 (0)208 088 8978

Data exploration in F#

This week Akash uses .Net Interactive with F# to see how easy it is to explore data from an API

I've been primarily using F# for web development with the SAFE stack and have been loving the ease of developing full features without the heavy split of frontend vs backend. But recently I'm getting to see that F# is amazing for tasks other than software development.

The ease of data manipulation in F# is on a whole other level. From the many methods that exist on enumerables to the dynamic feel of the language.

Type Providers are one such example, a powerful tool in F#. They encourage data exploration through dot notation which is just a dream. In a few lines I can completely understand a response from an API. Even code my entire UI against it with real time updates should the schema be changed. Something Ryan did that I loved was use JsonProvider on a response from Azure Event Grid to be able to safely access the data without any headache.

In this session I want to try out .NET Interactive! It seems like it'd be a great fit combined with Type providers to empower data discovery.

Setup:

  • VS Code
  • Install the .NET interactive extension
  • Ctrl + shift + p and select a new .NET Interactive workbook I'll be using a .dib
  • Ensure you've selected the F# interactive setting
  • Each code snippet is another entry in the notebook
#r "nuget: FSharp.Data"

open FSharp.Data

type Pokemon = JsonProvider<"https://pokeapi.co/api/v2/pokemon/1">

let bulbasaur = Pokemon.GetSample()

You may see red underlines but click run and they should go away

The type Pokemon has now been inferred from the api, this could alternatively be from a local file or even as a string directly inputted, you can see examples here.

Now we have a Pokemon type defined we can use the Load or AsyncLoad on the other pokemon endpoints and get full type safety.

let pokeApiRoot = "https://pokeapi.co/api/v2/pokemon"

let genOne = [ for index in 1..150 do Pokemon.Load($"{pokeApiRoot}/{index}") ]
    // There are better ways to get the original 150 but this works for our demo
    // https://pokeapi.co/api/v2/pokemon?limit=150

let tallestPokemonInGenOne =
    genOne
    |> List.sortByDescending (fun pokemon -> pokemon.Height)
    |> List.truncate 15

Let's have some fun and calculate the BMI of the top 15 tallest pokemon!

From some extensive research and fact checking I've confirmed the units of measure our API provides for a Pokemon. If we take bulbasaur as an example the API tells us it's height is 7 which corresponds to 0.7 meters and it's weight of 69 is equivalent to 6.9kg.

Time for another F# feature! Units of measure 😍

[<Measure>] type m
[<Measure>] type kg
[<Measure>] type bmi = kg/m^2

let calcBmi height weight =
    let h = float height
    let w = float weight
    let heightMeters = (h/10.) * 1.<m>
    let weightKilos = (w/10.) * 1.<kg>
    weightKilos/(heightMeters * heightMeters)
    // int -> int -> kg/m^2

Now let's take the tallest 15 pokemon and run our calculation:

let pokemonWithBMI =
    tallestPokemonInGenOne
    |> List.map (fun pokemon ->
        let bmi = calcBmi pokemon.Height pokemon.Weight
        {| Name = pokemon.Name; Bmi = bmi |})

pokemonWithBMI.Display()

Just as I expected it's not looking too good for Snorlax.

We can use the capabilities of .NET Interactive and take advantage of Plotly.NET.Interactive.

#r "nuget: Plotly.NET.Interactive, 2.0.0-beta8"

open Plotly.NET

let pokemonSeqNameAndBmi = pokemonWithBMI |> List.map (fun p -> p.Name, p.Bmi)|> List.toSeq

Chart.Column(pokemonSeqNameAndBmi)

It looks like Snorlax takes it by quite a lead followed by Dragonite, we could delve deeper and group the Pokemon by type to see if that has an effect on a Pokemon's BMI. But I'll leave that as an activity for you!

We've gotten to see how flexible F# can be and the great tooling supporting it! .NET Interactive will help people from various backgrounds get into F# making its use more widespread 🙌