業務で関数型っぽく書くメリットを『1から100未満の素数の和をforを使わずに求める。』を例に考えてみる

## **きっかけ**

**[【swift】 1から100未満の素数の和をforを使わずに求める。](https://qiita.com/BMJr/items/92acef984200c116fc9c)**

こちらの記事を拝見して、業務で`for`文を使わず関数型**っぽく**書くメリットを考えてみました。

## **業務で関数型っぽく書くメリット**

個人的に**業務**で関数型**っぽく**書くことのメリットは

- **読みやすさ**
- **バグの少なさ**

だと思っています。
（ですので、今回は**パフォーマンスを最優先していません。**）

ですので、僕ならまず「**素数かどうか**」を判定する関数を書きます。
その関数をフィルター系の関数[^1]に渡して素数だけの数列にする形をとります。

そうすることで、読み手に何をする処理かが伝わりやすくなるのと、
再利用がしやすくなると思います。

**C#**で書きますが、**Swift** や **JavaScript** などほかの言語でも同様です。

## **素数か判定する関数**

まず、数値が素数なら`true`、そうでない場合は`false`を返す関数を作ります。
例題の解き方だけを考えるだけなら、
1 から 99 までを数え上げて足す処理の中に素数判定を組みこんだほうが効率が良いですが、
あえて、読みやすさ、再利用性のために小さなメソッドに切り出しています。

```c#
// 素数なら true を返す
bool IsPrime(int n) {
  if (n == 1) return false;
  if (n == 2) return true;
  var boundary = (long)Math.Floor(Math.Sqrt(n));
  for (long i = 2; i <= boundary; i++) {
    if (n % i == 0) return false;
  }
  return true;
}
```

上記の関数は [C#プログラミングのイディオム/定石&パターン](https://www.amazon.co.jp/%E5%AE%9F%E6%88%A6%E3%81%A7%E5%BD%B9%E7%AB%8B%E3%81%A4-C-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E3%82%A4%E3%83%87%E3%82%A3%E3%82%AA%E3%83%A0-%E5%AE%9A%E7%9F%B3-%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3/dp/4774187585)を参考にしました。

素数判定で`for`使わない解法は @albireo さんにコメントしていただいたので、そちらへどうぞ。

## **1から100未満の素数の和をforを使わずに求める**

先ほどの**`IsPrime`を使って1から100未満の素数の和**を`for`を使わずに求めたいと思います。
ここから関数型っ**ぽく**なります。

```c#
var sum =
  Enumerable.Range(1, 99)
    .Where(n => IsPrime(n))
    .Sum();
Console.WriteLine(sum); // 1060
```

- **`Enumerable.Range(1, 99)`**：1 から 99 まで
- **`.Where(n => IsPrime(n))`**：素数だけを[^3]
- **`.Sum();`**： 足す。

かなり読みやすいのではないでしょうか？[^2]

`IsPrime`を定義しないと何をやっているか読みにくくなります。

```c#
var sum =
  Enumerable.Range(1, 99)
    .Where(n => {
      // 素数かどうか判定する <- みたいなコメントがいる。(IsPrime なら自明)
      if (n == 1) return false;
      if (n == 2) return true;
      var boundary = (long)Math.Floor(Math.Sqrt(n));
      for (long i = 2; i <= boundary; i++) {
        if (n % i == 0) return false;
      }
      return true;
    })
    .Sum();
```

## **`IsPrime`を再利用**

**`numbers`**には何かしらランダムな数列が入っていると思ってください。

```c#
// 素数だけをカウント
var count = numbers.Count(n => IsPrime(n));

// すべて素数か？
bool isAllPrime = numbers.All(n => IsPrime(n));

// 素数が混じっているか？
bool containsPrime = numbers.Any(n => IsPrime(n));

// 最大の素数
int max = numbers.Where(n => IsPrime(n)).Max();

// 数列中に最初に現れる素数
int first = numbers.First(n => IsPrime(n))
```

素数を**X**個取り出すような実装も簡単です。

```c#
// 無限に素数を返すイテレータ
IEnumerable<int> PrimeGen() {
  int n = 1;
  while (true) {
    if (IsPrime(n)) yield return n;
    n++;
  }
}
// 素数を5個取り出す
var primes = PrimeGen().Take(5);  //=> 2, 3, 5, 7, 11 のシーケンス
```

### **2018/08/03 23:11 追記**

色々、アドバイスいただいたので、それに従って`IsPrime`を書き直してみました。

```c#
static class IntExtention {
    public static bool IsPrime(this int n) {
        if (n < 2) return false;
        var primes = new List<int>();
        return Enumerable.Range(2, (int) Math.Sqrt(n) - 1).All(i => {
            if (primes.All(p => i % p != 0)) {
                if (n % i == 0) return false;
                primes.Add(i);
            }
            return true;
        });
    }
}
```


```c#
foreach (var n in Enumerable.Range(1, 99)) {
    Console.WriteLine($"{n}: {n.IsPrime()}");
}
```

```
1: False
2: True
3: True
4: False
5: True
6: False
7: True

以下略
```

[^1]: Swift や JS の`filter`とか C# の `Where` とか関数を引数にとってフィルタリングするメソッドのことを言っています。
[^2]: `Aggregate`でも良いと思います。
[^3]: `Where(IsPrime)`のように関数そのものを渡すこともできます。