トップ «前の日記(2014-09-16) 最新 次の日記(2014-09-23)» 編集

Route 477



2014-09-21

[ruby][event] 近況報告@RubyHiroba2014 (パターンマッチ、Hash#map、Opal/Yeahの話)

本日行われたRubyHiroba 2014 (RubyKaigiの後夜祭的イベント) にて、「近況報告」というタイトルでRubyの話をしてきました。

以下はテキスト版です(内容は少し差異があります)。

近況について

いちおうBoomがメインの感心事のつもりなのですが、あまり進んでいません。 理由の一つはケータイを持って社寺仏閣を回るゲームにうつつを抜かしていたことですが、最近はプレイしてないのでもう大丈夫だと思います。

Rubyとパターンマッチについて

Boomの実装にはpattern-matchというgemを使っています。その名の通り、Rubyでパターンマッチをやるためのライブラリです。 Rubyにはパターンマッチがない、と多くの人が思っていると思いますが、gemを使うとそれなりにやりたいことができたりするんですよね。

Rubyでパターンマッチするためのgemはいくつか存在します。

  • pattern-match gem 辻本さん(speaker)のやつ。

  • patm gem todeskingさんのやつ。pattern-match gemより機能が少ない代わりに速いらしい

  • egison gem エギさん(speaker)の作っているエギソンという言語のパターンマッチ機能をRubyに移植したもの。 「..., x, x+2, ...」みたいなパターン(non-linearというらしい)が書けたりしてすごい。 詳細はRubyKaigi1日目の録画を参照。

さてところで、パターンマッチって何なんでしょうか。HaskellやOCamlなどのいわゆる関数型言語によくある機能ですが、知らない人のために一言で言ってみると、

オブジェクトに対する正規表現

みたいなものかなと。

正規表現を使うと、文字列に対して

str = "add 1 2"
if str =~ /add (\d+) (\d+)/
  puts "Add #{$1} and #{$2}"
end

利点は、if式とかで頑張るよりもずっと簡潔に書けることで、欠点は、Rubyの文法とは別に正規表現の文法を覚えないといけないことです。

パターンマッチも同様で、例えば「こういうパターンの配列」みたいな条件を簡潔に書けるようになります。その代わりに追加でパターンの書き方を覚えないといけないのも同様です。

ary = [:add, 1, 2]
match(ary){
  with(_[:add, x, y]){
    puts "Add #{x} and #{y}"
  }
}

pattern-match gemでは配列だけでなく、ユーザ定義クラスについてもマッチさせることができます(Scala由来なのかな?)。

class Book  # has :title, :price
  def initialize(title, price)
    @title, @price = title, price
  end
  attr_reader :title, :price

  def self.deconstruct(val)
    accept_self_instance_only(val)
    return [val.title, val.price]
  end
end

book = Book.new("Programming Ruby", 3400)
match(book){
  with(Book.(/Ruby/, 0..5000)) {  # タイトルに「Ruby」が入っていて
    p book                        # 値段が5000円以下の場合にマッチ
  }
}

パターンマッチはRubyKaigi初日のRuby Commiters vs the Worldでも話が出たのですが、「本当に入れるならいろいろと詰めないといけないところがあるので、叩き台の提案をどうぞ」みたいな結論になっていましたね。

とりあえずgem i pattern-matchで簡単に入るので、ぜひ試してほしいと思います。

Enumerable#hash_by(仮)について

僕はRubyコミッタではないのですが、いくつかCRubyへの機能提案を通したことがあります。

  • __dir__ (2.0.0)
  • String#lines returns Array (2.0.0)
  • Enumerable#lazy (2.0.0)

で、あと一つ入れたいメソッドがあって、それは「Hashを返すHash#map」です。 RubyのHashって、mapするとArrayになっちゃうんですよね。

h = {a: 1, b: 2}
h.map{|k, v|
  [k.to_s, v]
}
#=> [["a", 1], ["b", 2]]
# Wanted {"a" => 1, "b" => 2}

こういうものが欲しいとき、従来のRubyではHash#[]とかを使って「キーと値の組の配列」をハッシュに変換する必要がありました。

h = {a: 1, b: 2}
Hash[h.map{|k, v|
  [k.to_s, v]
}]
#=> {"a" => 1, "b" => 2}

Ruby 2.1からはEnumerable#to_hというメソッドが入ったので、「キーと値の組の配列」をto_hすることでハッシュが作れて、だいぶ良くなりました。

h = {a: 1, b: 2}
h.map{|k, v|
  [k.to_s, v]
}.to_h
#=> {"a" => 1, "b" => 2}

が、これって「ハッシュから配列の配列を作って、それをハッシュにする」っていうコードなんですよね。やりたいのは「ハッシュを加工した別のハッシュを作る」ことなので、これでは意図が十分に表現されていない、と考えます。

議論

この問題に対しては、前に一度チケットを立てたことがあります。

このときはHash#applyかHash#hash_mapみたいな名前はどうか?と提案していますね。それに対してまつもとさんが、Hash#collectの挙動を変えたらいいじゃん、と言っています。どうでしょうか?

  • Array#mapとArray#collectはalias
  • Hash#mapとHash#collectもalias(今のところ)
  • しかしそういえばHash#reject, Hash#selectはハッシュを返す (特にHash#selectは1.9からそうなった)
  • そういわれるとHash#collectがハッシュを返すのは綺麗な気がする
  • 互換性の問題がなければ:-P

という感じで、そのあともいろいろな名前が出るのですがacceptには至らないのでした。

それで、最近また提案したのがこのチケットです。 to_hはいまブロックを取らないのですが、これに直接ブロックを渡せるようにするのはどう?という案です。

User = Struct.new(:id, :name)
users = [User.new(1, "Alice"), User.new(2, "Bob")]

users.hash_by{|u| [u.id, u.name]}
# {1 => "Alice", 2 => "Bob"}
h = {a: 1, b: 2}
h.to_h{|k, v|
  [k.to_s, v]
}
#=> {"a" => 1, "b" => 2}

この案は残念ながら「to_hという名前でこの挙動は変じゃない?」という話で終わりそうです。 が、「こういうメソッド自体は欲しい」という意見もあったので、良い名前があれば採用されるかもしれません。いまのところ自分の中で一番マシなのはEnumerable#hash_byですが、そこまで良い名前でもないような気もして…。

Opal(Ruby to Javascriptコンパイラ)について

最後はOpalの話です。

去年のRubyHirobaでもちょっと紹介しましたけど、RubyからJavaScriptへのコンパイラですね。厳密には、文字列がimmutableだったりとか100%のRubyではないんですけど、普通にRubyを書く感じで使える程度にはなっています。

Why Opal Matters #1

Opalになんで注目してるかというと、近年ではJavaScriptを使っていろいろなことが出来るようになっています。

JavaScript x HTML5
JavaScript x WebSocket
JavaScript x WebRTC
JavaScript x Mobile
 ...

Opalがあれば、このへんがぜんぶRubyを使って遊べるわけです。

Ruby(Opal) x HTML5
Ruby(Opal) x WebSocket
Ruby(Opal) x WebRTC
Ruby(Opal) x Mobile
 ...

例として、Yeahというすごい名前のゲームフレームワークがあります。gem install yeah で入ります。 会場ではこれのライブコーディングをしました。以下は別に作った(というか移植した)ゲームです。こっちはyeahのgit masterを使っています(bundle exec yeah serveしてください)。

Why Opal Matters #2

もう一つ、Opalの応用として面白いものが出ています。 VoltというWebアプリケーションフレームワークで、「サーバ側もクライアント側もRubyで書く」のですが、なんかクライアントでデータを変更するとシームレスにサーバ側に通知されるとか書いてあってヤバそうな感じです。

Webアプリのサーバ側・クライアント側を同じ言語で書く、というアイデアは昔からあるものですが(Linksとか)、Node.jsの登場で、いろいろ研究が進んでるみたいなので(RendrとかMeteorとか)、Opalでそういった技術がRubyに持ってこれると面白いのかなと思っています。