RubyKaigi 2022
抜粋してトークの感想を列挙していく。フル参加していたはずなのに数が少ない理由はあとで述べる……
Ruby meets WebAssembly
今まで、Opal とかを使って Ruby で書いたコードを JavaScript に変換してブラウザ上で動かす、みたいなものは存在したけど、CRuby そのものがブラウザ上で動くという信じられない偉業について、その実現方法を解説してくれた講演だった。初日の最初のトークがこれなの、めっちゃすごいなって思った。後述する Code Puzzle も、この WebAssembly が使われていて、わしにとって今回の RubyKaigi を象徴するトークだった。
Making *MaNy* threads on Ruby
「あっちの言語では出来てることが、なんで Ruby では出来ないんだっけ?」みたいな感じで出来ない理由を列挙していって、それら一つ一つについて解決の糸口を探っていくと、だんだん「なんかできそうな気がしてきた」ってなってくる様子を見て、問題解決かくあるべしという気がしたし、憧れを持った。Ruby のすごいところの1つとして、Ruby ならではの機能とか改善をやりつつ、他言語の利点も強力に吸収していくところがあると思っていて、それが現れてるトークだった。
TRICK 2022 (Returns)
なんというか、言葉にならない感動が得られるコマだった…… やんちゃには芸術はわからぬ、であるにも関わらず、あれほど感動させられてしまう事に驚きを感じた……
Packet analysis with mruby on Wireshark – dRuby as example
Wireshark を Ruby で使いたいけど Lua とかでしか使えないので mruby が使えるようにした。という話なんだけど、やりたいことが明白で、とにかく自分がやるしか実現する方法がないし、やれば実現しそうだしやるしかない、という、営みのお手本みたいなトークで、見習いたいなと思った。
The Better RuboCop World to enjoy Ruby
人間と Ruby と、という感じの、とても考えさせられる良いトークだった。RuboCop がとても便利なツールである反面、人間との関係性を設計した上で使わないと、ある人間の人生にとっての Ruby という存在を歪めかねない一面をもっており、仕事でのことならそれが許されるのだろうか、もっとよく出来ないか、など色々と考えた。
Fast data processing with Ruby and Apache Arrow
普段、仕事で DWH を使っているので、Data Processing を Ruby でやるということに興味はあるものの、使っている DWH が BigQuery なので、Apache Arrow と関わる事がなくて寂しい。BigQuery と Ruby との間で Apache Arrow を使って何か得するような事が見つかったら何かをしたいと常々思っている事を再確認したトークだった。
Cookpad Code Puzzle for RubyKaigi 2022
スポンサーのクックパッドから提供されていた Ruby のパズルが面白すぎた…… RubyKaigi の会期中であるにもかかわらず、トークを聴く集中が途切れて、気がついたらパズルに取り組んでいる自分がいた…… Web Assembly の CRuby がブラウザ上で動いており、その上で課題を解いていくという、この RubyKaigi にピッタリのエンタメだった。
標準では func10 までが出題されており、エクストラステージとして func20 まで出題されていたが、ギリギリ会期中に func20 まで解くことができた。ただ、途中、何問かは出題意図を掴みきれないまま嘘回答で乗り切っており、会期が終わってから他の参加者と相談しながら正規の解き方を探った。でも、それもまた楽しかった。
どういう解き方をしたのか、Gists にまとめてあるので、興味のある人は見てもらったらよい。func10 までは公式の解説がでているけど、func20 までの解説は来週になるらしいので、それまでの間に自力で解きたい人は、ここで読むのをやめて、後日また見に来て欲しい。
# You can call `func1` | |
p func1(0) #=> 1 | |
p func1(1) #=> 2 | |
p func1(2) #=> 3 | |
# Can you tell how `func1` is defined? | |
# Hint: def func1(n) = n + ??? | |
# Define `answer1` that works like `func1` | |
def answer1(n) | |
n.succ | |
end |
# Congrats! You've solved the first puzzle! | |
# Next, challenge func2! | |
p func2("Hello") # => ??? (press "Run Ruby" to see output) | |
p func2("world") # => ??? | |
def answer2(str) | |
str.upcase | |
end |
# Next, challenge func3! | |
p func3(1) | |
p func3(2) | |
p func3(3) | |
def answer3(n) | |
n.succ.times.to_a | |
end |
# Still want to play? | |
# You may want to try func4 | |
# This call raises an error! | |
# Try to find a correct way to call it. | |
func4{9} | |
def answer4(&f) | |
f[] + 42 | |
end |
p func5(0) | |
p func5(1) | |
p func5(2) | |
p func5(0,2) | |
def answer5(a, b=1) | |
a + b | |
end | |
# Hint: Check the parameters |
def answer6(n) | |
case | |
when n < 10 then n | |
else | |
n2 = eval(n.to_s.chars.join('+')) | |
n2 < 10 ? eval(n2.to_s.chars.join('+')) : answer6(n2) | |
end | |
end | |
0.upto(1000) do |n| | |
func, ans = func6(n), answer6(n) | |
puts "#{func == ans ? 'OK' : '—-NG'} n: #{n}, func: #{func}, ans: #{ans}" | |
end | |
p func7(0) | |
p func7(1) | |
p func7(2) | |
# Hint: Try to pass non-Integer! | |
def answer7(n) | |
n.succ | |
end |
# Hint: %b | |
def answer8(n) | |
sprintf("%b", n).chars.select{|c| c == '1'}.size | |
end | |
1.upto(1000) do |n| | |
f = func8(n) | |
a = answer8(n) | |
p [n, f, a] | |
end |
# Can you tell what string is replaced? | |
# Hint: Pass a spy (or mock) object to func9 | |
class Hoge | |
def gsub(a, b) | |
p [a, b] | |
end | |
end | |
def answer9(s) | |
s.gsub("u-g0t-me", "yikes") | |
end | |
((%w[foo bar baz])+[Hoge.new]).each do |c| | |
res = func9(c) | |
p [c, res] | |
end |
def answer10(reset=false) | |
if reset | |
@counter = nil | |
end | |
@counter ||= -1 | |
@counter += 1 | |
end | |
p func10(true) | |
p answer10(true) |
def answer11(o) | |
o.hash | |
end |
def answer12(n) | |
n. | |
to_s. | |
chars. | |
sort. | |
chunk_while{|i,j| i==j}. | |
to_a. | |
map(&:size). | |
reduce(:*) | |
end | |
TABLE = Encoding.list.map{|e|e.to_s[0..1]} | |
def answer13(n) | |
TABLE[n] | |
end |
def answer14(n) | |
case n.to_s | |
when '0' then 'Z' # Zero | |
when '10' then 'T' # Ten | |
when '1000' then 'T' # Thousand | |
when /^(2|3)/ then 'T' # Two, Twenty Three, Thirty | |
when /^(4|5)/ then 'F' # Four, Five, Forty, Fifty | |
when /^(6|7)/ then 'S' # Six, Seven, Sixty, Seventy | |
when /^8/ then 'E' # Eight, Eighty | |
when /^9/ then 'N' # Nine, Ninety | |
when '11' then 'E' # Eleven | |
when '12' then 'T' # Twelve | |
when '13' then 'T' # Thirteen | |
when '14' then 'F' # Fourteen | |
when '15' then 'F' # Fifteen | |
when '16' then 'S' # Sixteen | |
when '17' then 'S' # Seventeen | |
when '18' then 'E' # Eighteen | |
when '19' then 'N' # Nineteen | |
when /^1/ then 'O' # One | |
end | |
end | |
0.upto(1000) do |i| | |
f, a = func14(i), answer14(i) | |
next if f == a | |
p [i, f, a, f == a] | |
end |
TABLE = Object.constants.sort.map{ |s| s[0..1] } | |
def answer15(n) | |
TABLE[n] | |
end |
def answer16(&f) | |
return false if f.parameters.empty? | |
f.call do | |
true | |
end | |
end |
def answer17(msg) | |
JS.eval("alert('#{msg}')") | |
end |
def answer18(s) | |
a = s.scan(/\d+/) | |
b = s.scan(/[^\d]+/) | |
pair = s.match?(/^\d/) ? [a,b] : [b,a] | |
pair.reduce(:zip).flatten.compact.map { |s| | |
case s | |
when /\d+/ | |
s.to_i | |
else | |
s.split(//) | |
end | |
}.flatten | |
end | |
def polandnize(ast) | |
case ast | |
in String => s | |
return s | |
in ['value', Integer] | |
return ast | |
else | |
# do nothing | |
end | |
case ast.children | |
in [Array, nil, node] | |
polandnize(node) | |
in [Integer => n] | |
['value', n] | |
in [node1, operator, node2] | |
[operator.to_s, node1, node2].map { |n| polandnize(n) } | |
in [next_ast, nil] | |
polandnize(next_ast) | |
end | |
end | |
def answer19(a) | |
ast = RubyVM::AbstractSyntaxTree.parse(a.map(&:to_s).join) | |
polandnize(ast) | |
end |
def solve(input) | |
case input | |
in ["value", Integer => n] | |
["value", n] | |
in [String => op, ["value", Integer => a], ["value", Integer => b]] | |
solve(["value", eval("#{a} #{op} #{b}")]) | |
in [String => op, Array => a, Array => b] | |
solve([op, solve(a), solve(b)]) | |
in [String => op, ["value", Integer] => a, Array => b] | |
solve([op, a, solve(b)]) | |
in [String => op, Array => a, ["value", Integer] => b] | |
solve([op, solve(a), b]) | |
in [String => op, ["value", Integer] => a, ["value", Integer] => b] | |
solve([op, solve(a), solve(b)]) | |
end | |
end | |
def answer20(input) | |
solve(input)[1] | |
end |
Ruby Music Mixin 2022
RubyKaigi の最終日の夜、ピクシブ社のRuby Music Mixinというイベントに参加させて貰った。結構前から、ライブハウスで色んな音楽を聴くのが好きだったんだけど、ここ2、3年は色々あって遠ざかっており、このイベントで久しぶりに良い気持ちになって嬉しかった。盆踊りサークルモッシュみたいな謎な集団行動が発生する感じ、とても懐かしかったです。
行き帰り
今回、どうやって三重県津市まで行って帰って来るか、非常に悩んだ。というのも、地図を見ていると名古屋の南あたりに、いかにもショートカット出来そうな湾があって、調べてみると実際に伊勢湾フェリーというフェリーが運航されていて、とても楽しそうだったからだ。
伊勢湾フェリーを使うとなると、新幹線を中途半端な駅で降りないといけないし、港まで行く渥美半島の鉄道はすべて各駅停車で大変そうすぎる。伊勢湾フェリーの仕様を見ていると、バイクや車を積むことが出来るとある。わしは原付二種のスクーターを所持しているので、スクーターで行けばスクーターごと向こう岸に渡ることができて便利そう。これは機運なのでは?と思った。
ここまで書いていて、それは嘘だなぁって思う。結局、要するに、面白そうだったので原付二種のスクーターで三重まで行って帰ってきた。詳細は Twitter のハッシュタグにまとまっているので、興味のある人はのぞいてみてほしい。 やんちゃの旅
行きは、9/6 の 13:00 に家を出て、湯河原の Airbnb の宿で一泊。翌朝 9/7 の 07:00 くらいに出発して、19:00 くらいに三重の宿に着いた。帰りは 9/11 の 07:00 くらいに宿を出て、23:45 くらいに家に着いた。いわゆる強行軍というやつだ。
道中、一番辛かったのは一部のバイパス道の途中が自動車専用道路に指定されているせいで、125cc 以下の車両は強制的に下ろされてしまい、信号待ちのキツイ道を延々と走らされるという事に現地で気づいたことだった。イイカンジで流れている道の途中で「ここから先は遠慮してくれるか?」って看板が出て下ろされて、降りたところに「9km先でまた合流させてあげるから頑張ってねw」って看板が出てるのを見たときの感情はしばらくのあいだ忘れられないだろう。
行きはまだしも、帰りを1日でやりきろうとしたのは失敗だった。神奈川に入ってから家までの道中、かなりの疲労感があったし、危険だった。かといって中途半端なところで臨時で宿を取るという決断もできず、最初から富士市とか御殿場市で一泊する予定にすべきだったなという反省がある。
自動 二輪車でフェリーに乗ったのは初めてだったけど、とても楽しかったので、総じて成功ではある。次回、似たようなことをやることになったときには、今回の反省を活かして、イイカンジにやっていきたいと思った。
やんちゃハウス
2017 年から、やんちゃハウスという、RubyKaigi 期間限定のシェアハウスを主催しているんだけど、今回、3年ぶりにやんちゃハウスを実施した。やんちゃハウス2022は色々な事を加味して、3LDKの家をAirbnbで借りて3人でシェアするというスカスカ構成にした。今までのやんちゃハウスでは家にある布団の数だけRubyistを詰め込んでワイワイやる、というスタイルだったので家を借りる費用を頭数で薄めることが出来るメリットがあったが、今回は四泊25,000円くらいになってしまい、下手すると普通のビジネスホテルの方が安かった可能性すらある。
そこまでして、どうしてやんちゃハウスを実施したのかというと、一回中断してしまうと、再開するハードルが上がるだろうから、という理由だった。種火程度でも残っていれば、またあの頃のやんちゃハウスが戻ってくるかもしれないし、あるいは未来の違う形のやんちゃハウスが現れるかもしれない。それらの可能性が潰えないように、こじんまりでも実施したというのが今回のやんちゃハウスだった。
ところで、やんちゃハウス2023について。RubyKaigi 2023 は松本で開催されると予告されたわけだけど、これは 2020 のリベンジであり、やんちゃハウス2020のリベンジでもある。なので2020の時に借りようとしていた家にはオファーを出していて、少しずつ準備は進んでいる。もう次の話が進んでいるんだなぁ。
あと、年内に RubyWorld Conference 2022 というイベントが島根県松江市で行われる事になっていて、こちらで『やんちゃWorldハウス2022』というのをやろうかという話が進んでいる。マジか、という感じ。今後、募集開始するとしても1枠しか残ってないので、もしも強い興味を持っている人がいたら、公募が始まる前にネジ込みに来て欲しい。
まとめ
「やりたくてやったものの、もう一生やりたくないな」という事を沢山やって、死期を悟った頃に振り返って「あれもこれも楽しかったけど、やっぱりもう2度とやりたくないな。まぁ、もう人生も終わりなので出来ないがw」みたいになりたい
— yancya (@yancya) September 12, 2022