zmq4でFizzBuzzする

# 初めに

Goのすごい人がついったーで`gophernotes の pure go zeromq 対応が入った。`とツイートしてた。
すごい人が言ってるんだから使っておいて損無いハズ。

5分ぐらい考えた結果、FizzBuzzすることに決めた。
プログラマならFizzBuzzとか数列とか使いたくなるんだからしょうがない。

# できたもの

![dekita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/c9c78ca2-00ba-d820-d45b-042640df79de.png)

<font color="dark_pink">斬新なFizzBuzz結果</font>

# zeromqとは

ブローカーなしのメッセージング。ブローカー不要が何より嬉しい。
どうせ通信相手そんなにいないのに、いちいちサーバ立てたくない。
詳しくは[ZeroMQ](https://zeromq.org/)参照。

# FizzBuzzとは

よくやる奴。割愛。

# 【非検証】gophernotesを使おうとしたけど使えなかったので手順だけまとめる

gophernotesとは、Jupyter NotebookのGo言語版的な奴。詳しくは[gophernotes](https://github.com/gopherdata/gophernotes)を参照。

諸般の事情(Windowsしか持ってない)があるので、Dockerを利用する必要がある。
が、今現在Dockerが利用できる端末がない。
なので環境構築の順番だけでもまとめる。

簡単に利用するためにgopherdata/gophernotesをベースとして自分の使いやすいイメージを作る。

<details><summary>Dockerfile</summary><div>

```Dockerfile
FROM gopherdata/gophernotes:latest

RUN go get github.com/go-zeromq/zmq4 \
  && mkdir -p /etc/jupyter

COPY ./entrypoint.sh /entrypoint.sh
COPY ./jupyter_notebook_config.py /etc/jupyter/jupyter_notebook_config.py

ENTRYPOINT [ "/entrypoint.sh" ]
```

</div></details>

<details><summary>entrypoint.sh</summary><div>

```bash
jupyter notebook --no-browser --allow-root --ip=0.0.0.0  --config=/etc/jupyter/jupyter_notebook_config.py
```

</div></details>

configはgopherdata/gophernotesのイメージをいったん走らせてから、コンテナに吐いてもらう。

```bash
# コンテナ走らせて中に入る
docker run -it -d gopherdata/gophernotes
docker exec -it ${gophernotesのid} /bin/sh
# config吐く
jupyter noteboopk --generate-config
mv /path/to/jupyter_notebook_config.py /tmp/
exit
# ローカルに落とす
docker cp ${gophernotesのid}:/tmp/jupyter_notebook_config.py ./
```

手元で試すだけなら、jupyter_notebook_config.pyのtokenを書き変える。
後はコンテナをビルドして実行するだけ

```bash
docker build -t miyatamagophernotes:1.0.0 .
docker run -it -p 8888:8888 miyatamagophernotes:1.0.0
```

# zmq4

まずはpub側から

## publish側

1秒おきに100までのFizzBuzzを垂れ流す。

```go
package main

import (
	"fmt"
	"time"
	"context"
	"sync"
	"github.com/go-zeromq/zmq4"
)

func main() {
	err := startPublisher()
	if err != nil {
		fmt.Printf("%v", err)
	}
}

func startPublisher() error {
	pub := zmq4.NewPub(context.Background())
	defer pub.Close()

	err := pub.Listen("tcp://*:5563")
	if err != nil {
		return err
	}
	for {
		publishFizzBuzz(pub)
		if err != nil {
			return err
		}
		time.Sleep(time.Second)
	}
	return nil
}

func publishFizzBuzz(publisher zmq4.Socket) {
	getFizzBuzzText := func(num int) string {
		if (num % 15) == 0  {
			return "FizzBuzz"
		}
		if (num % 3) == 0 {
			return "Fizz"
		}
		if (num % 5) == 0 {
			return "Buzz"
		}
		return fmt.Sprintf("%d", num)
	}

	wg := &sync.WaitGroup{}
	for i := 0; i < 100 ; i++ {
		wg.Add(1)
		go func(number int) {
			defer wg.Done()
			msg := zmq4.NewMsgFrom(
				[]byte("FizzBuzz"),
				[]byte(getFizzBuzzText(number)),
			)
			publisher.Send(msg)
		}(i + 1)
	}
	wg.Wait()
}
```

続いてsub

## Subscribe側

受け取って表示するだけ

```go
package main

import (
	"context"
	"fmt"
	"github.com/go-zeromq/zmq4"
)

func main() {

	err := startSubscribe()
	if err != nil {
		fmt.Printf("%v", err)
	}
}

func startSubscribe() error {
	sub := zmq4.NewSub(context.Background())
	defer sub.Close()

	err := sub.Dial("tcp://localhost:5563")
	if err != nil {
		return err
	}

	err = sub.SetOption(zmq4.OptionSubscribe, "FizzBuzz")
	if err != nil {
		return err
	}

	for {
		msg, err := sub.Recv()
		if err != nil {
			return err
		}
		fmt.Printf("%s\n", msg.Frames[1])
	}
}
```

# ふりかえり

今回も様々な知見を得られた

 + FizzBuzzをgophernotes上のZeroMQでやる必要は全くない
 + gophernotesが利用できなかったので単純にzmq4でFizzBuzzしただけになった
 + 並列処理したおかげでFizzBuzzの結果が正しいかよく分からなくなった