ASP.NET Core の C# を F# に書き換える 

私は普段仕事で ASP.NET Core と C# で開発しているのですが，先日[この F# for C# programmers という動画](https://www.youtube.com/watch?v=KPa8Yw_Navk)を流していたら，何となく自分も C# を F# に書き換えたくなってきました:sunglasses:。
元々，関数型言語には興味があり，Haskell などを勉強していたこともあるのですが，本格的に使うには至らず (尤もその考え方は OO 言語での開発にも役立っていると思います)。F# は正直全然使ったことがなく (当然周りも誰も使ってない)，何となく敷居の高さを感じていましたが，上記の動画を見て，意外とできそうと錯覚？したので，まずは既存の API を F# で書き直してみようと思い，やってみました。具体的には，

 - Web API (ビューなし)
 - Entity Framework Core を使って SQLite にアクセス
 - ASP.NET Core にデフォルトで導入されている DI を使用

なお Visual Studio 2017, .NET Core 2.2 を使用しています。

最後にちょっと疑問？に思ったことも書きます。

# F# のプロジェクトを作成
Visual Studio であれば，ASP.NET Core の WebAPI プロジェクトをテンプレートから作れます。コマンドであれば，`dotnet new webapi -lang F#` でしょうか。

# モデルクラス及び DataContext を作成
ここからは既存の C# プロジェクトをベースに作っていきました。
Models フォルダを作成してモデルクラスを作成。複合キーテーブル用です。なおクラス名やフィールド名は実際の (書き換え対象である) C# のものとは変えてます。

```fsharp:Models/TestModel.cs
namespace FsharpTest.Models

open System

type [<CLIMutable>] TestModel =
 {
    Key1: int
    Key2: int
    Value1: decimal
    Value2: decimal
 }
```

データコンテキストクラスの作成。C# とほぼ同じですが簡単な文法に意外と苦戦しました。変更可能な変数は mutable を付けるとか，戻り値を棄てる場合は ignore というものがないといけないとか。複合キーの設定も，C# だと new {c.key1, c.key2} と匿名クラスオブジェクトを作る感じでしたが，F# だと :> Object という顔文字っぽい記号でやってやる必要がありました:slight_smile:。

```fsharp:Data/FsharpDataContext.fs
namespace FsharpTest.Data

open Microsoft.EntityFrameworkCore
open FsharpTest.Models
open System

type FsharpDataContext(options: DbContextOptions<FsharpDataContext>) =
    inherit DbContext(options)

    [<DefaultValue>]
    val mutable _TestModel: DbSet<TestModel>
    member x.TestModel
        with get() = x._TestModel
        and set v = x._TestModel <- v
    override x.OnModelCreating(builder : ModelBuilder) =
        base.OnModelCreating(builder)
        builder.Entity<TestModel>().HasKey(fun c -> ( c.Key1, c.Key2 ) :> Object) |> ignore
```
そういえば，さらに「リポジトリ」を作る流儀もあるようなのですがここでは作りません。

# サービスクラスのインタフェースの作成

ASP.NET Core になってから初めて DI を使ったのですが，「依存関係逆転の原則」のありがたみを実感しています。
F# でも当然可能なので[インタフェース](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/interfaces)を定義。インタフェースですけどメンバーを abstract キーワードを使って定義するのがちょっと戸惑いました。GetTaple ではタプルを返していますが，(double * double) のようにアスタリスクで区切るのが分からず苦労しました。

```fsharp:Services/Abstract/ITestService.cs
namespace FsharpTest.Service.Abstract

type ITestService =
    abstract member GetDouble : key1:int -> key2:int -> double
    abstract member GetTaple : key1:int -> key2:int -> (double * double)
    abstract member GetDbData : key1:int -> key2:int -> TestModel

```

# サービスクラスの実装

インタフェースを実装します。ここでの FsharpDataContext ものちほど設定する DI でランタイムより渡されます。実装する側で interface キーワードを指定するというのも C# に慣れていると戸惑うところ。
本当の C# の実装はもっと面倒なことをしていますが，ここでは適当な値を返します。(C# での) タプルを返す時でも括弧なしでよいようです。関数 (といってもこの場合はメソッドか) の定義で括弧を使わなくていいのは関数型っぽいですね(￣ー￣)。

```fsharp:Services/TestService.cs
namespace FsharpTest.Service

open FsharpTest.Service.Abstract
open FsharpTest.Data

type TestService(context: FsharpDataContext) =
    interface ITestService with
        member this.GetDouble key1 key2 = 0.1
        member this.GetTaple key1 key2 = 0.1, 0.2
        member this.GetDbData key1 key2 = this.context.TestModel.First()

    member this.context with get() = context

```

# データコンテキストとサービスクラスの DI

Startup.fs を編集し，データコンテキストとサービスクラスを Inject します (「追加↓」～「追加↑」以外の部分は namespace を読み込む open 以外手を付けていないので省略)。前述の通り DB は SQLite に Entity Framework Core で接続します。
実は，C# で使えていた GetConnectionString メソッドだと何故か値が取れず…。F# だからではない別の原因の可能性も高いですが，ひとまず GetSection メソッドで appsettings.json の設定値を取っています。

```fsharp:Startup.fs
type Startup private () =
    new (configuration: IConfiguration) as this =
        Startup() then
        this._configuration <- configuration

    // This method gets called by the runtime. Use this method to add services to the container.
    member this.ConfigureServices(services: IServiceCollection) =
        // 追加↓
        services.AddDbContext<FsharpDataContext>(
            fun options -> (options.UseSqlite(this._configuration.GetSection("ConnectionString:SQLiteConnection").Value) |> ignore) )
            |> ignore
        services.AddTransient<ITestService,TestService>() |> ignore
        // 追加↑
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) |> ignore

    // (中略)

    member val _configuration : IConfiguration = null with get, set
```

# コントローラで DI されたサービスクラスを受け取る
紹介する順番がこれで妥当なのか自信がありませんが(汗)最後にコントローラです。
これも C# と同じく DI したサービスクラスを受け取ることができます。

```fsharp:Controllers/TestController.fs
namespace FsharpTest.Controllers

open Microsoft.AspNetCore.Mvc
open FsharpTest.Service.Abstract

[<Route("api/[controller]")>]
[<ApiController>]
type TestController (service: ITestService) =
    inherit ControllerBase()
        member this.service with get() = service

    [<HttpGet>]
    member this.Get(key1:int, key2:int) =
        let value = service.GetDouble key1 key2
        ActionResult<double> value

```

以上でビルドして普通に C# と同じ動作をしました (SQLite ファイルは C# のを使いまわしているので新規作成していません)。

# 終わりに
ということで C# を F# に書き換えることはできました。感触としては，Web API であれば今まで C# で書いていたものを今後ほぼ全て F# で書くことも可能なのかなという気がしています。ビュー (Razor) についてはよく分かっていませんが。 

ただ…DI 関連のことを書いているくらいから気づいていたのですが，これって F# である必要全然なくない？という気持ちにもなりました。結局，定義しているのはクラスであり，インスタンスが生まれているのであり，「関数」ではなく「メソッド」なのです。あまり関数型らしいことをしてないのでは，と。第一 DI 自体が，オブジェクト指向のポリモルフィズムを利用したものですしね。
勿論善悪の問題でもないし，これができるのも F# の長所なのでしょうけど。

とはいえ，自分の中の F# に対する敷居は確実に低くなったので，ASP.NET Core の C# を F# に書き換えてみるのはオススメです！

今後は，[MSDN Magazine (2019/9)](https://msdn.microsoft.com/ja-jp/magazine/mt833511) でも紹介されていた [Giraffe](https://github.com/giraffe-fsharp/Giraffe) などを試してみるのと，F# の文法から少し勉強して関数型らしいコーディングもできたらと思います。

Qiita 初投稿でした:v:。