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 🙌