F# と遊ぼう！ 関数・タプル・リスト・パターンマッチ

[前回](https://qiita.com/dsudo/items/24e5f9f162d8d2ae4d01)は全くコーディングしませんでしたので，今回は少し遊んでみたいと思います。

## F# Interactive

F# は対話モードで遊べる機能があります。今回はこれを利用します。

```
$ dotnet fsi
```

入力の終了はセミコロン２つ (`;;`) で指定します。通常のコードでも `;` が登場するので，混ざるのを避けるためだと思われます。MySQL でストアドプロシージャを記述するときに一時的にデリミタを変更するのと同じ動機ですね。

最後に評価した結果は `it` という変数にバインド（束縛）されます。
（これに限らず対話モードだと普通に再バインドできるみたいです）

対話モードを抜ける場合は `#q` を指定します。
（[Q#](https://docs.microsoft.com/ja-jp/quantum/language/) みたいで紛らわしいｗ）

参考：
https://docs.microsoft.com/ja-jp/dotnet/fsharp/tutorials/fsharp-interactive/


## 関数

`let` で変数・関数を定義します。

``` fsharp
> let f x y = x + y ;;
val f : x:int -> y:int -> int

> f 2 3 ;;
val it : int = 5
```

う～ん。シンプルに記述でき過ぎて，逆に戸惑うｗ

なんて遊んでいると，職場のＫ先生から「こんなのあるよ」と見せてもらったのがコチラ。

``` fsharp
let f = (+)
```

「ん？何が起こるんですか？これ？？」

教えて頂きました。`x + y` と `(+) x y` が等価ということらしいです。プラスを括弧で囲むと引数を二つとって合計を返す関数になり，それを `f` にバインドしたってことみたいですね。

``` fsharp
> let f = (+) ;;
val f : (int -> int -> int)

> f 2 3 ;;
val it : int = 5
```

`(int -> int -> int)` となるのは，カリー化と部分適用の話のようです。
順番にやるとこうなります。

``` fsharp
> f 2 ;;
val it : (int -> int) = <fun:Invoke@2684>

> it 3 ;;
val it : int = 5
```

まず `f 2` を評価すると，引数を１つ受け取ると `2` を足して返す関数が返ってきます。さらに，これに `3` を渡すと合計した結果 `5` が返ってくるというわけです。
括弧付きだとこうですね。

``` fsharp
> f (2) (3) ;;
val it : int = 5
```

なるほど。
JavaScript だとこんな感じですね。

``` js
> const f = x => y => x + y;
> f (2) (3);
5
```

## タプル

引数２つ取って反転させて返す関数のつもりだけど・・・

``` fsharp
> let swap x y = y x ;;
val swap : x:'a -> y:('a -> 'b) -> 'b

> swap 2 3 ;; // error
```

あれ、、なんか `y` が関数と推論されてるな。

``` fsharp
> swap 2 (fun x -> x + 3) ;;
val it : int = 5
```

あぁ～複数の値を返したいときはタプルにするんですね。

``` fsharp
> let swap x y = y, x ;;
val swap : x:'a -> y:'b -> 'b * 'a
```

タプルの中身を直接バインドできます。
さらに `as` を使うとタプルと中身どっちもバインドできます。

``` fsharp
> let a, b as t = swap "a" "b" ;;
val t : string * string = ("b", "a")
val b : string = "a"
val a : string = "b"
```

うん。ややこしいｗ


## リスト

次はリストで遊んでみます。
（ちなみに配列は要素が可変，リストは不変な感じみたいです）

### 生成

``` fsharp
> let a = [ 1; 2; 3 ] ;;
val a : int list = [1; 2; 3]

> let b = [ 4 .. 10 ] ;;
val b : int list = [4; 5; 6; 7; 8; 9; 10]

> let c = a @ b ;;
val c : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

> let d = 0 :: c ;;
val d : int list = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
```

a. 要素を全て指定
b. 範囲を指定
c. リストの連結
d. 先頭に追加（HEAD と TAIL のパターンマッチ）

### 変換

``` fsharp
> let sq arr = [ for i in arr -> i * i ] ;;
val sq : arr:seq<int> -> int list

> sq [ 1 .. 10 ] ;;
val it : int list = [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
```

Python のリスト内包表記みたいな構文で新しいリストがつくれます。
`sq` はリストを受け取って，各要素を2乗したリストを返します。

お馴染みの `map` や `filter` もあります。

``` fsharp
> List.map (fun x -> x * x) [1 .. 10] ;;
val it : int list = [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]

> List.filter (fun x -> x % 2 = 0) [1 .. 10] ;;
val it : int list = [2; 4; 6; 8; 10]
```

興味深いのは `List.map2` という関数。リストを２つ渡すと zip 的なことができます。

``` fsharp
> List.map2 (fun x y -> x, y) ["a1"; "a2"; "a3"] ["b1"; "b2"; "b3"] ;;      
val it : (string * string) list = [("a1", "b1"); ("a2", "b2"); ("a3", "b3")]
```

長さが違うリストを渡すとエラーになります。

### 集計

``` fsharp
>  List.sum [1 .. 10] ;;
val it : int = 55

> List.average [1. .. 10.] ;;
val it : float = 5.5
```

合計と平均。
`List.average` は `int` だとエラーになるので `float` にしています。`1.` は `1.0` と同義で `float` になります。ちなみに，F# の `float` は C# でいう `double` らしいです。ややこしい。。

``` fsharp
> List.sumBy (fun x -> x * x) [1 .. 10] ;;
val it : int = 385

> List.averageBy (fun x -> float x) [1 .. 10] ;;
val it : float = 5.5
```

`List.averageBy` を使うと平均する前に各要素に適用させる関数を渡せます。これで `float` にキャストすれば `int` のリストでも OK ですね。

### ソート

``` fsharp
> [4, 3, 5, 2, 1] |> List.sort ;;
val it : (int * int * int * int * int) list = [(4, 3, 5, 2, 1)]
```

あれ、、ソートされないですね。
カンマで区切っちゃうと要素が１つのタプルのリストと認識されちゃうんですね。
セミコロンが正しい区切り文字でした。

``` fsharp
> [4; 3; 5; 2; 1] |> List.sort ;;
val it : int list = [1; 2; 3; 4; 5]
```

というか，このセミコロンって改行区切りの意味なんですね。
わざわざ一行ずつ書くとセミコロンは要りません。

``` fsharp
> [
-   2
-   3
-   5
-   2
-   1
- ]
- |> List.sort
- ;;
val it : int list = [1; 2; 2; 3; 5]
```

`sortWith` を使うと比較関数を渡してソートできます。これで降順にしてみます。

``` fsharp
> [4; 3; 5; 2; 1] |> List.sortWith (fun a b -> b - a) ;;
val it : int list = [5; 4; 3; 2; 1]
```

### 再帰

#### 普通の再帰

``` fsharp
> let rec sum arr =
-   match arr with
-   | [] -> 0
-   | h::t -> h + sum t
- ;;
val sum : arr:int list -> int

> sum [1 .. 10] ;;
val it : int = 55
```

#### 末尾再帰最適化

``` fsharp
> let sum arr =                   
-   let rec sumR acc arr =        
-     match arr with              
-     | [] -> acc                 
-     | h::t -> sumR (acc + h) t  
-   sumR 0 arr                    
- ;;                              
val sum : arr:int list -> int

> sum [1 .. 10] ;;
val it : int = 55
```

良さげですね😉
再帰するには関数に `rec` を付ける必要があるみたいです。

参考：
https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/lists


## パターンマッチ

試しに Codewars の初級問題を解いてみます。

**▽ Calculate BMI**
https://www.codewars.com/kata/57a429e253ba3381850000fb

体重と高さを渡したら BMI に対応したメッセージを返す関数をつくる問題です。

先ほどの再帰でも使いましたが，パターンマッチを使います。`when` で対象の変数に対する条件を追加することができます。手元で試したときに `int` か `float` かを指定しないとエラーになってしまったので，引数の型を明示的に指定しています。

``` fsharp
> let bmi (w:float) (h:float) =
-   match (w / (h * h)) with
-   | x when x <= 18.5 -> "Underweight"
-   | x when x <= 25.0 -> "Normal"
-   | x when x <= 30.0 -> "Overweight"
-   | _ -> "Obese"
- ;;
val bmi : w:float -> h:float -> string

> bmi 60.0 1.75 ;;
val it : string = "Normal"
```

## つづく

ということで，おなかいっぱいになってきたので，今回はこのへんで✋

つづき
https://qiita.com/dsudo/items/39867b9d0df896fb2701