こんにちは、hachi8833です。
今回から不定期で、Go言語だけで書かれたRubyライクな言語「Goby」について書きます。おそらく日本語で書かれた最初のGoby記事になると思います。
- リポジトリ: goby-lang/goby
Railsへのコミット経験もある@st0012さんが作ったGobyは現在バージョン0.1.3で、first commitからまだ1年も経過していませんが、st0012さんの驚異的な実装の速さのおかげでかなり早くから基本的な部分をひととおり動かすことができ、HTTP serverやDBアダプタといった基本的なライブラリも装備していて、簡単なWebアプリ(https://sample.goby-lang.org/)やAPIを実際に書くことができます。コミット数は現時点で1800を超えています。
Gobyはその名のとおりRubyから強く影響を受けていて、Rubyと同じ感覚で使えます。Goby実行系はGo言語だけで書かれている(CGOなどのC言語のコードは今のところ含まれていない)ので、C言語を知らないけど気軽に言語系をいじって遊んでみたい方にはぴったりだと思います。Gobyの最適化はこれからですが、その分Goby実行系のソースコードが読みやすいのもありがたい点です。
また、Gobyを知ることでRubyを知るのにも役に立つと思いますし、少なくとも私はそう感じています。実際Gobyのcontributorの中には、Gobyで遊んだ後にRuby本体にパッチを投げた方もいます。
Gobyの特徴については追って順にご紹介しますので、今回はまずGobyの動かし方をご紹介します。
Gobyをどうとらえるか
たとえとしてはとても大ざっぱで恐縮ですが、RubyがサッカーだとすればGobyはさしずめフットサルのようなものと自分は考えています(それならmrubyだよね?というツッコミがありそう...)。フットサルはオフサイドやスローインがないなどルールが若干異なっていますが、同じ感覚でプレーでき、さらに屋内でもプレーできます。何より、サッカーをやる人はいつでもフットサルも楽しむことができます。
Gobyの目的の一つに「マイクロサービスやWebアプリを楽に書けるようにする」というのがあり、Gobyの仕様や標準ライブラリもその点を優先して整備が進められています。Gobyは、Rubyと完全に同じものにする予定は今のところないそうです。実際、明確な意図のもとに少し仕様を変えているところもあり、そうした点については今後の記事でご紹介します。
いろいろ書きましたが、要はGoby実行系のソースをいじるのは私にとって楽しいということです。
Gobyのインストール方法
Gobyのインストールには、Homebrewを使う方法、Go言語ソースをコンパイルする方法、Docker imageを使う方法があります。
Gobyインストール上のポイントは1つ、$GOBY_ROOTの設定です。この環境変数は、Gobyの標準ライブラリをrequireするときなどに必要です。
1. Mac+Homebrew
単に動かすのであれば、Macユーザーはhomebrewでインストールできます。この場合、$GOBY_ROOT環境変数も自動でセットアップされます。インストールの際は最新バージョンをご確認ください。
brew tap goby-lang/goby
brew install goby
gobyを実行して以下が表示されればインストール成功です。
$ goby
Usage of goby:
  -e    Generate reporting format
  -i    Run interactive goby
  -p    Profile program execution
  -v    Show current Goby version
2. ソースからインストール
GobyのGoソースをいじって楽しみたい方、Windows/Linuxの方は、Go言語の環境をセットアップしたうえでGobyのソースからコンパイルします。私はWindowsでGoを動かしたことがないので、ここではMacとLinuxで説明します。ご了承ください。
1. Go言語のセットアップ
Go言語が利用可能な方は1.をスキップできます。
Macの場合、brew install goでGo言語をインストールするのが楽です。
Linuxの場合は以下でGo言語をインストールします。
$ wget https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz -O /tmp/golang-1.9.tar.gz
$ sudo tar xv -C /usr/lib -f /tmp/golang-1.9.tar.gz
$ sudo mv /usr/lib/go /usr/lib/go-1.9
ここからはMac/Linux共通です。以下で$GOPATHと$GOROOT環境変数をセットアップし、$PATHも設定します。ここでは.bashrcに設定する前提ですが、必要があれば.bash_profileの方に設定します。
Homebrewの場合を除き、$GOPATHは好きな場所に設定できます。~/goや~/.goに置かれることが多いようです。$GOPATHは後から変更すると問題が起きやすいので、一度設定したら変えないようにしましょう。
$ echo 'export GOPATH=$HOME/go' >> $HOME/.bashrc
$ echo 'export GOBY_ROOT=$GOPATH/src/github.com/goby-lang/goby' >> $HOME/.bashrc
$ echo 'export PATH=$PATH:$GOPATH/bin' >> $HOME/.bashrc
$ source ~/.bashrc #設定をリロード
go versionを実行して以下のように表示されればGo言語のインストールは完了です。
go version go1.9.2 darwin/amd64
2. Gobyソースのインストールとコンパイル
以下を実行してGobyソースをインストールします。実行はどのディレクトリにいても構いません。
$ go get github.com/goby-lang/goby
以下を実行してGobyの$GOBY_ROOT環境変数を設定します。
$ echo 'export GOBY_ROOT=$GOPATH/src/github.com/goby-lang/goby' >> $HOME/.bashrc
$ source ~/.bashrc #設定をリロード
Gobyのディレクトリに移動してmake installを実行します。
$ cd $GOPATH/src/github.com/goby-lang/goby
$ make install
gobyを実行して以下が表示されればインストール成功です。お疲れさま!
$ goby
Usage of goby:
  -e    Generate reporting format
  -i    Run interactive goby
  -p    Profile program execution
  -v    Show current Goby version
3. Docker imageを取得して環境まるごとインストール
Gobyには公式のDocker imageもあります。
私のMacbookではなぜかDocker for Mac自体がどうしても動いてくれない(泣)ので、Homebrewで素のDockerをインストールして動かしました。Dockerのインストール方法は省略します。
# bash
$ docker pull gobylang/goby
$ docker run -it gobylang/goby
GobyのREPLやサンプルコード
goby -iでインタラクティブモード(REPL)でGobyを使えます。実はREPLの多くは私が実装しました(repl.go)。
- resetでREPLをリセット、- exitで終了できます。
- Goby REPLでは、↑キーで履歴をさかのぼったりCtrl-Rで履歴マッチしたりもできます。いわゆるreadline的なことはひととおりできます。
- お遊びですがREPLを起動すると絵文字のfortuneが3つ表示されます。
- 素のWindowsだと絵文字が化けたので、何とあのmattnさんがパッチを当ててWindowでfortuneを非表示にしてくれました。感謝!
 
- Goby REPLのプロンプトは»、インデント中は¤、出力は#»が使われていますが、ターミナルからコピーして貼り付けると行冒頭の»と¤は自動で削除されるので、コードを楽にREPL上でコピペできます(下)。
# REPLのプロンプト: このままターミナルにコピペできます。
» def foo
¤   42
» end
#»
駆け足紹介
- クラス名#methodsでメソッド一覧を表示、- クラス名#ancestorsで継承パスを表示できます。
- クラス名#singleton_classで特異クラスを表示できます。
- moduleキーワードでモジュールを作成し、- includeや- extendできます。
- 次の特殊定数が使えます: ARGV、STDIN、STDOUT、STDERR、ENV
Gobyのネイティブクラス
Gobyには現時点で以下のネイティブクラスがあります。Rubyでもお馴染みのクラスの他、Goby固有のクラスもあります。なお、ネイティブクラスはほとんどがnewできない仕様です(ユーザーのクラスはnewできます)。
- Object: 名前空間上は原則- Classと同一
- Array
- Boolean
- Channel:- #threadとともにマルチスレッド用途に使う(サンプルコード)
- File(サンプルコード)
- Float: まだfloatリテラルがないため、- '3.14'.to_fで生成する
- GoMap
- Hash
- Integer
- MatchData(正規表現のマッチ結果保持)
- Null
- Range: 原則としてrangeリテラル(- 1..100)などで使われる
- Regexp: dlclark/regexp2を用いた、Onigmoに迫る正規表現(まだ正規表現リテラルがないため、- Regexp.new("正規表現")で生成する)
- String: エンコードは今のところUTF-8一択です(4バイトUTF-8対応)。
Gobyの標準ライブラリ
Gobyには、上の他にrequireで導入できる標準ライブラリもあります。
- Concurrent::Array(- require 'concurrent/array'): スレッドセーフなArray(新機能)
- Concurrent::Hash(- require 'concurrent/hash'): スレッドセーフなHash(新機能)
- DB(- require "db"): データベースアダプタ(現時点ではPostgreSQLのみ対応)
- Net::HTTPと- Net::HTTP::Client(- require "net/http")
- Net::SimpleServer(- require "net/simple_server")
- URL(- require "uri")
- Json(- require 'json')
- Plugin(- require "plugin"): Go言語の多くのパッケージをプラグイン化して利用できます(現時点ではLinux環境のみ)(サンプルコード)
Gobyスクリプトの実行
goby ファイル名.gbでGobyスクリプトを実行できます。
# Goby
def f(from)
  i = 0
  while i < 3 do
    puts(from + ": " + i.to_s)
    i += 1
  end
end
f("direct")
c = Channel.new
thread do
  puts(c.receive)
  f("thread")
end
thread do
  puts("going")
  c.deliver(10)
end
sleep(2) # This is to prevent main program finished before goroutine.
# bash
$ goby channel.gb
direct: 0
direct: 1
direct: 2
going
10
thread: 0
thread: 1
thread: 2
- 参考: Gobyプロジェクト内のサンプルスクリプト: samples
- 参考: Gobyだけで書かれたサンプルWebアプリ: https://sample.goby-lang.org/(同ソースコード: https://github.com/goby-lang/sample-web-app)
Gobyスクリプトのサンプルとして、上のサンプルWebアプリのコードからmodel.gbも以下に転記してみました。こうしてみるとRubyとほぼ同じ感覚ですね。実際、Rubyのsyntax highlightingがそのまま使えます。
# Goby
require_relative "plugin"
PluginPG.run("create table if not exists list_items (
  id      serial primary key,
  title   varchar(40),
  checked boolean
)")
class ListItem
  attr_reader :id, :title, :checked, :error
  def initialize(params)
    @id      ||= params[:id]
    @title   ||= params[:title]
    @checked ||= params[:checked]
    @error   ||= params[:error]
  end
  def check
    self.class.plugin_db.exec('UPDATE list_items SET checked = true WHERE id = $1', @id)
    @checked = true
  end
  def uncheck
    self.class.plugin_db.exec('UPDATE list_items SET checked = false WHERE id = $1', @id)
    @checked = false
  end
  def update_title(title)
    self.class.plugin_db.exec('UPDATE list_items SET title = $1 WHERE id = $2', title, @id)
  end
  def destroy
    self.class.plugin_db.exec('DELETE FROM list_items WHERE id = $1', @id)
  end
  def valid?
    @error.nil?
  end
  def self.plugin_db
    PluginPG
  end
  def self.all
    plugin_db.query("SELECT * FROM list_items ORDER BY id DESC")
  end
  def self.find(id)
    result = plugin_db.query("SELECT * FROM list_items WHERE id = $1", id).first
    if result
      new({ id: result[:id], title: result[:title], checked: result[:checked] })
    end
  end
  def self.create(params = {})
    validates(params) do |result|
      if result[:error].nil?
        title   = params[:title]
        checked = params[:checked].to_i == 1
        resultID = self.plugin_db.exec("INSERT INTO list_items (title, checked) VALUES ($1, $2)", title, checked)
        new({ id: resultID, title: title, checked: checked })
      else
        new({ error: result[:error] })
      end
    end
  end
  def self.validates(params)
    if params.nil? || params[:title].nil?
      yield({ error: 'Title cannot be empty' })
    elsif params[:title].empty?
      yield({ error: 'Title cannot be empty' })
    else
      if params[:title].length > 40
        yield({ error: 'Title too long (should less than 40 characters)' })
      else
        yield({})
      end
    end
  end
end
GobyのSlackチャンネルとContribution Guideline
以下からGobyのSlackチャンネルにアクセスできます(英語のみ)。知りたいことなどや議論はこちらでどうぞ。
Gobyのバグを見つけたらissueまでお願いします。
Gobyにプルリクしたい方は以下のガイドラインをご覧ください。GitHubでGobyをforkしてローカルにcloneし、ブランチを切って修正したらgit pushしてプルリクするという、通常のプルリク手順です。
- ガイドライン(英語)
誰かGoのtestingパッケージでGobyにベンチマークを追加してくれないかな...
参考: Gobyのテストについて
Gobyディレクトリでmake testを実行するとテストが走ります。このときPostgreSQLアダプタのテストも行われるので、ローカル環境でPostgreSQLをセットアップしておかないとテストを完了できません。なお、Docker imageにもPostgreSQLが入っていないことに今気づいたので、Docker内でapt-get update; apt-get install postgresql-9.6でインストールしてください。
PostgreSQLのインストール方法については省略します。
テストを実行するには、postgresユーザーの権限でデータベースを作成できるようになっている必要があります。事情があって権限を与えられないといった場合は、事前にpsqlコマンドでgoby_testデータベースを作成しておきます。
$ psql
create database goby_test;
 
       
                      
