.NET Interactive NotebooksとF#の相性はバツグンだ！

この記事は [F# Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fsharp) の3日目の記事です。


# .NET Interactive Notebooks とは

乱暴な言い方をすると、 Jupyter Notebooks の .NET 版を Visual Studio Code で使う拡張機能となります。

ノートブック内では F#、C#、Powershell といった.NET言語を使ったコーディング、その出力結果の可視化、markdown のドキュメントの埋め込みが可能です。

# 動作環境

本記事は以下の環境で動作確認した内容に基づいて記載しています。

- Windows10 Pro 20H2
- Visual Studio Code 1.52.0 - Insider

なお、同環境上のWSL2(Ubuntu 18.04)でも以前動作させた実績があります。

# 準備

.NET Interactive Notebooks は現在 Visual Studio Code Insiders (VSCodeのプレビュー版)でしか使えません。

まずは https://code.visualstudio.com/insiders/ でインストーラーもしくはパッケージをダウンロードして VSCode Insiders をインストールします。
Windows版、Mac版、Linux版が用意されています。
![2020-12-03-08-44-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/519d9424-a7dc-30c2-f202-c4e8230c7917.png)


次に VSCode Insiders を開き、拡張機能で適当に `notebooks` などで検索すると出てくる .NET Interactive Notebooks をインストールします。
![2020-12-03-08-45-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/1bb9198b-c4c0-210e-6f63-0d8e89d8913c.png)

これで .NET Interactive Notebooks はインストールできましたが、F# でのコーディングをするので Ionide-fsharp もインストールしておくとよいでしょう。
`fsharp` などで検索すると出てきます。
(Ionide-fsharp が依存している C#拡張もインストールが必要になります)
![2020-12-03-08-51-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/a065af86-e993-4ba2-3808-5baab51c7992.png)


# Hello World!

とりあえずお約束の `Hello World!` を表示させてみましょう。

コマンドパレット(Ctrl+Shift+P)から `.NET Interactive: Create new blank notebook` を選択します。
![2020-12-03-16-53-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/df26177f-1de6-c1c2-e0a4-2d92a321e606.png)


すると拡張子が .ipynb のドキュメントが新規作成されます。 .ipynb は Jupyter Notebooks のファイルの拡張子です。
![2020-12-03-08-46-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/dbeeb7e3-131b-7540-b776-47189761b78f.png)

ノートブックの先頭にある空のエディタ部分にコードやドキュメントを書いていくことになります。
今回は F# でコーディングをしたいので、エディタ部分の右下の言語選択を C# から F# に変更します。
![2020-12-03-09-15-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/4255b9c2-f785-eec1-1de5-885e310b08f1.png)
![2020-12-03-09-19-42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/42343dfa-31fa-a953-1145-e7beb327fa33.png)

エディタ部分に F# のコードを書いて「Alt + Enter」を入力すると、コードが実行されて結果の値がエディタの下に出力され、新たなエディタセルが追加されます。
(なお、「Ctrl + Enter to run」と書かれていますが、私の環境では「Alt + Enter」で実行でした)
`printf` などの出力関数を使わなくても、一連のコードの最終的な結果の値が自動的に表示されます。
![2020-12-03-09-40-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/fa3f7a79-8b48-1c9f-c777-c076ee16bbc8.png)

# markdownドキュメントの埋め込み

ノートブックにドキュメントやコメント的なmarkdown文書を埋め込むこともできます。
言語選択でmarkdownを選択し、エディタセルの中に普通にmarkdownを記述して同じく「Alt + Enter」を入力するとmarkdownが確定されてリッチテキストとして表示されます。
markdown記述中にもプレビューウィンドウを開いて見栄えを確認することも可能です。
![2020-12-03-09-43-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/69013a81-a2d4-1de4-3cc5-d7f42d07c833.png)
![2020-12-03-09-46-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/dd62cbeb-bca2-bc18-7f67-83a144912bc5.png)

# パッケージやスクリプトファイルの読み込み

[F# Advent Calendar 1日目の記事](https://qiita.com/Midoliy/items/66a221818bc3901da48f) にもある通り、F#5.0からはF#スクリプトの `#r` ディレクティブでNuGet上のパッケージを直接読み込むことが可能となりました。
.NET Interactive Notebooks でもこの機能に対応しています。
![2020-12-03-11-18-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/38abc44a-421e-e6fc-f7e2-7951f53c635b.png)
ちなみに、上記の例のように構造化された値を出力する際には見やすいように表形式で出力してくれます。
同様に `#load` ディレクティブを使用して他のF#スクリプトファイルを読み込んで、そのスクリプトファイル内で定義された関数やモジュールを呼び出すことも可能です。

# グラフの描画

.NET Interactive Notebooks では [XPlot](https://fslab.org/XPlot/) の [Plotly](https://fslab.org/XPlot/plotly.html) によるグラフ表示をサポートしています。

例として、Spotifyから日本で先週1週間で再生数トップ200の曲のテンポをヒストグラムとして表示してみました。
以下のサイトのデータおよびパッケージを使用しています。

- トップ200のリストのCSVファイルは [Spotify Charts](https://spotifycharts.com) から取得しました
- CSVファイルのパーズには [FSharp.Data](https://fsharp.github.io/FSharp.Data/index.html) の [CSV Type Provider](https://fsharp.github.io/FSharp.Data/library/CsvProvider.html) を使用しました
- 各曲のテンポのデータは [SpotifyAPI-NET](https://johnnycrazy.github.io/SpotifyAPI-NET/) を使用して取得しました

```fsharp
#r "nuget: FSharp.Data"
#r "nuget: XPlot.Plotly"
#r "nuget: SpotifyAPI.Web"

open FSharp.Data
open XPlot.Plotly
open SpotifyAPI.Web
open System

type TopMusicCsv = CsvProvider<"regional-jp-weekly-latest.csv">
let topMusicCsv = TopMusicCsv.GetSample()
let topMusicIds = topMusicCsv.Rows |> Seq.map (fun r -> Uri(r.URL).Segments |> Seq.last)

let config =
  SpotifyClientConfig.CreateDefault()
  .WithAuthenticator(ClientCredentialsAuthenticator("<client id>", "<client secret>"))
let spotify = SpotifyClient(config)
let tempos =
  topMusicIds |> Seq.map (fun musicId -> async {
    let! resultAsync = spotify.Tracks.GetAudioFeatures musicId |> Async.AwaitTask |> Async.StartChild
    do! Async.Sleep 500
    let! result = resultAsync
    return result.Tempo
  }) |> Async.Sequential |> Async.RunSynchronously

let layout = Layout(title = "Japan Top 200 Musics", xaxis = Xaxis(title = "tempo"))
Histogram(x = tempos, xbins = Xbins(size = 5))
|> Chart.Plot
|> Chart.WithLayout layout
|> Chart.WithWidth 700
|> Chart.WithHeight 500
```

![2020-12-03-15-35-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/abe75b3a-7afb-7045-e74f-a97da75d84b0.png)

# 画像の表示

ビルトインの `display` 関数を使うことでHTMLを表示することが可能なことを利用して、 `img` タグに画像のデータを埋め込んで表示させることができます。
画像ファイルを表示する関数と、OpenCVの `Mat` を表示する関数の両方を実装してみました。

```fsharp
#r "nuget: OpenCVSharp4"
#r "nuget: OpenCvSharp4.runtime.win"

open OpenCvSharp
open OpenCvSharp.Extensions
open System
open System.IO
open System.Drawing.Imaging

let displayBase64 (mime: string) (base64String: string) =
  let url = $"data:{mime};base64,{base64String}"
  display(HTML($"""<img src="{url}" />"""))

let displayImageFile (file: string) =
  let ext = Path.GetExtension(file).ToLower()
  let mime = match ext with ".jpg" | ".jpeg" -> "image/jpeg" | ".png" -> "image/png" | ".gif" -> "image/gif" | other -> failwith $"{other} is not supported."
  let bytes = File.ReadAllBytes(file)
  let base64String = Convert.ToBase64String(bytes)
  displayBase64 mime base64String

let displayMat (mat: Mat) =
  use bitmap = mat.ToBitmap()
  use stream = new MemoryStream()
  bitmap.Save(stream, ImageFormat.Jpeg)
  let base64String = Convert.ToBase64String(stream.ToArray())
  displayBase64 "image/jpeg" base64String
```

`displayMat` 関数を使用してOpenCVの `Mat` を表示してみた例です。
これでOpenCVを使用した画像加工後に加工後の画像を表示して確認が可能になります。
![2020-12-03-16-37-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34451/e4e10a7a-6c01-c80d-cc77-ead0b269ed3e.png)

# まとめ

.NET Interactive Notebooks を使うことでデータの加工および表示をアドホックに行うことができます。

F# には [FSharp.Data](https://fsharp.github.io/FSharp.Data/index.html) のような強力なデータ加工用パッケージが存在しており、静的型付けや [Units of Measure](https://qiita.com/adacola/items/b65752b678e81bc8e354) などと組み合わせて、お手軽さ・堅牢さ・強力さ・高速さを兼ね備えた各種データ処理が可能です。

ぜひこの機会に .NET Interactive Notebooks を使用した快適なスクリプティングを！