Tech Racho エンジニアの「?」を「!」に。
  • 開発

Goby: Rubyライクな言語(1)Gobyを動かしてみる

こんにちは、hachi8833です。
今回から不定期で、Go言語だけで書かれたRubyライクな言語「Goby」について書きます。おそらく日本語で書かれた最初のGoby記事になると思います。

Railsへのコミット経験もある@st0012さんが作ったGobyは現在バージョン0.1.3で、first commitからまだ1年も経過していませんが、st0012さんの驚異的な実装の速さのおかげでかなり早くから基本的な部分をひととおり動かすことができ、HTTP serverやDBアダプタといった基本的なライブラリも装備していて、簡単なWebアプリ(https://sample.goby-lang.org/)やAPIを実際に書くことができます。コミット数は現時点で1800を超えています。

View post on imgur.com

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キーワードでモジュールを作成し、includeextendできます。
  • 次の特殊定数が使えます: ARGVSTDINSTDOUTSTDERRENV

Gobyのネイティブクラス

Gobyには現時点で以下のネイティブクラスがあります。Rubyでもお馴染みのクラスの他、Goby固有のクラスもあります。なお、ネイティブクラスはほとんどがnewできない仕様です(ユーザーのクラスはnewできます)。

Gobyの標準ライブラリ

Gobyには、上の他にrequireで導入できる標準ライブラリもあります。

  • Concurrent::Arrayrequire 'concurrent/array'): スレッドセーフなArray(新機能)
  • Concurrent::Hashrequire 'concurrent/hash'): スレッドセーフなHash(新機能)
  • DBrequire "db"): データベースアダプタ(現時点ではPostgreSQLのみ対応)
  • Net::HTTPNet::HTTP::Clientrequire "net/http"
  • Net::SimpleServerrequire "net/simple_server"
  • URLrequire "uri"
  • Jsonrequire 'json'
  • Pluginrequire "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スクリプトのサンプルとして、上のサンプル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;

CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。