@m_seki の

I like ruby tooから引っ越し

RubyKaigi 2018でRubyでつくるRubyを紹介して売り切った件

RubyKaigi 2018

仙台行ってきた!楽しんでいただけたでしょうか。
太助 本店、太助 分店、山形ラーメン、大漁旗(笹かま)、ホテルの朝食、そしてマネーフォワード提供の会場のランチがとても心に残っています。

今回はMinRubyを改造して自分用の言語作ろうって話でした。最後はこういう感じになります。

今回は直前に とてか05 があったり英訳問題があったりほんとに準備不足になってしまいました。説明飛んじゃったところや滑舌が死んだところがあったりして申し訳ない。
発表はうまくいかなかったけど、会場で売ってた「RubyでつくるRuby」を売り切れたので満足です。うれしー。なお、幸福の王子本も売り切れたのすごい!みんなもつきあいで買おう!

技術書出版ラムダノート株式会社 – 技術書出版と販売のラムダノート

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

dRubyによる分散・Webプログラミング

dRubyによる分散・Webプログラミング

スライドはspeakerdeckで公開してます。
(階乗のコード間違ってるぽい!)

speakerdeck.com

新幹線代をCookpadに、その他の交通費や宿やもろもろの経費はスライドスポンサー、pixivFANBOX、people.suzuriのみなさんに支援していただきました。
ありがとうございます。Let's自転車操業
来年は福岡か...前後の日程で再演に呼んでくれる会社とかないだろか。来年でなくても福岡予習に行きたい。

あわせて課金したい

スライドスポンサーはまだ課金できます!(そしたらspeakerdeck更新します)

恒例!スライドに名前を入れる券 2018-2019
m_seki[pixivFANBOX]
咳 ∞ SUZURI People

minrubyにDripをつける

minrubyにDripのSTMぽいやつをつける実験 1

Dripという自分しか使ってないライブラリがあります。追記しかできないストリーム型のストレージで、某所のRWiki全文検索や画像アップローダーのバックエンドで使われています。
全てのデータには時刻から計算されるユニークな識別子がつき、さらに重複可能なタグ(名前)も設定できます。
タグをキーに見立てると、バージョンつきのキーバリューストアのようにも使えます。

DripはgithubにあるしThe dRuby Bookにもあるので見てね。


アプリケーションがあるキーの値を参照し、計算し、格納するケースを想像してください。計算のために参照した値が、格納するまでに変更されていたとしたら、その計算は無効になってしまいます(というユースケースを妄想しよう!)。
そこでアプリケーションが値を参照する際に、そのバージョン番号をメモしておき、格納するときにそのバージョン番号が最新であるか確認できれば、計算をやり直すことができるはずです。

Dripの書き込みにはwrite_if_latestという変わったメソッドがあります。あるタグの最新のデータの識別子と、引数で与える条件の組みとを比較して、一致したときだけ書き込みが成功するメソッドです。
つまり任意のキーのバージョンをチェックしてから書き込むプリミティブです。バージョンチェックと書き込みはアトミックに行われるので、バージョンチェックと書き込みの間に、別のコンテキストが書き込むことはできません。
Dripのサーバーにはトランザクション的な状態は一切持たずに、似たようなことができるのでお得です。

こんな感じです。(MyDripはDripに入ってるおまけ)

require 'my_drip'

MyDrip.invoke

def root
  Root.new
end

class Root
  class VersionMismatchError < RuntimeError; end
  def initialize
    @visited = {}
  end

  def visited
    @visited.collect do |key, age|
      [key, age]
    end
  end

  def [](key)
    found ,= MyDrip.head(1, key)
    age, value, = found
    @visited[key] = age || 0
    value
  end

  def []=(key, value)
    it = MyDrip.write_if_latest(visited, value, key)
    raise(VersionMismatchError) if it.nil?
    @visited[key] = it
    value
  end
end

if __FILE__ == $0
  3.times do |n|
    Thread.new(n) do |x|
      root = Root.new
      10.times do
        begin
          count = root['count'] || 0
          sleep(rand * 3)
          p [root['count'] = count + 1, x]
        rescue
          p [:retry, x]
          retry
        end
      end
    end
  end

  gets
end

この仕組みは前からあって意識して書けばうまく機能します。

minrubyに入れる

(ちょっと苦しいんだけど)意識しなくても使えるように考えてみました。minrubyでデータベースの更新をするだけのプログラムを書いたとして、更新に矛盾が起きそうなときはプログラム全体を再起動する、ってことにします。

db = root()

count = db['count'] = db['count'] + 1
p count

たとえばこういうやつ。root()でデータベースのインターフェイスを手に入れて、db['count']の値に+1して保存します。もしも参照したdb['count']が別のプロセスによって変更されていたら、プログラム全体を再起動します。

もとのminrubyインタプリタの最後の方を次のように変更すればできそう。ただし、minrubyで実装されていないrescueやらretryやらがあるので、minruby自身でminrubyを実行することはできなくなります。そこは我慢。

str = minruby_load()
begin
  tree = minruby_parse(str)

  genv = {
    "p" => ["builtin", "p"],
    "require" => ["builtin", "require"],
    "minruby_parse" => ["builtin", "minruby_parse"],
    "minruby_load" => ["builtin", "minruby_load"],
    "minruby_call" => ["builtin", "minruby_call"],
    "root" => ["builtin", "root"]
  }
  lenv = {
  }
  evaluate(tree, genv, lenv)
rescue Root::VersionMismatchError
  p :retry # 観察用
  retry
end

なお実行が速過ぎてなかなか「やりなおし」が起こせないので、evaluate()の冒頭でsleepさせることにしました。

def evaluate(tree, genv, lenv)
  sleep(rand * 0.3)
  case tree[0]

全体のコードはgistにあります。

minruby + STM · GitHub

シェルを駆使して(すみません。コピペしました。もっとうまくかけるはず)同時にたくさんプロセスを起動するとretryする様子が見えます。

$ (ruby -I. stm-interp.rb test-1.rb &) ; (ruby -I. stm-interp.rb test-1.rb&) ;(ruby -I. stm-interp.rb test-1.rb&) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb&) 
$ :retry
:retry
:retry
:retry
:retry
31
30
32
33
:retry
:retry
34
35
36
37

テストプログラムに10回ループしてdb['count']を更新する例があります。これを二つのプロセスで実行すると、矛盾を検知するたびにプログラム全体が再起動されてしまい、10回のループをやり直すので、運が悪いとやり直しが交互に発生してなかなか終われなくなります。賽の河原の石積みを連想させる、ダメなサンプルでした。


gist.github.com

車載スマホホルダーを自作したのでみんな真似していいよ!7x7

Golf7にiPhone7を固定したい(そしてyahooカーナビ活躍させたい)

みんカラで同じ車種でスマホホルダーを使っていい感じに固定している例を見かけたので真似しようと記事で紹介されていた商品を発注!!

件のホルダーは手帳型ケースをつけたままでは無理。ぐぬぬ。しょうがないのでsuzuriで薄いケースを自作することにしました。 

作ったのはこれ!ミワサ x toteka 5がテーマ。かっこいいぞ!納期がちょっとかかるけど!
 
suzuriからケースが到着する前にホルダーが到着したので、記事の固定場所に装着。
  • う。ハンドルで画面隠れてるじゃん。
  • あれ?ライト周りの操作で干渉しないのか?左右入れ替えてみよ。
  • やっぱりハンドルで画面隠れる...

たぶん、元の記事の人と体型/ドライビングポジションの違いがあるようでどうにも具合が悪い感じ。

 
どうしようかな。
 

自分で作ろう

自分のクルマはATで左足は身体を支える以外にすることがないため、これを活用したい。

  1. マジックテープのついたバンドと、プラスチック製のカード、手帳型iPhoneケースを用意
  2. バンドにカードを固定(両面テープ使った)
  3. カードを手帳型ケースのカード入れに挿す
  4. 左のふとももに巻きつけて完成!

写真つけてツイートしたのでそれを引用します。

 

 

特徴

  • Golf7に限らず、オートマの車なら全車種にフィットする。
  • クルマから降りるとき、ケータイを忘れない
  • スカートだと固定しにくい(かも?)
  • 手帳型ケースが必要
  • ちょっとかっこわるい

 

問題点

一昨日、suzuriで注文したケースが届きました。これ、使うチャンスない..。どうしようかな。toteka05の会場で売っちゃおうかな。(iPhone7/8用です)

これかっこいいから、factory.PIXIVの手帳型ケースで作り直すのはどうだろうか。

 

 

 

 

 

 

反復開発再エン

反復開発の再演

www.wantedly.com

 

去年から何度か依頼をいただいてる「反復開発」ネタです。今回は id:tricknotes 経由 エン・ジャパンに呼んでもらいました。再演といっても、ひと月前に打ち合わせの様子から「テーマ変えないとまずい」と感じて、スライドをほとんど書き直して臨んだ豪華版(全然再演じゃない!)です。

 

みんな集中して考えているのが伝わってきて、あー、これはいいチームだわ、と思いました。三時間近く集中するのはしんどいと思うけど、みんなすごい。

再演は金で買えるのでみんなも課金しよう!こんな場が作れてお買い得ですよ!

 

追記

 

恒例!スライドに名前を入れる券 2018-2019

The right to put your sticker on my MacBookAir, again.

だいたいフォント感を身につけたい

Mdnの絶対フォント感特集

大昔はMac系で、いまはデザイン系の雑誌(たぶん)のMdNの「絶対フォント感を身につける」特集、以前から気になっていました。最近、ポプテピピック特集の紙の本を買った時、kindle unlimitedで読めることに気づきました。お得!

検索してみたら、三冊もあったのねー。

 

これは楽しい。今は鉄道各社のフォントの紹介(使用フォント名も書いてある)を読んでます。あー、いろんなフォント欲しいなあ。

kindle unlimited、MdN読むだけでも元が取れたなー(端末代も考えると紙の方が安いけど)

 

なお最新号はポプテピピック特集。

 

 

 

 

DockerでdRubyしたって言っても、そんなにいい話じゃない

なんでDocker...

Dockerの中のirbdRubyしてほかのコンテナと通信するのと、いつもの複数プロセス、複数マシンでやってるdRubyプログラミングとあまりにも違わない。(複数ユーザーにしてユーザー権限でいろんなサービス動かすとそっくり
なんで今さらDocker...。

いい話にすり替える

さっきの実験ではゼロコンフィグ風だったけど、さらにゼロコンフィグ感高めたい。

Ringを思い出す

dRubyが標準添付になるどさくさに紛れて、rinda/ringというルックアップサービスを入れた。dRuby本を書くケース以外の利用例を見たことがないが、今日こそ使ってみたい。
Ringは主に二つのパートからなる。一つは、ネットワーク上のRindaのタプルスペースを公開する/探索する部分。もう一つは、タプルスペースをネームサーバーと見立て、そこにサービスとその場所(URI)を書く/自動的に消す部分である。たぶん。

RingはUDPを使ってブロードキャストを行うので、Ring用のタプルスペース(RingServer)は実質的に一つのマシンに一つだけ生成できる。PCが一台しかないと実験しにくいが、Dockerを使えばいい感じ!

タプルスペースを公開する

dRubyを動かして、タプルスペースを作って、RingServerを作れば公開完了。

$ docker run -it test/ruby
irb(main):001:0>   require 'rinda/tuplespace'
irb(main):002:0>   require 'rinda/ring'
irb(main):003:0>   DRb.start_service()
irb(main):004:0>   ts = Rinda::TupleSpace.new
irb(main):005:0>   Rinda::RingServer.new(ts)

じゃあもう一つ別のコンテナで検索する。

$ docker run -it test/ruby
irb(main):001:0> require 'rinda/ring'
irb(main):002:0> DRb.start_service
irb(main):003:0> finger = Rinda::RingFinger.finger
irb(main):004:0> ts = finger.primary
=> #<DRb::DRbObject:0x00005629551a1da8 @uri="druby://172.17.0.2:40721", @ref=47391105262580>

ひょー。見つかった。操作できるぞ。*1

irb(main):005:0> ts.write(['hello', 'world'])
irb(main):006:0> ts.read(['hello', nil])
=> ["hello", "world"]

最初のコンテナでもreadしてみる。

irb(main):006:0> ts.read([nil, nil])
=> ["hello", "world"]

おー。ふつうだ!

コンテナ同士がお互いのIPアドレスやポート番号を別の人間などが教えあうことなくつながったんだけど、面白さわかる? *2


サービスをネームサーバーに登録する

今回はなし。使い方は以下のページにあるが、CFP的にそっちには行かない予定。

http://www2a.biglobe.ne.jp/seki/ruby/d208.html

TODO

絵を描かないとなんの話かわかんないなー。
一度下書きをロストしたら、二度目はかなり投げやりな感じになってちょうどよかった。

なお、ロストしても投げやりにならないように、スライドスポンサー募集してます!

恒例!スライドに名前を入れる券 2018-2019
The right to put your sticker on my MacBookAir, again.

*1:ちなみにfinger.primaryは最初に見つかったやつをキャッシュして返すけど、lookup_ringを使えば都度検索できるぞ!

*2:UDPのポート番号は固定だからちょっと嘘がある

DockerでdRubyするために数年ぶりにcommitした、いい話

なんでDocker...

不本意な事情でDockerを調べる時期と、RubyKaigiのCFPが重なった。
CFPのころは集中して作業できてたんだけど、旅費のための再演とかでぶっつり中断しちゃってた。

このエントリーはDockerの入門とかそう言うんじゃなくて、中断しがちな自分の作業を再開するときのための日記。

なお、こういう中断しなくてもすむように、スライドスポンサー募集してます!

恒例!スライドに名前を入れる券 2018-2019
The right to put your sticker on my MacBookAir, again.


最初の実験

最初にどんな準備したか忘れちゃったので、もうDocker動くようになったあとのメモ。
rubyのイメージを起動するとirbが動く。

$ docker run -it ruby
irb(main):001:0> 

へー。シェルが動くのかと思ったら、だいたい合ってた。じゃあとりあえずdRubyしてみよう。

irb(main):001:0> require 'drb'
=> true
irb(main):002:0> DRb.start_service(nil, {}); DRb.uri
=> "druby://0b0be9d41935:34963"
irb(main):003:0> 

えー。このURI("druby://0b0be9d41935:34963")は気に入らない気がする。URIを省略したとき、ホスト名を補うからか。

きっとDockerの起動方法とか工夫したらうまくできるんだろけど、カジュアル層にはめんどくさい。

次のターン

こんなユーティリティー作れば良いんだろうな。

def make_drb_uri(port=0)
  name = Socket.getaddrinfo(Socket.gethostname, nil)[0][3]
  "druby://#{name}:#{port}"
end

実際にやってみた。

$ docker run -it ruby
irb(main):001:0> def make_drb_uri(port=0)
irb(main):002:1>   name = Socket.getaddrinfo(Socket.gethostname, nil)[0][3]
irb(main):003:1>   "druby://#{name}:#{port}"
irb(main):004:1> end
=> :make_drb_uri
irb(main):005:0> require 'drb'
=> true
irb(main):006:0> DRb.start_service(make_drb_uri, {}); DRb.uri
=> "druby://172.17.0.2:45267"

IPアドレス表記になったぞ!しめしめ。

数年ぶりのcommit

これを.irbrcに書いてDockerfileでCPすれば良い。ってところまでやって、drb/drb.rb直すべきだなって思ってしまった。
久しぶりに読んだらgetaddrinfo使うなど、どなたかいろいろ直してくださったみたいでどこを改造したらいいかわからなかったけどなんとかなったと思う。
ChangeLogがなくなってから初めてのコミットだったかも。(k0kubunさんをコミッタにコミットしたのはあったか)

でまだそれが含まれたイメージはない(作り方よくしらない)のでHEADのlib/drb/drb.rbをローカルに持ってきてコピーした。

Dockefileはこう。

FROM ruby:2.6-rc
COPY drb.rb /usr/local/lib/ruby/2.6.0/drb/
$ docker build -t test/ruby .
...
$ docker run -it test/ruby
irb(main):001:0> require 'drb'
irb(main):002:0> DRb.start_service(nil, {}); DRb.uri
=> "druby://172.17.0.2:39415"

よしよし。きっといつか、.irbrc作らなくてもrubyのイメージを使うだけでよくなるぞ。

最初にやりたかったこと

二つのターミナルでrubyコンテナを起動し、それぞれのirbで対話的にdRubyを動かしていちゃいちゃする。

### Terminal A
$ docker run -it test/ruby
irb(main):001:0> require 'drb'
irb(main):002:0> inbox = {}
irb(main):003:0> DRb.start_service(nil, inbox); DRb.uri
=> "druby://172.17.0.2:33215"
### Terminal B
$ docker run -it test/ruby
irb(main):001:0> require 'drb'
irb(main):002:0> inbox = {}
irb(main):003:0> DRb.start_service(nil, inbox)
irb(main):004:0>DRb.uri
=> "druby://172.17.0.3:35641"
irb(main):005:0> there = DRbObject.new_with_uri('druby://172.17.0.2:33215')
irb(main):006:0> there['greeting'] = 'hello'
=> "hello"
### Terminal A
irb(main):004:0> inbox
=> {"greeting"=>"hello"}
### Terminal B
irb(main):007:0> there['outlet'] = $stdout
=> #<IO:<STDOUT>>
### Terminal A
irb(main):005:0> inbox 
=> {"greeting"=>"hello", "outlet"=>#<DRb::DRbObject:0x000055f1db015248 @uri="druby://172.17.0.3:35641", @ref=47265107100840>}
irb(main):006:0> inbox['outlet'].puts('fire')   
=> nil
### Terminal B (操作するんじゃなくて"fire"が表示されてる)
irb(main):007:0> there['outlet'] = $stdout
=> #<IO:<STDOUT>>
irb(main):008:0> fire

いい話はどこ

ゼロコンフィギユレーションでdRubyできるよー、というのと、久しぶりにコミットしたのと、スライドスポンサー募集しているあたり。

あわせて読みたい

The dRuby Book: Distributed and Parallel Computing with Ruby

The dRuby Book: Distributed and Parallel Computing with Ruby