Elixirの並行処理でサンプルプログラムを書いてくださいとChatGPTにお願いしてみた

かなり乗り遅れていたのですが、ようやっと重い腰をあげて、ChatGPTを触り始めました。
せっかくなので、噂に聞く「__ChatGPTにプログラムを書いてもらう__」というのを試してみました。

プログラミング言語は、**Elixir**でお願いしてみました。
今回は、Elixirで並行処理のサンプルコードを書いてもらおうと思います。

## 準備：ChatGPTに並行処理について聞いてみる

この確認が必要かどうかは微妙なところ。
まぁ、並行処理のことをわかっているか（笑）という観点で、ちょっと質問してみました。

質問を誤字してしまったのですが、しっかり補完して回答してくれたのは、ちょっと嬉しかったり。

でも、なんかどっかでみたことあるような、ないような...（笑

![concurrency_sample_code_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/ac58ce38-2a96-10a5-eb6f-1c0b89261347.png)
![concurrency_sample_code_0_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/0a2f1041-61d3-0881-7700-b365b254aee4.png)

では、これを踏まえて、並行処理のサンプルを書いてもらいます。

## サンプルコード１：プロセス間のメッセージ

「_では、プログラミング言語Elixirの並行処理について、サンプルのプログラムを書いてください_」

最初のサンプルプログラムは、プロセス間メッセージに関するものでした。
※コードブロックのシンタックスが__ruby__ベースなのは、ご愛嬌…なのかな？

![concurrency_sample_code_1_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/4b39226b-e130-efb0-3960-48f8bb696232.png)

コードを転記すると、以下となっています。

```elixir:my_process.ex
defmodule MyProcess do
  def start_link do
    # プロセスの生成
    spawn_link(__MODULE__, :loop, [])
  end

  def loop do
    receive do
      # メッセージを受信したら、受信したメッセージを表示する
      message ->
        IO.puts("Received: #{message}")
        loop()
    end
  end
end

# プロセスの生成
pid = MyProcess.start_link()

# メッセージの送信
send(pid, "Hello, Elixir!")

# メッセージの送信
send(pid, "Goodbye, Elixir!")
```

で、特に修正せずに実行。

![concurrency_sample_code_1_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/53ce65ee-2450-2d6c-99eb-4d13a326c466.png)

はい、実行できました。

では、違うサンプルコードを書いてもらいましょう。

## サンプルコード2：GenServerによるカウンターアプリ

「_プログラミング言語Elixirの並行処理について、別のサンプルのプログラムを書いてください_」

すると、今度は**GenServer**を利用したサンプルコードを書いてくれました。

![concurrency_sample_code_2_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/4bf31e5b-7bde-d78a-a0ac-c5483c185bdb.png)
![concurrency_sample_code_2_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/7704c739-872a-34c5-b335-ae82083a7cc5.png)


相変わらず、どこかでみたことあるようなないような…。
でも、しっかりとサンプルコードの説明をしてくれています。

サンプルコードの説明にもありますが、
「_GenServerを使うことで、より複雑な状態を持つプロセスを実装することができます。_」
には納得。

では、ソースコードの転記。

```elixir:counter.ex
defmodule Counter do
  use GenServer

  def start_link(init \\ 0) do
    GenServer.start_link(__MODULE__, init)
  end

  def increment(pid) do
    GenServer.call(pid, :increment)
  end

  def decrement(pid) do
    GenServer.call(pid, :decrement)
  end

  def get_count(pid) do
    GenServer.call(pid, :get_count)
  end

  def handle_call(:increment, _from, count) do
    {:reply, count + 1, count + 1}
  end

  def handle_call(:decrement, _from, count) do
    {:reply, count - 1, count - 1}
  end

  def handle_call(:get_count, _from, count) do
    {:reply, count, count}
  end
end
```


今回も、修正せずに実行する。
すると、ワーニングが。

```
warning: function init/1 required by behaviour GenServer is not implemented (in module Counter).

We will inject a default implementation for now:

    def init(init_arg) do
      {:ok, init_arg}
    end

You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.

  counter.ex:1: Counter (module)
```

意味合いとしては、「`GenServer.start_link/3`に渡す`init/1`をこんなふうに定義してね」ってところですね。
`init/1`はGenServerのコールバック関数であるので、以下のようにコードを追加します。

```elixir
  # add: fuction for init/1
  def init(init_arg) do
    {:ok, init_arg}
  end
```

修正して再度実行すると、ワーニングは消えていました。

### コードを追記して動作確認してみる

ワーニングは消えたので、今度は実際にカウンターの数字が変更されるか確認します。

このサンプルプログラムでは、初期値を設定した後で、`:increment`または`:decrement`を呼ぶことで数字がカウントアップ／カウントダウンされることになります。

なので、初期値、カウントアップ処理、カウントダウン処理を追記します。

最終的に出来上がったコードはこちら。

```elixir:counter.ex
defmodule Counter do
  use GenServer

  def start_link(init \\ 0) do
    GenServer.start_link(__MODULE__, init)
  end

  def increment(pid) do
    GenServer.call(pid, :increment)
  end

  def decrement(pid) do
    GenServer.call(pid, :decrement)
  end

  def get_count(pid) do
    GenServer.call(pid, :get_count)
  end

  def handle_call(:increment, _from, count) do
    {:reply, count + 1, count + 1}
  end

  def handle_call(:decrement, _from, count) do
    {:reply, count - 1, count - 1}
  end

  def handle_call(:get_count, _from, count) do
    {:reply, count, count}
  end

  # add: fuction for init/1
  def init(init_arg) do
    {:ok, init_arg}
  end
end

### 以下、カウンタ処理を確認するための操作

# Start the server
{:ok, pid} = GenServer.start_link(Counter, 100)

# start count
result = GenServer.call(pid, :get_count)
IO.puts "start count"
IO.puts result

# increment x2
IO.puts "increment x2 count"
GenServer.call(pid, :increment)
GenServer.call(pid, :increment)
result = GenServer.call(pid, :get_count)
IO.puts result

# decrement x3
IO.puts "decrement x3 count"
GenServer.call(pid, :decrement)
GenServer.call(pid, :decrement)
GenServer.call(pid, :decrement)
result = GenServer.call(pid, :get_count)
IO.puts result
```

初期値「100」、カウントアップ（「`:increment`」）を2回、カウントダウン（「`:decrement`」）を3回実行します。
結果は、100→102→99となる想定です。

では実行。

![concurrency_sample_code_2_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/a04c8c49-097c-c134-a82a-1ea243323c95.png)

はい、無事に実行できました。
結果も、想定通りです。

## まとめ

何番煎じかは分かりませんが、ChatGPTにサンプルコードを書いてもらいました。
なんていうか、面白いですね。

一発で動く／動かないはあるにせよ、ある程度の形になったコードが出力されるのは、ブログ記事なり技術同人誌を書くなりで、参考情報として大いに役立つかなぁ、と感じました。

機会があれば、また別のサンプルを作ってもらおうかなと考えています。