C#で書いたGoogle Chatに猫画像を投稿するだけのコマンドラインツールをF#初心者がF#に移植してみた

# はじめに

.NET FrameworkのC#でASP.NET開発をしているIosifです。

今回は、同じ.NET言語でありながらなかなか触る機会のないF#で、過去に書いたGoogle Chatに猫画像を投稿するだけのコマンドラインツールを書いてみました。なお、これまでF#を書いたことはありません。そんな私が無謀にもF#で書いてみました。

# F#とは？

この記事を読めばわかるかと思います。

[F# を知ってほしい](https://qiita.com/cannorin/items/59d79cc9a3b64c761cd4)

要は、.NETで関数型プログラミングができる言語です。

書き味はPythonとRubyとTypeScriptとC#を足して4で割った感じです。

# 書いてみたコードの概要

こんなカードをGoogle Chatに投稿するだけのコードです。

![スクリーンショット 2020-12-22 18.54.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137169/55814f05-6bd6-2235-eee5-307fdf625935.png)

仕様としてはこんな感じです。

1. [random.cat](https://aws.random.cat/)からランダムな猫画像のURLを取得。
2. Google Chatのカードに画像URLを埋め込んで投稿。
3. 画像をクリックするとオリジナルサイズに拡大される。

なお、猫画像を投稿するには投稿先のWebhook URLを`CHAT_URL`という環境変数に設定する必要があります。

# コードの構成

[GitHubのリポジトリ](https://github.com/huideyeren/FsChatPost)をご覧ください。

## 実際のコードの解説

では、実際のコードを見ていきましょう。F#の考え方ができていないところは大目に見てくれると嬉しいです。

なお、書き直す前のC#のコードについては大人の事情で掲載できませんでした。

### 猫画像のURLを取得するコード

```FSharp
namespace FsChatPostLibrary

open FSharp.Data

type CatPictureApiJson = JsonProvider<"https://aws.random.cat/meow">

module CatPictureApi =
    let url = CatPictureApiJson.GetSample().File
```

[F# Data](https://fsharp.github.io/FSharp.Data/ja/index.html)というつよつよライブラリのおかげでURLが一発でとれます。

そう、F# DataのJsonProviderは、取ってきたJSONをパースして型情報を取得できるのです！

しかも、エスケープ処理された文字列を自動でなんとかしてくれるのです！

必要なのはURLだけなので、このような簡潔なコードにすることができました。

C#のコードではJSONをパースしてうんたらかんたらする必要がありましたが、このようにわかりやすいコードになってよかったと思っています。なお、Proxy越えの処理については入れていません。

ところで、このコードの返り値は何なんでしょうか。このコードには、C#ではお馴染みのreturn文なんて書かれていません。では、F#の関数はどうやって値を返すのかというと、なんと最後に評価した値がそのまま返り値になるのです。これとよく似た仕掛けの言語がありましたね。そう、Rubyです。

### Google Chatにカードを投稿するコード

```FSharp
namespace FsChatPostLibrary

open System
open FSharp.Data
open FSharp.Data.HttpRequestHeaders

module PostToGoogleChat = 
    let chatUrl = Environment.GetEnvironmentVariable("CHAT_URL")
    let catUrl = CatPictureApi.url
    let card = $""" {{
                     "cards": [
                         {{
                             "header" : {{
                                 "title": "猫画像",
                                 "subtitle": "癒やしの猫画像をお届けします。"
                             }},
                             "sections": [
                                 {{
                                     "widgets": [
                                         {{
                                             "image": {{
                                                 "imageUrl": "{catUrl}",
                                                 "onClick": {{
                                                     "openLink": {{
                                                         "url": "{catUrl}"
                                                     }}
                                                 }}
                                             }}
                                         }},
                                         {{
                                             "textParagraph": {{
                                                 "text": "Photo by <a href='https://aws.random.cat/'>random.cat</a>"
                                             }}
                                         }}
                                     ]
                                 }}
                             ]
                         }}
                     ]
                 }} """
    let result = Http.RequestString( chatUrl, 
                                     headers = [ ContentTypeWithEncoding (HttpContentTypes.Json, Text.Encoding.UTF8) ],
                                     body = TextRequest card)
```

このコードの恥ずかしいところは、猫画像のURLをJSONにF# 5.0の新機能、挿入文字列を使って当てはめているところです。

元々のコードでも挿入文字列を使っていましたが、ここはF#でJSONを作る必要があったと思っています。

なお、Proxy越えの処理と例外処理はしていないので、何かエラーがあると全部落ちます。

### 最初に実行されるコード

```FSharp
// Learn more about F# at http://docs.microsoft.com/dotnet/fsharp

open System
open FsChatPostLibrary

[<EntryPoint>]
let main argv =
    printfn "Result: %s" PostToGoogleChat.result
    0
```

いわゆるC#でのmainメソッドです。

ここは猫画像を投稿するコードを呼びだしているだけなのですが、盲点だったのが最後の0です。実は、F#のmain関数はint型の値を返さなければいけません。本当だったら成功時は0、失敗時は1を返すべきなのでしょうが、ここは0返しで勘弁してください。

なお、ここだけC#で書けばF#とC#の相互運用性がわかってよかったと思うのですが、その場合はその場合で苦戦してそうです。

# よかったところ

まず最初によかったと思うのは、コードの見通しがよくなったことです。JSONを埋め込んでいるコードを除き、個人的にはわかりやすいと感じます。F#は記述量が少なく、また上から下にという流れがあるのでC#に比べて処理の流れを追いやすいのではないかと思います。

それと、今回は.NET 5.0でF# 5.0を使って書いてみました。というか、JSONの作り方がわからなかったのでF# 5.0の新機能を使わざるを得なかったわけですが。おかげで、macで動かすことができました。余裕があれば、WindowsおよびLinuxで動かしてみたいと考えています。

# 苦戦したところ

そもそも関数型プログラミングは初めてだったので、いろいろと概念に悩まされました。これでよいのか、これでよいのかと苦悩しながらいろいろなリファレンスを参照し、見よう見まねで書いてみたのがこれというわけです。

あと、Proxy越えの処理や例外処理などできなかったところもあります。とりわけ、猫画像のURLを取得するコードのところでProxyが使えなかったのが痛いと思っています。例外処理もできていませんし、ここは年明けごろの第2報をお楽しみに、ということでお願いします。

F#の今後の課題としては、資料、とりわけ書籍資料の少なさだと思っています。ネットの記事はよいのですが、体系だった知識を勉強しようとすると古い本しか見当たらない状態です。なので、新しい本があると助かります。もっとも、なければ自分で書くしかないような気もしますが。

# おわりに

この記事を読んだ人がひとりでもF#に興味を持ち、コードを書いてくれれば嬉しいです。