Hello everyone, today I bring you some F# 5.0 preview niceties FSharp Conf was last Friday (June 5th, 2020) and there were many many awesome #fsharp talks 100% recommended, if you are into web development there are a couple of talks for you:
- SAFE Stack – The Road Ahead - Isaac Abraham
- From Zero to F# Hero - James Randal
That being said... today's content post is little different I'll talk about the nuget package references for F# scripts (fsx files), you can read more about the new F# 5.0 features here.
In the past if you wanted to script with F# you needed to have the library code downloaded and reference the dll file directly
#r "../libs/MyLib.dll"
open MyLib
/// do code with MyLib
F# 5.0 is introducing the nuget
references now if you want to reference a library it's pretty simple for example
#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json
let o = {| X = 2; Y = "Hello" |}
printfn "%s" (JsonConvert.SerializeObject o)
that will download the Nuwtonsoft.Json package (in a transparent way for the user) and add the referenced library, after that you will be able to open any namespaces that the library provides.
I feel that gives you a better experience when you want to create some scripts either for quick usage, prototyping or even API exploration as we will see.
Recently Microsoft released a new nuget package: Microsoft.Windows.Sdk.NET
which exposes in a pretty nice way the native WinRT API's from Windows. The same API's you would use from UWP Apps the most modern stuff is in there
The C#/WinRT Language Projection
C#/WinRT provides Windows Runtime (WinRT) projection support for the C# language. A "projection" is an adapter that enables programming the WinRT APIs in a natural and familiar way for the target language. The C#/WinRT projection hides the details of interop between C# and WinRT interfaces, and provides mappings of many WinRT types to appropriate .NET equivalents, such as strings, URIs, common value types, and generic collections.
WinRT APIs are defined in *.winmd
format, and C#/WinRT includes tooling that generates C# code for consumption scenarios, or generates a *.winmd
for authoring scenarios. Generated C# source code can be compiled into interop assemblies, similar to how C++/WinRT generates headers for the C++ language projection. This means that neither the C# compiler nor the .NET Runtime require built-in knowledge of WinRT any longer.
Motivation
.NET Core is the focus for the .NET platform. It is an open-source, cross-platform runtime…
I've done some WinRT stuff in the past with UWP applications and I have always felt that the WinRT API is just so nice to work with and remembering that everything you do with it is 100% native.
What can you do with the WinRT projection?
Anything that does not require some sort of UI, if you want to use an API that requires a CoreWindow or that it executes on the UI thread then you would need to implement some interfaces for your application as noted here everything else that is UI-less is good to go, and as an example: In this repository I made a small Avalonia App that leverages the WinRT API. It includes examples for the following API's
- Windows.Media
- Windows.Networking
- Windows.System.Power
AngelMunoz / WinRTFs
A small showcase of some of the WinRT APIs that can be used thanks t the C#/WinRT projection https://github.com/microsoft/CsWinRT
WinRT + F#
In the last build event (May 2020) one project was shown C#/WinRT which is a projection of the WinRT API over C#, this projection is compatible with .netstandard2.0 and .net5 (once it's released). This is not the first time an attempt to expose the WinRT API to Win32 apps has been made, the last one was SDK Contracts and while you could use most of the WinRT APIs it had some limitations around certain APIs like Bluetooth and if you were an F#'er like me, you were in bad luck because the SDK Contracts didn't even allow your project to compile that stuff is now past and the next iteration (which I believe is a better take) is here.
The projection is also available for C++, Rust and Python.
Samples
Check the Core project where I tried to put most of the WinRT API code
- …
And... after half day reading... Finally some code.
The first Example is a small gist that uses the
Windows.Storage
API to take 5 files from the the user's music library and check the its music properties.
the code is very short and is async heavy the reason for that is there are a lot of Async APIs in WinRT and here's Larry Osterman explaining why
Fortunately the System namespace includes a nice extension method that converts an IAsyncAction to a usual Task, then we use some F# Async functions to make it work seamlessly in F#.
KnownFolders
.MusicLibrary
.GetFilesAsync()
.AsTask() |> Async.AwaitTask
Here we use the MusicLibrary IStorageFolder to get its files in a very simple way the IStorageFolder
contains some nice properties and methods that we can leverage to either create/delete/update new files or directories
for file in files do
file
.Properties
.GetMusicPropertiesAsync()
.AsTask() |> Async.AwaitTask
In this one, we just iterate over the files to read properties on each of them you can read more about the StorageFile
type here
And that's it. The WinRT API is at your fingertips reach now available with F#!
For the Full reference on these API's you can check this link
https://docs.microsoft.com/en-us/uwp/api/
Bonus
What about a terminal music player prototype?
As noted here...
you can actually write a PoC of a media player in less than 50 LoC with F#!
If you add some lines for the System Media Transport Controls
(the ones that give you info when you turn up/down the volume, change or play/pause the song) and input management, you can actually write a better PoC in less than 100 LoC
To run this sample type
dotnet fsi --langversion:preview media-player.fsx
(or whatever the name of the file is), also don't forget to download .net5 preview
Let's go bit by bit
#r "nuget: Microsoft.Windows.Sdk.NET, 10.0.18362.3-preview"
open System
open Windows.Media
open Windows.Media.Core
open Windows.Media.Playback
open Windows.Storage
open Windows.Storage.FileProperties
open Windows.Storage.Search
open Windows.Storage.Streams
Nothing fancy so far, we are just opening the namespaces that contain the types and classes we will use along the way
let asyncGetFiles () =
async {
let queryOpts = QueryOptions()
queryOpts.FileTypeFilter.Add(".mp3")
let query =
KnownFolders.MusicLibrary.CreateFileQueryWithOptions(queryOpts)
let! files = query.GetFilesAsync().AsTask() |> Async.AwaitTask
return files |> Seq.take 5
}
Here we define the function asyncGetFiles
inside we do a query to get only .mp3
files also the Windows.Storage.Search API contains some nice classes to create complex queries on files as well as give you a number of ways to present that information to you.
Like in our first example we get the files from the music library in an asynchronous way. Since this is a prototype we just take 5 but we could bring the entire in a single trip if we wanted to.
The next section is quite large so I'll add the explanation as comments as we go by
let asyncGetPlaylist =
async {
let! files = asyncGetFiles ()
/// create a new media playlist
/// this will be the source for our player later on
let playlist = MediaPlaybackList()
for item in files do
let! thumbnail =
item
.GetThumbnailAsync(ThumbnailMode.MusicView)
.AsTask() |> Async.AwaitTask
let! musicProps =
item
.Properties
.GetMusicPropertiesAsync()
.AsTask() |> Async.AwaitTask
/// let's create a media source for each file we found
let source = MediaSource.CreateFromStorageFile item
/// create the items from the sources
let mediaPlaybackItem = MediaPlaybackItem source
/// this part is important only if you want a nice
/// integration with the OS, but is not really necessary
let props = mediaPlaybackItem.GetDisplayProperties()
props.Type <- MediaPlaybackType.Music
props.MusicProperties.Title <- musicProps.Title
props.MusicProperties.AlbumArtist <- musicProps.AlbumArtist
props.MusicProperties.AlbumTitle <- musicProps.Album
props.MusicProperties.Artist <- musicProps.Artist
props.MusicProperties.TrackNumber <- musicProps.TrackNumber
props.Thumbnail <- RandomAccessStreamReference.CreateFromStream thumbnail
/// once you have set the properties don't forget to apply them
/// otherwise these will not show in the SMTC
/// (System Media Transport Controls)
mediaPlaybackItem.ApplyDisplayProperties(props)
/// finally add the mediaplayback item to the playlist
playlist.Items.Add mediaPlaybackItem
return playlist
}
Now, it may seem convoluted having to create a source for the item then a source for the playback item then adding it to the playlist but here's the cool thing they are abstractions that will allow you to control the playlist and the player in an easy way without worrying what is the actual source of the media you will play, because you can play physical Video, Music, and even Streams the source can be in your hard drive or can come from the internet the API is pretty flexible.
let player = new MediaPlayer()
async {
let! playlist = asyncGetPlaylist
player.Source <- playlist
player.Play()
}
|> Async.RunSynchronously
This part is pretty simple I believe, just get the playlist, assign it to the player, then start playing.
The rest it's just to be able to change songs and pause/play by reading keystrokes
let hmsg = "Press q to quit, up arrow to play or pause and arrows to move previous or next"
printfn "%s" hmsg
let mutable key: ConsoleKeyInfo = Console.ReadKey()
let playlist = player.Source :?> MediaPlaybackList
while key.Key <> ConsoleKey.Q do
match key.Key with
| ConsoleKey.RightArrow ->
playlist.MoveNext() |> ignore
| ConsoleKey.LeftArrow ->
playlist.MovePrevious() |> ignore
| ConsoleKey.UpArrow ->
match player.CurrentState with
| MediaPlayerState.Paused -> player.Play()
| MediaPlayerState.Playing -> player.Pause()
| _ -> ()
| ConsoleKey.H -> printfn "\n%s" hmsg
| _ -> ()
key <- Console.ReadKey()
player.Dispose()
exit(0)
when you have a player in memory you will be only able to Play/Pause if you want to stop completely you need to dispose the player
player.Dispose()
that will also release the SMTC integration you may have had added.
This is a prototype that if you feel confident enough you may even be able to just copy paste the functions into a console application with a few more tweaks and you have a terminal player MVP if you want to go crossp-platform you may want to use LibVLCSharp instead 😁
Closing thoughts
So... Yeah! using F# 5.0 scripts to prototype and explore API's is a blessing. Once F#5.0 is GA (Generaly Available) you could also set a few snippets of code to showcase how simple is to use F# to do complex things (like some of the stuff you can do with the WinRT API) if you want to get some of your Colleagues to adopt F#
If you have any doubts/comments please feel free to write them below or to ping me on twitter 😁 thanks for the time you spent here.
Top comments (3)
Just stumbled upon this fascinating exploration of using F# 5.0 scripts to delve into WinRT APIs. Kudos for breaking down the nuget references and showcasing WinRT's power. The terminal music player prototype is the cherry on top! How does it work on windows 11 professional? I just installed it on my laptop. As a fellow explorer, I'm curious if anyone's cooked up more experiments with F# and WinRT. Share your thoughts!
It changed a little bit, today the contracts are not the preferred way to access these APIs, from dotnet 6 and above you can access them via the target framework of a dotnet application as an example
This is a more recen example and it is equally simple however, this is for Speech Recognition: github.com/AngelMunoz/Spiko
Big kudos for showcasing the potential of F# with WinRT APIs! The step-by-step nuget reference integration is super helpful for beginners like me. Also, the terminal music player? Genius! Works like a dream on my Windows 11 Pro laptop. Anyone experimenting with other creative projects using F# and WinRT? Would love to see more!