Go製のToolをHaskellで実装する

# 初めに

最近Haskellの学習としてGo製のToolをHaskellで実装するようにしています。

Haskellでなにかしら作りたいと考えていて、そういえばGo製のToolはよく作られているから面白そうなものありそうだなと思ったのがきっかけです。

mockサーバであるhttplabをHaskellで作ることにしました。



まだ100%の実装ができていないのですが、ある程度形ができたので記事を書くことにしました。

![screeencast.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217752/bd2e51db-1a92-a530-ec8c-75c9bf2718e6.gif)

ソースコードは[こちら](https://github.com/kwhrstr/httplabkell)にあります。

Haskellでbrickというライブラリを使っていますが、使い方等は[以前書いた記事](https://qiita.com/kwhrstr1206/items/25acf6eb3c43fa901c2a)を参考してください

GoもHaskellも至らぬところがあるので間違った箇所はアドバイスや助言をいただけると嬉しいです。

HaskellとGoもさほど違いがない箇所があったので、比較していきます。

## 引数のパース

Goでは[pflag](https://pkg.go.dev/github.com/spf13/pflag?tab=doc)を使ってパースしています。


```go

pflag.StringVarP(&args.status, "status", "s", "200", "Specifies the initial response status.")
```

Haskellでは[optparse-applivative](https://hackage.haskell.org/package/optparse-applicative)を使ってパースしてます。

```haskell

parseStatus :: Parser Status
parseStatus = option (maybeReader $ fmap toEnum . readMaybe)
  $ long "status"
 <> short 's'
 <> value status200
 <> help "Specifies the initial response status."
 <> metavar "StatusCode"

```

こうやってみると使い方はさほど違いがないように感じます。

ただHaskellの方は関数の引数ではなくmonoidを使ってプログラム引数の構成が自由にできることと、
IntやBool, String以外のデータ型に対してもパースできる強みがあります。

ただその分記述量が多くなっています。

## mock server


```golang:main.go

	srv := &http.Server{
		Addr:    fmt.Sprintf(":%d", args.port),
		Handler: http.Handler(middleware(NewHandler(ui, g))),
	}

	go func() {
		// Make sure gocui has started
		g.Execute(func(g *gocui.Gui) error { return nil })

		if err := srv.ListenAndServe(); err != nil {
			errCh <- err
		} else {
			ui.Info(g, "Listening on :%d", args.port)
		}
	}()

```

serverの情報を定義してgo rountineで起動させています。

Haskellは[wai](https://hackage.haskell.org/package/wai)を使って実装しています。

```Haskell:App.hs

  race_ (WAI.run (argPort cmd) $ middleware $ respHandler state chan)
            $ customMain vty buildVty (Just chan) app state


```

middlewareはまだちゃんと実装していないので`middleware=id`でやっています。

こちらもサーバを非同期で起動しています。

handlerについては

```go:main.go

// NewHandler returns a new http.Handler
func NewHandler(ui *ui.UI, g *gocui.Gui) http.Handler {
	fn := func(w http.ResponseWriter, req *http.Request) {
		if err := ui.AddRequest(g, req); err != nil {
			ui.Info(g, "%v", err)
		}

		resp := ui.Response()
		time.Sleep(resp.Delay)
		resp.Write(w)

	}
	return http.HandlerFunc(fn)
}

```

uiをpointerで渡すことでスレッド間の値を共有しています。

```haskell:App.hs

respHandler :: TVar ResponseData -> BChan Requested -> Application
respHandler tvar bchan req send = do
  dt <- readTVarIO $ tvar
  threadDelay $ dt ^. resDelay
  send $ responseBuilder
    (dt ^. resStatusCode)
    (M.foldrWithKey' (\k a -> (toResHeader k a :)) [] $ dt ^. resHeaders)
    $ B.byteString $ T.encodeUtf8 $ dt ^. resBody
  <* writeBChan bchan (toRequested req)
  where
    toResHeader a b = (CI.mk $ T.encodeUtf8 $ CI.foldedCase a, T.encodeUtf8 b)

```

スレッド間の値を共有する為にTVar を使用しています

大きな違いとして、Requestについては、brickはuiの情報は純粋なデータしか受け付けていない為、アプリケーション定義イベントで渡すようにしていますがgocuiも同じことができると思います。


## おまけ:Zipper

httplabはRequest情報を履歴で持つ機能が備わっています。それを実現するために内部データは次のように保持しています。

```go:ui.go
type UI struct {
	... 
	reqLock        sync.Mutex
	requests       [][]byte
	currentRequest int
}
```

`requests`がrequestのあった履歴、`currentRequest`がrequestを表示している場所になっています。
ただこれだと毎回リストを走査しないといけなく効率が悪いです。
やりたいことはrequest履歴の追加、履歴の表示、1つ前後の履歴の移動になります。Haskellでよくある手法として **Zipper** を使うことにしました。

Zipperについては[こちら](https://qiita.com/Aruneko/items/dee20161358e7c39e27a)がよくまとめられています。

今回はこのようにしました。

```Haskell:Types.hs

data RequestedZipper a = RZ
  { _reqzAbove :: [a]
  , _reqzBelow :: [a]
  , _reqzCurrent :: Maybe a
  , _reqzCurPos :: Int
  , _reqzLength :: Int
  }
reqzLength :: RequestedZipper a -> Int
reqzCurPos :: RequestedZipper a -> Int
reqzCurrent :: RequestedZipper a -> Maybe a
initReqZipper :: RequestedZipper Requested
moveAboveReqz :: RequestedZipper a -> RequestedZipper a
moveBelowReqz :: RequestedZipper a -> RequestedZipper a
```

## 最後に

UIについては割愛します。見比べたところIOに関してはHaskellもGoも対して変わらず、実装しやすかったです。
純粋なところは他の言語とかけ離れてしまいますが、実装できたときは嬉しく見比べたときはHaskellのが分かりやすく感じます。


Goは面白いToolがたくさんあって色々参考になりますので
Haskellを学びたいけど題材がないという方はぜひチャレンジしてください。