@m_seki の

I like ruby tooから引っ越し

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

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

図書館で昭和を借りた。

ポーの一族を借りてきたら、限定BOXがほしくなった。

 

あとはカセットテープの本も借りた。いま見るとパッケージのデザインもすてきでパクろうかな、と思ったなう。

カセットテープコンプリートブック (NEKO MOOK)

カセットテープコンプリートブック (NEKO MOOK)

 

 年代別、メーカー別に整理されてたり、開発の苦労とか載っててやばいです。

 

RubyKaigi(のためのGumroadとWishlist)の季節

RubyKaigi2018のCFPの締め切りがすぎました。そろそろ仙台に向けて準備しなくてはなりません。

仙台ツアーの旅費を募る、恒例のスライドスポンサー募集のお知らせです。

 

gumroad.com

 

本日からRubyKaigi2019のCFPまでの間、私のスライドにコメントが載せられるお得な権利が手に入ります!(2018円以上いくらでもOK)

 

また、反復開発ネタ( re: 反復開発 (Ite4) - @m_seki の )などの再演もカネで買える(これも旅費などに充てます)のでご相談ください。旅費は出してやりたいけどうちでなんか喋ってよ!みたいな感じで。

一般向けとクローズド向けに何度か再演やりましたが、まあまあわりと評判よいっぽいです!

 

そうそう。今月は誕生月なのでウイッシュリストも貼っておきますね。

https://www.amazon.co.jp/registry/wishlist/1R43BBPSPUEEE/

 

 

 

 

 

 

 

Lindaのeval()の話(MinRuby編)

はじまり

MinRubyならコンテキスト(tree, genv, lenv)もRubyオブジェクトだからdRubyで飛ばせるじゃん。
なにかいい使い道はないものか...

並列プログラムの作り方

並列プログラムの作り方


C-Lindaのeval()

このブログを見てる人たちにはおなじみのタプルスペースの話。
Lindaの操作はoutとin, rd、そしてプロセスを起動するevalです。outはタプルをタプルスペースに書きます。inはパターンで指定したタプルを取り出します(タプルスペースから取り除かれます)。rdはinと同様ですが読むだけでタプルスペースは変化しません。inp, rdpは待ち合わせをしないバージョンのinとrdです。*1

最後のevalが曲者で、実引数の評価(計算?)を別プロセスで行い、全ての実引数の評価結果から成るタプルをタプルスペースに書きます。evalをコールした側には直ちに制御が戻ります。つまり同時にN個のプロセスを生成し、それぞれの結果を待ち、その結果をタプルスペースに書くこと別プロセスが生まれるのです。これって相当ゴージャスな感じがします!

以下、C-Lindaの擬似コード
なんらかのサーバーを起動するならこんなの。server()という関数を実行するプロセスが一つ生成されます。

  eval(sever());

なにかの演算をさせておくならこんなの。"fib"と100とfib(100)を評価するプロセスが三つ生成されて、全てが終わるとタプルを書きます。

  eval("fib", 100, fib(100));
  /* .... */
  in("fib", 100, ?result);

こういうのも動くはず。

  eval("eval", a + b,  c * d, fib(1000), prime(200));

Nプロセスで格子状の計算をするならこう。

  for (i = 0; i < N; i++) {
    eval("row", sum_of_row(i + 1));
  }

  total = 0;
  for (i = 0; i < N: i++) {
    in("row", ?sum);
    total += sum;
  }

Rindaのeval

Ruby版LindaことRindaには標準にはevalがありませんが、MoreRindaを使うと、rinda_evalが追加されます。

10.times do |n|
  # placeはタプルスペース。ブロック引数が子プロセスで実行され、その結果はplaceにwriteされる。
  # サブプロセス側からは、親プロセスのタプルスペースはtsという仮引数で操作できる。
  Rinda::rinda_eval(place) do |ts| 
    [:sqrt, n, Math.sqrt(n)]
  end
end

Rubyでは実引数は呼び手のコンテキストで評価された後に渡されますから、C-Lindaのeval()のような見た目にはなりません。
代わりにブロック引数をforkした子プロセスに実行させることにしました。機能としては同じようなことができますが、見た目がだいぶ違います。
これじゃforkのラッパーにしか見えませんね。ぐぬぬ

MinRubyでeval

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

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


そこでMinRubyです!こちらはインタプリタの挙動を好きなように変えられます。くくく。
並列処理糊言語であるLinda、そのRuby実装Rinda、そしてMinRubyをRindaを使ったアプリケーション専用の糊言語にしてしまえ!

  • インタプリタ自身がインタプリタを実行できる機能(ブートストラップ)は、一旦忘れる
  • インタプリタは(ひとまずは固定のURI)Rinda::TupleSpaceサーバーのクライアントとして動く
  • 実装するプリミティブは、 write, read, take, eval だけ
  • evalで生成される(仮想的な)プロセスは、(UNIXな)ワーカープロセスで実行する
  • evalで評価させる内容はタプルとしてタプルスペースに書く(tree, genv, lenvはMarshal可能なRubyオブジェクトなので安心)

write, read, takeはpと同様に普通のbuiltinメソッドとしてgenvに登録すればいい。
問題はevalです。evalは評価方法を変えたいのでifやwhileみたいなスペシャルフォームにしたいところだけど、min_ruby改造するのもアレなので、とりあえずメソッド種にbuiltinとは別のマークをつけることにしました。

    genv = {
      "p" => ["builtin", "p"],
      "eval" => ["x-builtin", "nop"],
      "go" => ["g-builtin", "nop"], # これはいまは気にしない
      "read" => ["builtin", "ts_read"],
      "take" => ["builtin", "ts_take"],
      "write" => ["builtin", "ts_write"]
    }

func_callで種類がx-builtinのとき、関数コールではなくプロセス生成に変更します。

    when "func_call" 
      mhd = genv[tree[1]]
      case mhd[0]
      when "x-builtin"
        args = []
        i = 0
        while tree[i + 2]
          args[i] = tree[i + 2]
          i = i + 1
        end
        minruby_call(mhd[1], x_evaluate(args, genv, lenv))

実引数をevaluateせずにtreeのまま集めて、genvとlenvと一緒に x_evaluate メソッドに渡します。
現在の実装ではこのあたりからブートストラップ不可能になっていきます。残念。

x_evaluateは実行コンテキスト(tree, genv, lenv)をタプルスペースに書きます。これがワーカーへの指示になります。
その後、ワーカーが書く予定の結果のタプルをtakeして全ての引数分の結果が集まったら、それをタプルスペースに書きます。

  def x_evaluate(args, genv, lenv)
    keys = args.collect do |tree|
      case tree[0]
      when "lit" # リテラルは特別扱いしたけど、もしかすると変数参照も特別扱いするべきかも。
        tree
      else
        key = [tree, genv, lenv].hash
        $ts.write([:x_eval, @node, key, tree, genv, lenv])
        [key]
      end
    end

    Thread.new(keys) do |ks|
      tuple = ks.collect do |tree_or_key|
        case tree_or_key[0]
        when "lit"
          tree_or_key[1]
        else
          _, _, _, value = $ts.take([:x_eval, @node, tree_or_key[0], nil])
          value
        end
      end
      $ts.write(tuple)
    end

    true
  end

ワーカーに処理を依頼するタプルは次のように設計しました。

[:x_eval, @node, key, tree, genv, lenv]
[:x_eval, @node, key, nil]

:x_evalはタプルのおおまかな識別子。@nodeはプロセス固有の乱数です。
keyはコンテキストのハッシュ値です。まあ偶然ぶつかっちゃうかもしれないけど、今は実験だから気にしない。連番でも乱数でもいい。
genvを渡すってことは、定義されている関数全部飛ぶってことなので、けっこうゴージャスだ!

以下は10年前に情報処理に載せたNQueens問題をMinRubyで書き直したやつ。return使えないのツライ。

def check(size, board, q, col)
  k = 0
  succ = true
  while k < col
    case board[k] - q
    when 0
      succ = false
      k = col
    when col - k
      succ = false
      k = col
    when k - col
      succ = false
      k = col
    else
    end
    k = k + 1
  end
  succ
end

def nq(size, board, col)
  if size == col
    1
  else
    found = 0
    q = 0
    while q < size
      if check(size, board, q, col)
        board[col] = q
        found = found + nq(size, board, col + 1)
      else
      end
      q = q + 1
    end
    found
  end
end

def nq2(size, q)
  if check(size, [], q, 0)
    nq(size, [q], 1)
  else
    0
  end
end

size = 10
n = 0
while n < size
  eval("nq2", nq2(size, n))
  # nq2()の実行は別プロセスで行われる。
  # なお、nq2とnqとcheckの関数定義もコピーされちゃうぞ!
  n = n + 1
end
n = 0
found = 0
while n < size
  tuple = take("nq2", nil) # evalの結果を待つ。順序に意味がないのでおわった順に集める。
  p tuple
  found = found + tuple[1]
  n = n + 1
end
p found

まあ動く!ワーカー増やすと速くなる!!ふつう!!!

書いてて飽きてきた!続きはまた今度!!

末尾呼び出し最適化ごっこ

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

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

RubyでつくるRuby」という素晴らしい本があって、toRubyの勉強会で毎月読んでます。そこに出てくるminrubyの話だよ。

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門(紙書籍)www.lambdanote.com



minrubyを改造して末尾呼び出しの最適化ができないか試してみた。

def bar(n)
  foo(n - 1)
end

def foo(n)
  if n > 0
    if n % 1000 == 0
      p n
    end
    bar(n)
  else
    0
  end
end

foo(10000)

カウントダウンを二つの関数をまたぐ再帰呼び出しで実装した例。サブルーチンの末尾(構文木でいう末尾?)がfunc_callだったらそこにマークをつけるようにしたので、ifの分岐先の末尾とかも対象になります。っていうかminrubyにはreturnがないので分岐の先でのfunc_callに対応する必要がありました。


rubyだとこんな感じでスタックが足りません。

$ ruby  loop.rb 
10000
9000
8000
7000
6000
5000
Traceback (most recent call last):
	11913: from loop.rb:27:in `<main>'
	11912: from loop.rb:10:in `foo'
	11911: from loop.rb:2:in `bar'
	11910: from loop.rb:10:in `foo'
	11909: from loop.rb:2:in `bar'
	11908: from loop.rb:10:in `foo'
	11907: from loop.rb:2:in `bar'
	11906: from loop.rb:10:in `foo'
	 ... 11901 levels...
	    4: from loop.rb:10:in `foo'
	    3: from loop.rb:2:in `bar'
	    2: from loop.rb:10:in `foo'
	    1: from loop.rb:2:in `bar'
loop.rb:10:in `foo': stack level too deep (SystemStackError)

改造したminrubyで実行するとこう。

$ ruby interp.rb loop.rb 
10000
9000
8000
7000
6000
5000
4000
3000
2000
1000

なんとか入れ子でも動くようにした。

$ ruby interp.rb interp.rb loop.rb 
10000
9000
8000
7000
6000
5000
4000
3000
2000
1000

追記

末尾にマークつける処理も末尾呼び出し最適化されててかっこよくない?

def mark_tail(tree, genv)
  case tree && tree[0]
  when "func_call"
    mhd = genv[tree[1]]
    if mhd == nil || mhd[0] == "user_defined"
      tree[0] = "tail"
    end
  when "stmts"
    mark_tail(tree[-1], genv) # ここと
  when "if"
    mark_tail(tree[2], genv) # ここは違う
    mark_tail(tree[3], genv) # ここ
  end
end

コードはgistにあるよ

改造したminrubyはこんなの。もっとうまく書けるんだろうなあ。


gist.github.com