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

Goby: Rubyライクな言語(6)構文編1: ブロックは`do-end`のみ、`then`がないほか

こんにちは、hachi8833です。

本記事ではGoby 0.1.9を用いています。1.0になるまでは仕様変更の可能性もありますのでご了承ください。

インストール方法や動かし方については第1回目をご覧ください。

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

: GobyとRubyのどちらでも動くコードの冒頭には# Goby/Rubyと、Gobyでのみ動くコードには# Gobyと表記することにします。

Gobyの文法上の特徴(6)ブロックはdo-endのみ

Rubyのブロックはdo-end{ } の2とおりの書式があります。

Gobyでは後者を廃止してハッシュリテラル専用とし、ブロックはdo-endif-endなども含めてですが)のみで記述します。

# Goby
def foo(k)
  b = "4".to_i
  (1..k).each do |x|
    b += x
  end
  b
end

foo(2)
#» 7

スタイル統一もさることながら、パーサーが圧倒的に書きやすくなるからだろうと推測しています。

// https://github.com/goby-lang/goby/blob/master/compiler/parser/expression_parsing.go#L204
func (p *Parser) parseGetBlockExpression() ast.Expression {
    return &ast.GetBlockExpression{BaseNode: &ast.BaseNode{Token: p.curToken}}
}

Gobyの文法上の特徴(7)Blockオブジェクトとget_blockキーワード

Blockオブジェクト

Rubyのブロックは例外的にオブジェクトではないので、Proc.newまたはlambdaを用いてブロックをオブジェクト化します。Proc.newlambdaの挙動は微妙に違っているそうです。また、lambdaには->というショートハンドが用意されてます。こうしたものは「無名関数(anonymous function)」と呼ばれることもあります。

参考: Rubyの ブロック、Proc.new、lambdaの違い - Qiita

Gobyのdo-endブロックがオブジェクトではないという点はRubyと同様ですが、GobyではそのものズバリのBlock.newdo-endブロックを渡すことでブロックオブジェクト化できます。

# Goby
b = Block.new do  # bにBlockオブジェクトが保存される
  100
end

b.call
#» 100

ブロックオブジェクトに対して#callするとブロックが実行される点もRubyと同じです。#to_proc的なメソッドはGobyにはありません。

Proclambdaといった概念を取り入れていないので、その分Gobyは私にとってわかりやすいです😊。

get_blockキーワード

Goby独自のキーワードとしてget_blockがあります。

Rubyでは、ブロックをそのまま渡されたメソッドがそのブロックに対して行えるのはyieldのみです。そのため、メソッド定義のパラメータに&を追加する(パラメータ名はblockとする慣習)ことでブロックをProcオブジェクトとして受けるしくみも用意されています。&は引数を渡すときに付けることもできます。

# Ruby
[3] pry(main)> def foo(&block)
[3] pry(main)*   block.call
[3] pry(main)* end
=> :foo
[4] pry(main)> foo do
[4] pry(main)*   "hello"
[4] pry(main)*   "bar"
[4] pry(main)* end
=> "bar"

Gobyでは、ブロック演算子&を導入すべきかどうか検討した末、渡されたブロックリをget_blockキーワードでBlockオブジェクトに変換できるという一種チート的な構文を代わりに採用しました。

# Goby
def foo
  get_block # 受け取ったブロックをBlockオブジェクトにして返す
end

a = foo do  # aにBlockオブジェクトが渡る
  "hello"
  "bar"
end

a.call
#» "bar"

もちろんRubyと同様、yieldもGobyで使えます。

# Goby/Ruby
def foo
  yield 9
end

foo do |i|
  i * 7
end
#» 63

get_blockは割りと軽いノリで導入された感じでした。おかげで記号がひとつ減ってうれしいです😊。

参考: Gobyの用語について

一般にパラメータ(parameters)と引数(arguments)という用語は割りとごっちゃに使われることが多くてつらいので、Gobyのドキュメントでは厳密に区別するようにしています(少なくとも私は)。

  • パラメータ: メソッド定義側に記述するもの
  • 引数: メソッド呼び出し側に記述するもの

Gobyの文法上の特徴(8)thenがない

Rubyスタイルガイドでもthenは、ifunlessが複数行の場合は使わないとあるように、原則使われなくなっています。使うときがあるとすればcase文で使う場合があるとか、後はワンライナーで書くときぐらいでしょうか。

Gobyではthenキーワードを取っ払いました。

# Goby
if true then
  99
end
#» UndefinedMethodError: Undefined Method 'then' for <Instance of: Object>

Rubyスタイルガイドを読む: 文法(2)アンダースコア、多重代入、三項演算子、if/unless

Gobyの文法上の特徴(9)メソッド名末尾に?は置けるが!は置けない

Rubyでは、述語メソッド(truefalseのみを返すメソッド)の末尾には?を、破壊的メソッド(レシーバーが改変されるメソッド)の末尾には!を付ける慣習があります。

?は実にシンプルかつ明確かつ有用で、何のあいまいさもありません。これは偉大な発明だと思います。

しかし!の方は、「!なしのメソッド」と「!ありのメソッド」の両方がなければならないという慣習がなかなか徹底できずに「!ありメソッド」しかないコードになったりとか、「破壊的」の解釈が人それぞれ違ったりと運用上混乱を招いてる面があります。この問題が「プリマドンナメソッド」と呼ばれるものです。

Ruby:「プリマドンナメソッド」の臭いの警告を私が受け入れるまで(翻訳)

Gobyでは、メソッド名末尾の?のみを許容し、!は採用しませんでした。

# Goby/Ruby
class Foo
  def found?
    true
  end
end

Foo.new.found?
#» true

最近のGoby

@st0012さんがRubyKaigi 2018に再び登壇

Gobyの作者@st0012さんが昨年のRubyKaigi 2017に続き、今年のRubyKaigi 2018にも登壇します。私もちょっぴりスライドをお手伝いしました。


rubykaigi.org/2018より

GobyにRustの血も入る?

GobyはRubyの文法をベースに、Go言語そのものの特徴も少し取り入れられていますが、いよいよRustの血も入りそうな流れです。

Gobyのエラーハンドリングをどうするかという議論がGobyのSlackで進められていて、@st0012さんはWikipediaの例外処理に対する批判を元に、例外によるエラーハンドリングは用いないことを決心しました。

それに代わるものとして、当初Go言語のような複数戻り値による原始的なエラーハンドリングも検討されたのですが、Rustのor_else的な方法はどうかという提案が出て一気に盛り上がっています。

# Goby
File.open("/tmp/test.txt") do |f|
    puts f.read
end.or do |err|    # このend.orでエラー処理のブロックを記述
    puts(err)
end

上はまったくの下書きですが、その後議論を経てOptionというエラーを含む結果の保持用クラスが提案され、Resultクラスにリネームされてついこの間マージされました。
その過程で、まさかの●●●メソッドが導入されて私は心底びっくりしました😮。その手があったかと。私もまだ全貌がわかっていませんし、今後どう変わるもわかりませんが。

詳しくは@st0012さんのセッションをお楽しみに😊。スライドが最終的にどうなるか私も楽しみです。

関連記事

Goby: Rubyライクな言語(2)Goby言語の全貌を一発で理解できる解説スライドを公開しました!


CONTACT

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