こんにちは、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;