プレイドオフィス移転パーティー に参加してきました

plaidtech.connpass.com

お邪魔してきました。
たぶん twitter でしか知らない人も何人かいた気がするのだけど、せっかくの機会なので plaid 社の人の話を中心に聞くぞ、ということでブース来訪がメインに据えました。記憶をたよりにどんな話を聞いたのか書いておきます。

とりあえず始まる前に製品のこと知らないし(すみません)聞いとくかーという感じで聞いてきました。というわけで今夏リリースの [Karte live](https://karte.io/product/live/] の話を最初に。

見た感じだとwebサイト来訪者がどんな画面をみてるかリアルタイムに確認できる機能です。ユーザー側がスクロールするとオペレーター側の画面もスクロールされる、というような感じでした。すごーい。web接客ということで、チャットとかのご案内がうまくできるのも魅力ですけどwebサービスのサポートとかに使えるような気もしました。ざっくり話を聞いてるとセッション情報とかイベントとかを全部サーバ側に送信して、ビューアーで再生してるだけっぽいです。reduxっぽいなにか(vue だと vuex ですか?)があればイベントを再生する「だけ」ではあるんだけどいろいろ難しい点がありそうですよね。karteユーザーのサイトがそういう作りしてるかわかんないし。
今ぱっと思いつくだけで「ユーザーとオペレーターでブラウザ/os違ったらイベント再生してもズレちゃうんじゃないの」とか「イベント送信に欠落・追い越しとかがあったらどうすんの」とか「フォーム入力→POSTのイベントとかが複数回発生したりしないの」とか「パスワードとかの入力イベント送らないようにするにはどうするの」とか。実際のパーティ会場ではユーザー/オペレーターの環境差異はどうするんですか、という質問にはビューアー側を頑張ってなんとかしてるという回答を頂いた気がします。嘘言ってたらすみません。イベント送信に関してはよく考えたらそもそも karte 自体がそういうものなのでなんかいい感じに追い越しを並べ替えたりとかできるのかもですね。多少のイベント欠損はなんとかなる気もするし。そういえばパスワード入力フォームとかはそもそも見えないようになんかできるって言ってたから上手いことやってるのでしょう。

あとインフラの話が結構面白かったです。AWS + GCP のマルチクラウドになっていてこの間のGCPのLB障害のときもさっと切り替えて対応できましたー、という話を聞いたのですが、そもそもマルチクラウドになってる経緯が面白かった。AWS EC2上にnode APサーバ群とmongoクラスタを組んで運用してたんだけどmongo部分がスケールせずにGCP上のマネージドサービス(最初は BigQuery 目当てだったって言ってたかな)に移行。でもすでにmongoに入っているデータから移行するのは難しいんだか後回しになってるんだか、まあ両方併用しているのが現状とのこと。うーん、歴史を感じる。
マルチクラウドで障害時にも切り替えて対応できるとはいえいくつか対応できない障害はありそうで、AWS<->GCP間のVPCとかGCPのBQ, Spannarあたりとか、上述のmongoとか外部のマネージドredisとかもだめそう。あと障害時の切り替えってどうやってるんですか?と聞いたら Route53 のフェイルオーバーとのこと。そのへん全然知らんけどそういう機能あるんですねえ……。インフラ周りの知識が浅くて困る。

というわけで参加記録です。全体的に面白かったし、空間的にもいいオフィスでした。

『[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識』読了

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

結構前に読了してたけど書いてなかったので書きます。

ざっというと、OS初心者向けの解説本ですね。タイトルから受ける印象とレベル感が合っていたと思います。
僕自身も適当に ubuntu 触ったりふつリナやったくらいでそんなに詳しいわけじゃないのですが、特に詰まることなく読み進められました。
本書の特徴として書名にもあるように「試して理解」という点が挙げられています。具体的なソースコードと計測結果をもとにどう動いているかを解説するコンセプトは非常に面白かったです。

github.com

(が、上記レポを見ればわかるとおりそんなに自分ではやってません……。特に興味があったところくらい)

なお、Vagrant 上で試す場合には /vagrant のような共有フォルダ上ではファイル操作がうまく動かないことがあるのでご注意ください。確か、mmap とかやってるときにうまくいかなかった。

普段はあんまり意識していないOOMの話やファイルシステムの話に触れられたのがよかったです。この次のステップとして挙げられていた本がだいたいイカツいのでどうしたもんかと困っていますが、まあ気長にやっていこうと思います。はい。

1点だけ難点をあげると、誤記がかなり多かったように思いました。正誤表もあるので、版が進むにつれてよくなっていくとは思います。なのでそんなに気にすることではないかもしれません。

RubyVM::AST を用いた Ruby 製 Ruby インタプリタを作っている話

Ruby では v2.6 から RubyVM::AST というのが入ることになりまして、Ruby ランタイム上から AST を取得できるようになります。この話は以前に書いたので省略しますが、ASTがとれるということはASTを解釈して実行することもできるわけです。やるしかないですね。

というわけでやったのがこれです。まだ全然途中ですが。

github.com

デモ

if 式を解釈できるようす

# spec/snippets/if.rb
a = true
if a
  puts "truthy"
else
  puts "falsy"
end

b = false
if b
  puts "truthy"
else
  puts "falsy"
end
$ bin/stray spec/snippets/if.rb
truthy
falsy

メソッドを定義したりできてるようす

# spec/snippets/method.rb
def foo
  puts :foo
end

foo

def bar(arg)
  puts arg
end

bar :bar

def blk
  yield 1
end

blk do |arg|
  puts arg
end
$ bin/stray spec/snippets/method.rb
foo
bar
1

進捗

プロジェクトとしての進捗はこんな感じです。今回は紹介程度に留めて、実装時に留意したことなどは別エントリに書きます。インタプリタの実装をするのは初めてだったのでいろんな壁にぶつかりながらやってます。

できること

  • Integer, String, Symbol, Array あたりのリテラル
    • Float とかそういうのはちょっと面倒なんでやりません
  • ローカル変数代入
  • メソッド定義
    • オプショナル引数、キーワード引数はまだ
  • メソッド呼び出し
  • yield
  • if 式
  • ぼっち演算子

できないこと

  • クラス定義
    • サボってます
    • rb_cClass とかの定義を見てそれに合わせないとダメなのはわかってるんだけどオレオレ実装してしまったので修正が面倒……。
    • 当然継承とか include とか extend とか using ( refinements )とか
  • Kernel モジュール、Objectクラスに定義されてるだいたいのもの
    • 実装するのがめんどくさくて puts くらいしかやってない
  • case 式
    • === をちゃんとするのが単純にめんどくさそうでやってない
  • lambda とか

ちょっと外れた話

cruby のソースを読んでたらフォーマットが他と違うところを発見したのでPRしました。

Use nd_X shorthand for annotation by hkdnet · Pull Request #1901 · ruby/ruby · GitHub

また、実装していると NODE_DEFN から nd_mid を取れないことに気づきました。 NODE_DEFNdef foo; end のようなメソッド定義を示すノード種別です。nd_mid はメソッド名に対応するシンボルです。先程の例だと :foo という情報です。これがないとなんのメソッドを定義したのかわかりません。 twitter で y.kaneko さん( @spikeolaf )に質問したら快く修正してくれました。

新しい機能を実際に使ってみた結果をフィードバックをする人はたぶん少ないので意味がありそうですね。

オフラインリアルタイムどう書く E25 の解答

qiita.com

オンライン参加で解けました。17:02なので45分くらいでしょうか。

方針としては、たかだか200x200マス = 40000通りなので、スタート地点以外の1点を選んでベクトル作ってベクトル回して伸ばして座標が盤面内にあるかどうかを確認するので間に合いそうです。おわり。

というのを問題をみて3分くらいで決めたんだけど、回転下手なのと盤面の境界条件を間違えるといういつもので苦戦してました。 というわけでコード。割と汚めです。

class Solver
  def initialize
  end

  def solve(input)
    w, h, str = input.split(',', 3)
    @w = w.to_i
    @h = h.to_i
    x, y = str.scan(/\d+/).map(&:to_i)
    answer = {}
    s = [x, y]

    @w.times do |sx|
      @h.times do |sy|
        v = [sx - x, sy - y]
        ps = points(s, v)
        if ps.all? { |p| valid?(p) }
          l = len_of(v)
          answer[l] ||= []
          answer[l] << ps
        end
      end
    end

    m = answer.keys.max
    if answer[m].size == 1 && m != 0
      answer[m].first.map { |p| "(#{p.first},#{p.last})" }.join(",")
    else
      '-'
    end
  end

  def len_of(v)
    Math.sqrt(v.first**2 + v.last**2)
  end

  def valid?(p)
    (0...@w).include?(p.first) && (0...@h).include?(p.last)
  end

  def points(s, v)
    sx, sy = s
    a, b = v
    p1 = [sx + a, sy + b]
    p2 = [p1.first - b, p1.last + a]
    p3 = [p2.first - 2*a, p2.last - 2*b]
    [p1, p2, p3]
  end
end

リファクタリングしたのでこっちのがみやすいかも。あとサイズ小さいやつとか見る意味ないじゃんって気づいたので途中で next いれるようにしました(max_l あたり)

class Solver
  Pair = Struct.new(:x, :y) do
    def add(other)
      Pair.new(x + other.x, y + other.y)
    end

    def sub(other)
      Pair.new(x - other.x, y - other.y)
    end

    def multi(s)
      Pair.new(x * s, y * s)
    end

    def rotate270
      Pair.new(-y, x)
    end

    def to_s
      "(#{x},#{y})"
    end

    def size
      Math.sqrt(x**2 + y**2)
    end
  end

  def solve(input)
    w, h, str = input.split(',', 3)
    @w = w.to_i
    @h = h.to_i
    x, y = str.scan(/\d+/).map(&:to_i)
    answer = {}
    s = Pair.new(x, y)
    max_l = 0

    @w.times do |sx|
      @h.times do |sy|
        v = Pair.new(sx, sy).sub(s)
        next if v.size < max_l
        ps = points(s, v)
        if ps.all? { |p| valid?(p) }
          l = v.size
          answer[l] ||= []
          answer[l] << ps
          max_l = l if l > max_l
        end
      end
    end

    m = answer.keys.max
    if answer[m].size == 1 && m != 0
      answer[m].first.map(&:to_s).join(",")
    else
      '-'
    end
  end

  def valid?(p)
    (0...@w).include?(p.x) && (0...@h).include?(p.y)
  end

  def points(s, v)
    p1 = s.add(v)
    p2 = p1.add(v.rotate270)
    p3 = p2.sub(v.multi(2))
    [p1, p2, p3]
  end
end

TaPL読書録 #5

前回: hkdnet.hatenablog.com

今回は6章まるごと。

アルファ変換を許可する、というのは第5章で学んだが、しかし変数名が重複してるんだっけ?という疑念は常につきまとう。特に理論的な部分ではなく実装するという観点においては「標準の」の表現を定めておいたほうがいいとうことで デブルーイン ド・ブラウンの de Bruijn index という概念を導入してがんばるよ、という章である。

p.57
手法(4) の「明示的代入」とはなにか?がよくわからなかった。"explicit assignment" か?と思いググってみたがそれっぽいのが見つからず……( "explicit assignment lambda calculus" でぐぐった)。その場では放置したが後日論文から引けばいいのか、と思って著者から検索したら以下がヒット。 explicit substitution だったらしい。

http://www.hpl.hp.com/techreports/Compaq-DEC/SRC-RR-54.pdf

p.59 \w. y w -> \. 4 0 なのが初見殺しっぽい。ラムダ内なのでシフトが起きていて、名前付け文脈Τにおいて y = 3 なのに 4 になっている。

p.59 演習 6.1.5
ここの問題により「同じ名前がでてきても大丈夫であること」が確認できたとの声あり。

p.59 最終段落
[1 -> s](\. 2) の元になる de Bruijn index を用いない表現を考えられるか?という問題で僕が「これでしょ〜」って提示したのが間違ってて死亡。
提示したのは T: z => 2 という名前付け文脈における \x. -> x z だったのだけどこれはラムダ内に入るときにシフトが起きるので T: z => 1 における、 \x. -> x z が正しい(直後に書いてある)。

演習問題としてはやるだけが多かったのでさくさく進み、やるだけじゃないのが出てきたときはみんな「わからん」と言っていたのでアレだった。
さくっと答え合わせしたものを以下に書いておく

ex 6.2.3 -> スルーした
ex 6.2.6 -> 代入しても中でラムダができた場合との増減で相殺するからそうなるんじゃないの、とのこと
ex 6.2.8 -> スルー ex 6.3.1 -> 「0があってもラムダがあるのでセーフ」という走り書きが残っている
ex 6.3.2 -> ラムダの数をLとした場合に de Bruijn index i と de Bruijn level l について l = L - i として変換すればヨサソウ

次回7章全部。7章の末尾には「実装したからといって、理解したとは限らない」という挑戦的な文言が載っていることに注意。

RubyKaigi2018 基調講演2日目レポートで書かなかったことの補足

gihyo.jp

本記事は上記リンクの記事に関する補足です。プログラミング初心者です、Rails しかやってません、みたいな人でもわかることが望ましいとは思っていたのですが紙面の都合及びやたら技術解説がある記事になってもなあと思い書きませんでした。
明らかに説明足りてねーよな、というところについて補足します。

バインディング

Ruby/GTK3はGTK+プロジェクトのRubyバインディングです。言い換えると,C言語で書かれたGTK+にあるAPIRubyから実行できるようにするライブラリです。

バインディング」というのがそもそも「他言語で書かれたライブラリにあるAPIを、ある言語で実行できるようにするもの」みたいな感じですかね。別の言葉では ffi ( foreign function interface ) と呼ばれています。wikipedia によると ffiCommon Lisp 由来の言葉で、バインディングというのが Ada などに由来すると書かれています。
C や C++ で書かれているミドルウェアより下のレイヤについて Ruby などのからアクセスするのはだいたいバインディングの形式をとっている気がします。たまーにそういったバインディングではなくピュア実装です、と銘打ってるライブラリがあったりします。例えば nodejs の mysql ライブラリは "A pure node.js JavaScript Client implementing the MySql protocol." と自称しています。おそらくですがバインディングを使うとC, C++ のレイヤでブロックしてしまいノンブロッキングにならないからだと思います。

rb_gc_adjust_memory_usage

バインディングを利用していると、あるオブジェクトを new するときに実際には他にメモリ領域を確保している、ということが起きがちです。

  1. Ruby レベルで foo = MyLib::Foo.new する
  2. バインディングしてるCレベルで malloc がおきる <- コレ
  3. foo がスコープを抜けたとかでGCされる
  4. foo のデストラクタで free される

RubyGC があるので上記のままでも特に問題がないように思えますが、このままではGCを実行すべきタイミングをうまく予想できないという問題が隠れています。 Ruby から見るとあくまでも fooMyLib::Foo インスタンス1つ分のメモリしか使用していません。そのため Ruby は「まだ全然メモリ使ってないからGCしなくていいや」という判断をしてしまいます。しかし実際には malloc で確保されたメモリも使っています。これが記事中の「Ruby本体以外から確保されたメモリ量を認識できない」という問題です。
追加された rb_gc_adjust_memory_usage 関数は、malloc, free したメモリ量を記録することで Ruby に使用しているメモリ量を認識させることができます。

datadog の ruby ライブラリ dogapi で batch_metrics をネストするとダメそう

NOTE: v1.3.0 で見てます。

先日 datadog にメトリクスなげてーなと思ったのでライブラリの使い方を見てたのですがあんまり出来がよくなさそうです。気になったのはここ。

dog.batch_metrics do
  dog.emit_point('test.api.test_metric',10)
  dog.emit_point('test.api.this_other_metric', 1, :type => 'counter')
end

docs.datadoghq.com

batch_metrics というメソッド自体は、1回1回リクエストを投げずにためて投げるというやつですね。ふつーのバッチ処理

ですが、サンプルコードを見ただけでなんとなーくあやしい感じがします。内部実装を素直に予想するとこんな感じになりそうですね。

def batch_metrics
  @batch_mode = true # 逐次で投げないようにするフラグ
  yield
  @batch_mode = false
  flush! # ここでためたのを投げる
end

でもこれだとこういうときに困りそうです。手元で spec 書いたら落ちるしたぶんダメ。

dog.batch_metrics do
  # @batch_mode = true になる
  dog.batch_metrics do
    dog.emit_point('test.api.test_metric',10)
  end
  # @batch_mode = false になってしまう
  dog.emit_point('test.api.this_other_metric', 1, :type => 'counter') # 逐次で送られるんじゃね?
end

実際には @buffernil かどうかで同じようなことをやっています。このへんを参照。

https://github.com/DataDog/dogapi-rb/blob/d66698a363ae851845525d30a7d6a8afa1927dc5/lib/dogapi/facade.rb#L108-L116

https://github.com/DataDog/dogapi-rb/blob/d66698a363ae851845525d30a7d6a8afa1927dc5/lib/dogapi/v1/metric.rb#L52-L58

これより良さそうな API もあって、こんな感じ。

dog.batch_metrics do |batch_dog| # バッチリクエスト用のクライアントを返す
  batch_dog.emit_point('test.api.test_metric',10)
  batch_dog.batch_metrics do |batch_dog2| # batch_dog と batch_dog2 は同じものを返せばよい
    batch_dog.emit_point('test.api.test_metric',10)
  end
  # 最初の block から抜けるときに作られたものを flush すればよい
end

というのでなんかよくできるんじゃないかなーとプロジェクトを見てたら rspec なのに let 使ってなかったりでちょっと心が折れた日曜日なのでした。

まあ実際こういうの送るときにネストしたりしないからこまんないような気もしますね