5分で眺める F# 入門(1) トランプカードと判別共用体

F# の良さを示す短いサンプルコードを書きました。トランプゲームを題材とする「判別共用体」機能の概略です。

## (1/4) 網羅的なパターンマッチ

カードのスート(柄)を表す Suit 型を「判別共用体」として定義します。ここでは C# などの enum と同様です。

```fsharp
type Suit =
    | Spade
    | Clover
    | Heart
    | Diamond
```

使用例として、スートの名前を取得する関数を定義します。

```fsharp
let suitToName suit =
    match suit with
    | Spade     -> "スペード"
    | Clover    -> "クローバー"
    | Heart     -> "ハート"
    | Diamond   -> "ダイヤ"
```

`match...with` は `switch` のような場合分け構文です。**場合分けに漏れがあるときはコンパイル時に警告する** という機能があります。

まずは正常な動作例を見ます:

```fsharp
printfn "%s" (suitToName Spade) //=> スペード
```

そして、コンパイル時の警告も見てみましょう。Diamond が渡されたときのケースをコメントアウトして、ビルドしてみます。

```fsharp
    match suit with
    | Spade     -> "スペード"
    | Clover    -> "クローバー"
    | Heart     -> "ハート"
    // | Diamond   -> "ダイヤ"
```

エディタやビルド出力にこんな警告が出ます:

```
warning FS0025:
    この式のパターン マッチが不完全です
    たとえば、値 'Diamond' はパターンに含まれないケースを示す可能性があります。
```

## (2/4) フィールドを持つ判別共用体

次に、トランプのカードを表す Card 型を「判別共用体」として定義します。判別共用体の各ケースは、普通の enum と違って、フィールドを持つことができます。

```fsharp
type Card =
    | NormalCard of suit:Suit * rank:int
    | Joker
```

`of` の後ろにある `suit:Suit` などがフィールドの定義です。(`*`で区切る。)

この定義には次の2つの事実がほぼそのまま書かれていることが見て取れると思います。

- 普通のカード(NormalCard)はスート(柄)とランク(数字)を持つ
- ジョーカー(Joker)はフィールドを持たない

フィールドを持つケースはクラスのように、コンストラクタでインスタンス化できます。

```fsharp
let heart3 = NormalCard (Heart, 3)
```

使用例として、カードの名前を取得する関数を定義します。場合分けしてケースごとに実装を書くのは上と同じです。

```fsharp
let cardToName card =
    match card with
    | Joker ->
        "ジョーカー"

    | NormalCard (suit, rank) ->
        let suitName = suitToName suit
        let rankName = string rank
        suitName + "の" + rankName
```

実行例:

```fsharp
printfn "Joker: %s" (cardToName Joker) //=> ジョーカー
printfn "Heart3: %s" (cardToName heart3) //=> ハートの3
```

「判別共用体」の使い道はいくらでも見つけられます。

```fsharp
type HttpResponse =
    | OkWithText    of text:string            // 200
    | OkWithJson    of json:obj               // 200
    | Redirect      of uri:string * temp:bool // 301 or 302
    | InternalError of ex:exn                 // 500

type BinaryTree<'T> =
    | Node of left:BinaryTree<'T> * right:BinaryTree<'T>
    | Leaf of value:'T

type Contact =
    | ContactWithMail
        of address:string

    | ContactWith郵送
        of 郵便番号:string * 住所:string * 名前:string
```

## (3/4) 型推論

ところで、上で定義した関数は **引数や結果の型を全く書いていない** のにコンパイルできました。しかし、動的検査というわけではありません。型エラーがあれば、コンパイルエラーとして報告されます。

信じられない人は、これをコンパイルしてみてください。

```fsharp
printfn "%s" (cardToName "ジョーカー")
//                       ^^^^^^^^^^ 本来は Card 型の定数 Joker が正解
```

※public な関数の型は書きましょう。

## (4/4) まとめ

本稿が主張する F# の良さは **簡潔さ** です！ 判別共用体を抽象クラスと継承で定義したら、なかなかのコード量になるのではないでしょうか。

他にもたくさん良い点がありますが、そろそろ時間なのでここまで。わずかでも興味を持っていただけたら幸いです。

## おまけ: 練習問題

**ブラウザ上で F# を試すことも可能** です: [try.fsharp.org で本稿のコードを試す](https://try.fsharp.org/#?code=PTAEG0FZDAlRzBkLndCBDIaIZQDEDEpCmioANSAUAjAJSiATDIJcMgzwyDrDINUMgPwyCTDIBYMgJEqCkSoI6KgFK6DK8gF18ACwAuogA4BnAFwgAjgEtFogIYA6AMYB7ALbAAbqsUA7AAwAPYCoCmuqcADMkABwuARgHYAbACYb7u6Ers7uLpq+xABQUbraACYArgA2NqAACgBO2gDmmaq6MaIAnhJpAMqJKqAAvFGgDaAAPqDlEqrxNvWNLQDCydoGNpndDS0AEjaqmaKjzaAAIooF2ibxMamioFJVogAq2gByBWk71XWNoLqqoprC27ugAO4qwnMtbR1plwC0AHygABEgE6GQBbDAxgYD3qB+oNhr8AYDAPUMgFuGBiABYYGFDLhMpjNLqB-kDAPMMDDI2J6i2WcTWjSJgMAAwyAEoZAEUMgCOGKhMKFRCSZUyiABmJiBAFIpIDQPgzvsjidWu1OqQQDUAWDmEVSmletN4rVoYdtJlrsltZlddoBQ8VDJKtUAFSgfImADWMn50IAUtpncMNjYtsI8aJHLVQAajaoTTrJZNpqIADSgRzRKKbUCaHUHY66NIZs16y7XW73PO6l6iN440Ben2ZQl-OaXQGADoZAOcMDEY3Kr4eNpt1Ut2iadztI-0bjTT0uzaRqVpl07n44aaeHC9nUlEfJMOUdqhdS7nC4A1EDAHYMEpPq5OMV5-KFQJrwxkoDFEvwpazcsfmSVwBVoDbDsGB5LdBWFQFYxmRxn1fSUP1lHNQEDONk1AZUAVJMhT0cGIQFAW8TDA0VxTgzMELSFt207FM8J+Oj6IYximOYljWLY9i6KiPDAEGGQBjhkAG4YqGbLiwA4sTxIk8TcLAQAYBkw09AAqGCFAFgGFhAAsIwAuT0AVQZABiGZBAAiGQAxBkAawZAHUGQAzBkAfwYjJAu9wMg0QAEEYJI98yIXfAe0jPsYyDRMSGIX9-3kwhpNAOSyVPQBahioQAGhlUzTdIMkyLOswzbMI+8IKDABpFy33gjyvKjfN8Ac-zk0CtC-wwyLCBwkTQEAJYYqDIGRU39XcXU-RDhwLRoizuLrnWeV5oUIesgSUyFoQAfUmwFptgQB7BkAKwZABEGKEgA&html=DwCwLgtgNgfAsAKAAQqaApgQwCb2ag4CdMTJcMABwFp0BHAVwEsA3AXgCIBhAewDsw6AdQAqAT0roOSAMb9BAzoIAeYAPThoAbhkhMAJwDOJNgzAAzagA4OeQhqy5EhAEY9sYu6mBq3HvD6asEA&css=Q)

かんたんな練習問題もつけておいたので、興味がある人はやってみてください。

```fsharp
// 「ハートのエース」と表示されるようにしてみよう
printfn "HeartA: %s" (cardToName (NormalCard (Heart, 1))) //=> ハートの1

// 「ハートのキング」と表示されるようにしてみよう
printfn "HeartK: %s" (cardToName (NormalCard (Heart, 13))) //=> ハートの13

// ヒント:
let rankToName rank =
    match rank with
    | 1 -> "エース"
    | _ -> "エースではない"
```

([try.fsharp.org で解答例を見る](https://try.fsharp.org/#?code=PTAEG0FZDAlRzBkLndCBDIaIZQDEDEpCmioANSAUAjAJSiATDIJcMgzwyDrDINUMgPwyCTDIBYMgJEqCkSoI6KgFK6DK8gF18ACwAuogA4BnAFwgAjgEtFogIYA6AMYB7ALbAAbqsUA7AAwAPYCoCmuqcADMkABwuARgHYAbACYb7u6Ers7uLpq+xABQIKCAgwyAxwyANwxUgB0MgHYMgHtqgCregNHysICaDIBADIB3uoAOroD2DKBExAA01ZGg6YAXCdBFUVG62gAmAK4ANjagAAoATtoA5qOquh2iAJ4SQwDKvSqgALxRoDugAD6gyxKq3TbbuwcAwv3aBjaj5zsHABI2qqOij-ugACKKM9oTN0OoNRKApGtRAAVbQAORmQwh6y2u1AulUok0wnBkNAAHcVMIvgcjichqiALQAPlAACJAJ0MgC2GBj02nE0DXW73Sk02mAeoZALcMDEACwwMNmol5vD6o0DUumAeYYGGRxRdfv8ukDdnLaYABhkAJQyAIoZAEcMVCYbKiElGplEADMTHSAKRSWnVJHQuEIw7HU6kEAbGlM5hzRZDS7vbqbdmw7SjdH9MOjCPaG04lQyVbrABUoGmJgA1jJreyAFLaPP3EE2MHCKWiRybUDR2OqePh6qvd6ieqOaJRUE51T5mHw3RDXN5huxGoDoce0egQBFqYAHU0ASQyAX-jAAVKgHUGQB+DFvAPoMX3RmOx4-xhPZhFlvMAFQwslVPUCEK-a1IJQDDDHyH99CL5r3S+T1BgqG-A5CHrbVAFqGKhAAaGUDQAAfX-KRRCtEwJhnPNKzBTRw2HT1cMTSNUWPLFQEIiMCVEIkJVAUty1Ga8vlRWlUkAc4YGEYc1aKbOMEwjfA3XqcdSGpZjdn7N0RyGDZU3daS5PEnZ+3HBTZPHfD5zPKdGhQtCMMAaQZAEiGTDNKGQBrBkASE1ABezXcDyUuSFIAajpdIXRc1SEQ6S1rTtOl6PuGRQCdF18Aosy6LLe5fWAf1QHYziGAtNDbXtWkOw+RwgpC6pwrnIYa07HtQD9GlFTIdJHA6WIKVqur6oaxqmualrWra2qYjARIUlSTrZXagbBqGgbqrAQAYBnK9I73pQBYBhYQALCMALk9AFUGQAYhmQQAIhkAMQZzK3QAzBkAfwZtuS3y0oy0QAEFsudXK8Py6peJbfj21reoSGIGK4sm6bRtACalXSaCYLmpa1s2nb9qOraTpMVK6XOgBpa7QryhT8Ee1siPwc63p7D6StisqAaBqIgA&html=DwCwLgtgNgfAsAKAAQqaApgQwCb2ag4CdMTJcMABwFp0BHAVwEsA3AXgCIBhAewDsw6AdQAqAT0roOSAMb9BAzoIAeYAPThoAbhkhMAJwDOJNgzAAzagA4OeQhqy5EhAEY9sYu6mBq3HvD6asEA&css=Q))

## もっと F# を知りたい人へ

- [F# を知ってほしい](https://qiita.com/cannorin/items/59d79cc9a3b64c761cd4)
    - 詳細度の高い F# 概要
- [DDDを.NETでやるならドメイン層はF#が良いんじゃないか、という話](https://qiita.com/kwhrkzk/items/0868b569b12046eaebad)
    - F# とドメイン層の型定義の相性について
- [F#リファレンス](https://midoliy.com/content/fsharp/index.html)
    - @midoliy 氏による F# の言語機能のリファレンス (非公式。[公式](https://docs.microsoft.com/ja-jp/dotnet/fsharp/) は機械翻訳が多くて……)
- [Ionide - Crossplatform F# Editor Tools](http://ionide.io/)
    - ionide = F# の開発ツールを作ってる人々
- GitHub にある本稿のサンプルコード [2020-02-09-fsharp-goodness-playing-card/Program.fs](https://github.com/vain0x/examples/blob/master/2020-02-09-fsharp-goodness-playing-card/Program.fs)