みんGo学習メモ〜コード補完もできるREPL「gore」を使ってみた

https://github.com/motemen/gore
GoでREPLといえばgoreとのことなので、早速使ってみる。

## インストールする

まずは`go get`でインストール

```
GO111MODULE=off go get -u github.com/motemen/gore/cmd/gore
```

READMEのInstallation[https://github.com/motemen/gore#installation]
を見ると以下もインストールすることが推奨されているので、
こちらも`go get`しておく。

```
# 入力補完
GO111MODULE=off go get -u github.com/mdempsky/gocode

# 出力ハイライト
GO111MODULE=off go get -u github.com/k0kubun/pp # or github.com/davecgh/go-spew/spew
```

#### 【2019/12 追記】
APIドキュメントの参照のために以前は`godoc`が必要だった。
現在は不要(`go doc`コマンドを使用)なので削除。
[Fix :doc command in Go 1.12 by rzyns · Pull Request #117 · motemen/gore](https://github.com/motemen/gore/pull/117)
[drop godoc requirements from README.md by Songmu · Pull Request #142 · motemen/gore](https://github.com/motemen/gore/pull/142)

## 使ってみる

以下で実際に使ってみる。
ターミナル上でとりあえず`gore`と打ってみると、以下のような出力となる。

```
$ gore
gore version 0.4.1  :help for help
gore> 
```

早速REPLが起動しているよう。
まずは促されるがままに`:help`と打つ。

```
gore> :help
    :import <package>     import a package
    :type <expr>          print the type of expression
    :print                print current source
    :write [<file>]       write out current source
    :clear                clear the codes
    :doc <expr or pkg>    show documentation
    :help                 show this help
    :quit                 quit the session
```

利用できるコマンドの一覧が出力されたので、ひとつずつ試していこうと思う。
### `:import`を使ってみる

```go
gore> :import encoding/json
gore> b, err := json.Marshal(nil)
[]uint8{
  0x6e, 0x75, 0x6c, 0x6c,
}
nil
```

`:import`コマンドは説明の通り、goreのセッション内で使用するパッケージの
インポートを行う。

上の例では事前にimportすることでencoding/jsonパッケージを使用できるようにしている。
事前にimportしない場合は以下のような出力になる。

```
gore> b, err := json.Marshal(nil)
# command-line-arguments
/tmp/991290791/gore_session.go:10:25: undefined: json
error: exit status 2
exit status 2
```

### `:type`コマンド 【2019/12 追記】
[Implement type command by itchyny · Pull Request #127 · motemen/gore](https://github.com/motemen/gore/pull/127)

上記PRで実装された。引数に変数などを渡すと、その変数の型を返してくれる。

```go
gore> b, err := json.Marshal(nil)
[]uint8{
  0x6e, 0x75, 0x6c, 0x6c,
}
nil
gore> :type b
[]byte
gore> :type err
error
```

### `:doc`コマンドを使う
次に`:doc`コマンドを使うと、`godoc`のドキュメントがひける。
REPL上での出力は以下のような感じ。

```
gore> :doc json.Marshal
package json // import "encoding/json"

func Marshal(v interface{}) ([]byte, error)
    Marshal returns the JSON encoding of v.

...
```

パッケージ内の関数は`.`繋ぎで入力してもうまくいくよう。
また、変数のメソッドなども、変数のまま入力すればうまくいく。
とても便利。

これも事前にimportしておく必要がある。
importしてなかった場合は以下の出力となる。

```
gore> :doc json.Marshal
error: doc: cannot determine the document location
```

### `:print`コマンド、`:write`コマンド、`:clear`コマンド
試しに打ってみると、以下のような出力となる。

```
gore> :print
package main

import (
    "github.com/k0kubun/pp"
    "encoding/json"
)

func __gore_p(xx ...interface{}) {
    for _, x := range xx {
        pp.Println(x)
    }
}
func main() { b, err := json.Marshal(nil) }
```

これを`:write`コマンドによってファイル保存できる。

```
gore> :write example.go
Source wrote to example.go
```

##### `:clear`コマンド 【2019/12 追記】
[Implement clear command by itchyny · Pull Request #119 · motemen/gore](https://github.com/motemen/gore/pull/119)

ここで追加された`:clear`コマンドを使う。

```go
gore> :clear
gore> :print
package main

import "github.com/k0kubun/pp"

func __gore_p(xx ...interface{}) {
    for _, x := range xx {
        pp.Println(x)
    }
}
func main() {}
```

セッション中に定義したものを初期化できるようになった。
(上記では`func main()`内にに定義していた`b, err := json.Marshal(nil)`が消えて
初期化されたことが確認できる)

**【2019/12 追記ここまで】**

`:quit`コマンドでgoreセッションから抜け、
先ほど保存したファイルの中身を確認。

```go
gore> :quit
$ cat example.go
package main

import (
        "github.com/k0kubun/pp"
        "encoding/json"
)

func __gore_p(xx ...interface{}) {
        for _, x := range xx {
                pp.Println(x)
        }
}
func main()     { b, err := json.Marshal(nil) }
```

`:print`コマンドで出力したものと同じ内容となっていることが確認できる。

以上、ここまでがgoreセッション内でのコマンドとなる。
これらを駆使して快適なREPLライフを目指す。

## `gore`コマンド自体のオプション利用

まずは`--help`を渡して実行してみる。

```
$ gore --help
Usage of gore:
  -autoimport
        formats and adjusts imports automatically
  -context string
        import packages, functions, variables and constants from external golang source files
  -pkg string
        specify a package where the session will be run inside
```

### `-autoimport`オプション

`:import`コマンドを使う手間を省略できるというもの。

```
$ gore -autoimport
gore version 0.2.6  :help for help
gore> b, err := json.Marshal(nil)
[]uint8{
  0x6e, 0x75, 0x6c, 0x6c,
}
nil
```

### `-context`オプション

指定されたgoのソースファイルで使われているパッケージ群を
まとめて事前にインポートできる。

先ほど`:write`コマンドで作成した`example.go`を例として使用する。

```go
$ gore -context example.go 
gore version 0.4.1  :help for help
added file example.go
gore> :print
package main

import (
    "github.com/k0kubun/pp"
    "encoding/json"
)

func __gore_p(xx ...interface{}) {
    for _, x := range xx {
        pp.Println(x)
    }
}
func main() {}
```

`example.go`で使用していた`encoding/json`をデフォルトでインポートすることができた。

### `-pkg`オプション

`-context`オプションと同様に`.go`ファイルからパッケージの事前インポートができる。
こちらはパッケージ名を指定する。

先ほどの`example.go`を作業リポジトリを作って配置し以下のようにする。
(例: `github.com/Ken2mer/example`)

```
$ gore -pkg github.com/Ken2mer/example
```

そして`:print`コマンドを打つとさきほど`-context`オプションの時と
同じく以下の出力結果となる。

```go
gore> :print
package main

import (
    "github.com/k0kubun/pp"
    "encoding/json"
)

func __gore_p(xx ...interface{}) {
    for _, x := range xx {
        pp.Println(x)
    }
}
func main() {} 
```

こちらは複数ファイルを持つパッケージなどに対し一括でインポートできるので有用そう。

## おわりに

以上で使い方が網羅できたかと思います。

簡単な動作確認のために、わざわざ`main.go`を作成して`go run`と実行していたところが
コマンド一発で済ませられるようになった印象を受けました。

このツールの作者自身も

> REPL といいつつ実は裏ではそれまでの入力を順番に実行するソースコードを生成して go run しているだけ

とおっしゃっています。

* 参考: gore作者ブログ記事  
http://motemen.hatenablog.com/entry/2015/02/go-repl-gore

シンプルで非常に使いやすいので、これからもお世話になりたいと思います。
どんどん使っていきます。

#### 【2019/12 追記】
* 参考: [gore 0.4.0をリリースしました！ - プログラムモグモグ](https://itchyny.hatenablog.com/entry/2019/03/01/090000)