冪等性とは「同じ操作を何度繰り返しても、同じ結果が得られる性質」のこと

冪等性(べきとうせい、idempotency, idempotence)とは、__同じ操作を何度繰り返しても、同じ結果が得られる__という性質です。冪等性がある操作(idempotent operation)は、1回操作した場合の結果と、2回以上操作した場合の結果は同じになります。

<img width="681" alt="冪等性のろうそく_key.png" src="https://qiita-image-store.s3.amazonaws.com/0/889/75be3b97-9d8f-36c1-66d0-90143f0cb43a.png">


## 関数における冪等性

`f`という関数に引数`x`を与えた場合、次の式が成り立つ性質。(もちろん`f`は副作用がない関数であることが前提です)

```
f(x) = f(f(x))
```

__冪等性がある関数の例__

絶対値を求める関数は冪等性があります。`abs(-100)`の結果は`100`ですが、それを何度`abs()`に渡しても、最初に得られた結果である`100`と同じ結果が必ず得られます。

```
abs(-100) = abs(abs(-100))
```

__冪等性がない関数の例__

冪等性でない関数には、平方根を求める関数などがあります。`sqrt(16)`の結果は`4`です。`4`を更に`sqrt()`に渡すと、結果が`2`になります。一度目の結果と二度目の結果が異なるため、冪等性が無いわけです。

```
sqrt(16) ≠ sqrt(sqrt(16))
```

## RESTfulにおける冪等性

冪等性はRESTfulの文脈でもよく話題になります。RESTfulでも冪等性の概念は同じで「同じ操作を何度繰り返しても、同じ結果が得られる」という意味です。もう少しRESTfulっぽく寄せた説明をするなら、「同じリクエストを何度繰り返しても、同じリソース状態になること」という感じになると思います。

得られる結果というのは__リソース状態であって、レスポンスではない__ことに注意してください。`DELETE`メソッドには冪等性がありますが、1度目のリクエストでは200レスポンスを、2度目以降のリクエストは404レスポンスを返すことが普通です[^DELETEは冪等性があるけど]。このように冪等性があるAPIでも回数に応じてレスポンスが変化することがありますが、リソースが削除されたという結果(リソース状態)が2度目のリクエストで覆ることはありません。

[^DELETEは冪等性があるけど]: DELETEは冪等性がありますが、1回目の削除でリソースが無くなるので、2回目の操作からは404が返ることが普通です。これについては[本当にDELETEって冪等なの？](https://stackoverflow.com/questions/4088350/is-rest-delete-really-idempotent)というスタック・オーバーフローの質疑応答が参考になります。


__冪等性があるRESTful APIの例__

RESTfulではGET, PUT, PATCH, DELETEは冪等性があるメソッドとされています。

GitHubのAPIにはリポジトリにスターをつけるAPIが生えていますが、これは冪等性があるAPIです。

```
PUT /user/starred/:owner/:repo
```

何度上のPUTメソッドを叩いても、結果はスターがついた状態になります。スターが外れることはありません。

__冪等性がないRESTful APIの例__

一方、POSTは冪等性がないメソッドとされています。例えば、GitHubのAPIにはissueを立てるAPIがありますが、これは冪等性がないAPIです。

```
POST /repos/:owner/:repo/issues
```

同じリクエストを二度送ると、2個issueができてしまいます。

RESTfulの冪等性に関連して、[PUT か POST か PATCH か？ - Qiita](https://qiita.com/suin/items/d17bdfc8dba086d36115)もご覧ください。

## 自動化における冪等性

ChefやAnsibleなどのようなインフラ構築をプログラムで自動化する界隈でも冪等性という言葉がよく出てきます。自動化における冪等性の考え方も、他の分野の考え方と同様に「同じ操作を何度繰り返しても、同じ結果が得られる」というものです。

__冪等性がある操作の例__

ファイルをコピーする操作は冪等性があります。`$ cp file_a file_b`のような操作は、`file_b`がなければ作られ、すでに有ったとしても内容が変わったりすることはありません。これは何度やってももたらされる結果が同じであるため冪等性があると言えるわけです。

__冪等性がない操作の例__

逆に、`echo "some_config" >> /etc/some.conf`のような、ファイル末尾に行を継ぎ足す操作には冪等性がありません。実行するたびに行数が増えていきます。つまり、操作の回数に応じてもたらされる結果が異なるわけです。こうした操作は、すでにファイル末尾に設定が書き込まれているかをチェックしてあげる必要があります。

## 冪等性のメリット

冪等性のメリットのひとつは、関数やAPI、コマンドを実行する側が、__前提となる状態を気にしなくてよくなる__という点です。RESTful APIを例に考えてみましょう。

この投稿にいいね！をつけるAPIを想像してみてください。

```
POST /items/:item_id/like
```

このAPIは、この投稿にあなたがまだ「いいね」がついてなければ「いいね」をつけてくれます。しかし、もしも「いいね」済みだったら、その「いいね」は取り消される仕様になっています。これは冪等性がないAPIですね。

このAPIを使ってプログラムを書く側は、「投稿にいいねがついているか」を気にしなければなりません。プログラムとしては「いいね」を付けたいだけで、投稿がいいね済みかは気にしたくありませんが、コードはきっとこうしたif分岐が必要になることでしょう。

```java
if (post.liked) {
  // すでにいいねしている場合は、いいねしない
} else {
  apiClient.likePost(post.id);
}
```

もしも、このAPIが冪等なものだったら、クライアントコードはいいね済みかどうかを気にせずAPIを呼び出せるようになります。結果的に、条件分岐が不要になり、コードもシンプルになります。

```java
apiClient.likePost(post.id);
```

冪等性の他のメリットとしては、リトライのしやすさ、重複の排除、並行性を気にしなくていい点などもありますが、そこらへんの説明は追々していけたらと思います。

## 冪等性は外からみた性質

ここまで冪等性について読むと、「例えばメールアドレスを更新する操作があるとして、これを冪等なものにするなら、メールアドレスを__更新する__SQLを__毎回__発行するような実装にするんでしょ」と思うかもしれません。これは正しくもあり間違いでもあります。

冪等性は「外から見た」性質です。外からみて、つまり、APIを使う側から見て「冪等」かどうかという基準だけがあります。つまり、内部実装は、毎回更新SQLを発行するようになっていてもいいですし、更新済みなら更新しないような実装になっていてもいいわけです。

どっちも冪等性があるAPI:

```js
function updateEmail(user, newEmail) {
    sql.exec("UPDATE users SET email = {newEmail} WHERE id = {user.id}")
}

function updateEmail(user, newEmail) {
    if (user.email == newEmail) {
        // 更新はしない
    } else {
        sql.exec("UPDATE users SET email = {newEmail} WHERE id = {user.id}")
    }
}
```

## おわり

この投稿は何度読み返しても同じ説明になるので、この投稿には冪等性があります。[^1]

[^1]: もしもこの投稿を読む度に発見があり、知識が増したり、成長するようであれば、読み手には冪等性が無いことになるわけですが、それはさておき、この投稿を何度も読んでくださったことは大変嬉しく思います。

---

最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:→[Twitter@suin](https://twitter.com/suin)