TokyuRuby会議13で『現場のRunyVM::AbstractSyntaxTree』というLTをしました
『Scalaスケーラブルプログラミング第3版』読了
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレス
- 発売日: 2016/09/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
読了しました。いわゆるコップ本。手を動かした結果は以下のレポジトリに置きました。
注意点
まず最初に手を動かすにあたっての注意点が1つ。 2.12.8 ではパーザコンビネータとSwingに関しては別ライブラリを追加する必要がありました。 過去のバージョンでは標準ライブラリとして添付されていたものが分離したようです。
$ scala -version Scala code runner version 2.12.8 -- Copyright 2002-2018, LAMP/EPFL and Lightbend, Inc.
https://github.com/hkdnet/cup-book/blob/4c855822a4711b2677507f0018f240a43eb1c28d/build.sbt#L14-L15
// build.sbt libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" libraryDependencies += "org.scala-lang.modules" %% "scala-swing" % "2.1.1"
感想
Scala 面白いですね。強力な言語だなーと思いました。
特に言語機能として面白いのは implicit ですかね。最初見たときはこれなにが起きてるかわけがわからんのでは……と思ったのですが、4つのリーズナブルな制約のもとでならそんなに変なことにならなそうでした。import
をどこにでもかけてそれがレキシカルに参照されるので、読んだ当初は Ruby の refinements っぽいなーという気持ちになりました1。
あと trait に、「これを継承するなら継承するやつはこの型でなければならない」ということを明示できるのは面白かった。
PFDSやTaPLをやんわり読んでいるおかげでかなりスッと腹落ちしやすかったという感じはあります。純粋関数型の Deque とか出てくるし。償却時間とか言い出すし。共変・反変、Top/Bot あたりも出てくるし2。学んだことが別の本に出てくると嬉しいですね。
- 作者: Chris Okasaki,稲葉一浩,遠藤侑介
- 出版社/メーカー: KADOKAWA
- 発売日: 2017/04/28
- メディア: 単行本
- この商品を含むブログ (1件) を見る
- 作者: Benjamin C. Pierce,住井英二郎,遠藤侑介,酒井政裕,今井敬吾,黒木裕介,今井宜洋,才川隆文,今井健男
- 出版社/メーカー: オーム社
- 発売日: 2013/03/26
- メディア: 単行本(ソフトカバー)
- クリック: 68回
- この商品を含むブログ (11件) を見る
一方でなんかちょっと深入りしてないトピックもありそうでした。上述の variance の +
-
を書けるところのルールとかはかなり難しそうですし。あとパス依存型もさっと出てきて引っ込んでいったのであまりよくわかっていない。パス依存型のほうは言われて気づいたのでそういえばよくわからねえな、くらいのわかってなさです。
コップ本を20章まで読み終わったんだけど、variance annotationsとpath-dependent typeはこの本だけではちょっと理解仕切れないな。。
— y.kaneko (@spikeolaf) May 4, 2019
あとコレクションを自前実装するところ、制約が多くて実装者に知識を要求するなーという印象。まあいくつかの triat を無視しててもあとで必要になったときに実装すればいいのかもですが。
もともと Scala 学習のモチベは elasticmq のソースを読むところだったのですがそっちは全然手がついてません。たぶんこの本では触れられてなかったトピックとして、 Akka とかのことを知る必要がありそう。
elasticmq を読むモチベは仕事で使うからだったのですが、まあなんやかんやあって仕事で使わなくなったのでこの先学習するかはまた気分次第ですね。 別件でCコンパイラとかやってたらCPUの気持ちが知りたくなってきたのでまた別の分野に手を出すかもしれません。
C拡張のある gem を作れるようになる
はじめに
fukuokarb.connpass.com この記事は上記イベント会場で書いています。 Speee さん、 Fukuoka.rb の皆様ありがとうございます。
そういえばC拡張のgemって作ったことないなと思ったので作りました。RubyKaigi いくとなんかこういうことやりたくなりますよね。
https://github.com/hkdnet/fibc
このエントリではC拡張の gem ってどうなってるんだっけの解説、というほど高尚なものでもないですが今回認識したことを書きます。
C拡張を動かす
今回はCで複雑なロジックを書くことは考えず、フィボナッチ数列の n 番目の数を返すメソッドを追加することにする。つまりこんなメソッドを追加することを目的とする。
Fibc.fib(5) # => 8
bundle gem --ext fibc
で雛形ができる。 --ext
でC拡張用のファイルも作ってくれる。gemspec にTODOとかが追加されているので、以下それを解消しているものとして扱う。
ファイル構成はだいたいこんな感じ。
. ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin │ ├── console │ └── setup ├── ext │ └── fibc │ ├── extconf.rb │ ├── fibc.c │ └── fibc.h ├── fibc.gemspec ├── lib │ ├── fibc │ │ └── version.rb │ └── fibc.rb └── spec ├── fibc_spec.rb └── spec_helper.rb
実は Rakefile
にもそういう定義が追加されている。さっと確認すると compile
などという単語が見え、:default
のタスク定義にも compile
が追加されているので、ビルドしてからテストするような感じになることがわかる。
$ bundle exec rake -T rake build # Build fibc-0.1.0.gem into the pkg directory rake clean # Remove any temporary products rake clobber # Remove any generated files rake compile # Compile all the extensions rake compile:fibc # Compile fibc rake install # Build and install fibc-0.1.0.gem into system gems rake install:local # Build and install fibc-0.1.0.gem into system gems without net... rake release[remote] # Create tag v0.1.0 and build and push fibc-0.1.0.gem to TODO: ... rake spec # Run RSpec code examples
# Rakefile require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) require "rake/extensiontask" task :build => :compile Rake::ExtensionTask.new("fibc") do |ext| ext.lib_dir = "lib/fibc" end task :default => [:clobber, :compile, :spec]
gemspec の定義にも spec.extensions = ["ext/fibc/extconf.rb"]
という設定があり、gem install 時にどう build すればよいのかを示していることがわかる。extconf.rb
については後述する……予定だったがまあ実際 makefile を生成してるっぽいことくらいしかわからなかった。生成後の結果は tmp 配下に吐かれるので確認可能。
rake compile
すると lib/fibc/fibc.bundle
というファイルができる。これがC拡張をコンパイルしたもの。
最初 gem のエントリポイントである lib/fibc.rb
に require 'lib/fibc/fibc'
と書いてあって、そんなのないじゃんって思ってたのだけど、コンパイルしたものを require している。
ここまでわかったので、あとは ruby/ruby のコードを参考にしながら雑にメソッドを追加してみる。C言語において Ruby のモジュールを定義しているところを適当に参考にしながらやればよい。RubyKaigi で大人気だった RubyVM::AbstractSyntaxTree.of
の定義している箇所を参考にする。ちょうどよく module に1引数のシングルトンメソッドを定義するという意味で非常に似通っている。
ruby/ast.c at 03c6cb5e8f503dcf6bc80a70a48a9775bbd2a47e · ruby/ruby · GitHub
cruby の世界では、Rubyのオブジェクトはすべて VALUE
型であり、また Ruby のメソッドは必ず戻り値をもつ。なので (VALUE self, VALUE n) -> VALUE
な関数を定義してそれを使うことにする。とりあえずステップバイステップに実装するために、引数をそのまま戻す関数を定義してみる。
diff --git a/ext/fibc/fibc.c b/ext/fibc/fibc.c index 71788c7..4beb8a1 100644 --- a/ext/fibc/fibc.c +++ b/ext/fibc/fibc.c @@ -2,8 +2,15 @@ VALUE rb_mFibc; +static +VALUE fibc_fib(VALUE self, VALUE n) +{ + return n; +} + void Init_fibc(void) { rb_mFibc = rb_define_module("Fibc"); + rb_define_singleton_method(rb_mFibc, "fib", fibc_fib, 1); }
テストは適当に書いて動作確認おk1。
あとは適当にCで書いた関数を使いつつ FIX2INT
, INT2FIX
を使いながらCの世界とRubyの世界のオブジェクトを変更しておけば動く。
せっかくなのでベンチマークをとってみる。手元のマシンでやった適当な結果ですけどやっぱはやい。
$ cat bench.rb require 'benchmark/ips' require 'fibc' Benchmark.ips do |x| # These parameters can also be configured this way x.time = 5 x.warmup = 2 # Typical mode, runs the block as many times as it can x.report("naive") { Fibc.naive(10) } x.report("fib") { Fibc.fib(10) } # Compare the iterations per second of the various reports! x.compare! end $ bundle exec ruby bench.rb Warming up -------------------------------------- naive 19.611k i/100ms fib 223.955k i/100ms Calculating ------------------------------------- naive 195.295k (± 8.4%) i/s - 980.550k in 5.061215s fib 4.174M (± 9.1%) i/s - 20.828M in 5.043693s Comparison: fib: 4174204.0 i/s naive: 195295.2 i/s - 21.37x slower
動いてC拡張すごい、という気持ちになったところでおわり。ここまでは特に難しくなかったですね。
C拡張書くときに何が使えるのかとかがわからなかったり、Rubyでの処理がCのどの関数呼べばいいのかとかがわからないので次はもうちょい実践的な何かをチャレンジしていきます。
おまけ
objdump するとダミーシンボル的なものがあることがわかる(わからない(読めない
$ objdump -no-show-raw-insn -arch-name x86-64 -macho -x86-asm-syntax intel -D lib/fibc/fibc.bundle lib/fibc/fibc.bundle: (__TEXT,__text) section _Init_fibc: ec0: push rbp ec1: mov rbp, rsp ec4: lea rdi, [rip + 213] ## literal pool for: "Fibc" ecb: call 0xf5c ## symbol stub for: _rb_define_module ed0: mov qword ptr [rip + _rb_mFibc], rax ed7: lea rsi, [rip + 199] ## literal pool for: "fib" ede: lea rdx, [rip + _fibc_fib] ee5: mov ecx, 1 eea: mov rdi, rax eed: pop rbp eee: jmp 0xf62 ## symbol stub for: _rb_define_singleton_method ef3: nop word ptr cs:[rax + rax] efd: nop dword ptr [rax] _fibc_fib: f00: push rbp f01: mov rbp, rsp f04: mov rdi, rsi f07: call 0xf68 ## symbol stub for: _rb_fix2int f0c: mov edi, eax f0e: call _fibc_fib_int f13: cdqe f15: lea rax, [rax + rax + 1] f1a: pop rbp f1b: ret f1c: nop dword ptr [rax] _fibc_fib_int: f20: push rbp f21: mov rbp, rsp f24: push r14 f26: push rbx f27: mov r14d, 1 f2d: cmp edi, 2 f30: jl 0xf53 f32: mov ebx, edi f34: add ebx, 2 f37: mov r14d, 1 f3d: nop dword ptr [rax] f40: lea edi, [rbx - 3] f43: call _fibc_fib_int f48: add r14d, eax f4b: add ebx, -2 f4e: cmp ebx, 3 f51: jg 0xf40 f53: mov eax, r14d f56: pop rbx f57: pop r14 f59: pop rbp f5a: ret
-
見たければコミットを追ってください↩
GitHub の PullRequest でもう使ってない外部サービスがチェックに出てきて邪魔なとき
レポジトリ側の Settings > Branches
のブランチルールに残っているケースが多い。
ブランチ側のルールの Require status checks to pass before merging
から該当のサービスを抜いてやればよい。
TaPL読書録 #10
前回:
番号がやたらとんでるのは、勉強会はつつがなく開催されていたのですが僕が書くのをめんどくさがっていたら書かれなかったという回がたくさんあるからです。
今回は chap13最初-13.3まで。勉強会の slack があるのですが、TaPLチャンネルではなぜか Rust の話しかしておらず、当日の様子が全く振り返れず謎です。
いままで純粋な言語機能しかなかったが、今回からは純粋でないものを扱う。chap13では特に参照について話す。
p.120 オブジェクト
の話があるが、「副作用を及ぼす関数のかたまり」として定義しているような印象を受けた
p.121 ぶらさがり参照 = dangling reference
「明示的な解放操作のもとでは、型安全性の達成が極めて困難になる」
p.122
非解釈の集合Lをストアでの位置の集合とし
ここでいう「非解釈の」は、集合の要素の並びとか型とかそういうのは気にしないぜ、くらいの意味合いで言っていると思われる。
中段
...がストアμを変更せずに返すことに注意されたい。関数適用は、それ自体は何の副作用も起こさない。
t の操作はすべて純粋な操作であり、ここでは副作用とはμに対する変更として表されている。
脚注
例えばストアについて、位置nがFloatを保持しているという事実は、位置n+4の方について有用な情報を何も与えない。」
それをプログラマに任せているという話であり、人間はよく自分の足を撃つのであった……。
次回 chap 13 終わりまで。
オフラインリアルタイムどう書く E29 の誤回答 (Ruby)
タイトルどおり爆死しました
問題 : http://nabetani.sakura.ne.jp/hena/orde30sumt/
実装リンク集 : https://qiita.com/Nabetani/items/725e09cc5913a8569c04
以下考えたこと。
ざっくりいうと、最終行の必要な幅からどんどん上にいくにつれて必要な幅がたされていく。最終的に、その値がいきつくのが1行目なので、開始終了の幅の分だけとってきたら合計するだけ。
と思っていたのだけどそれでやると40ケース以上おちまして。
だめなパターンがこんな感じ。
あー、これで足すやつどうやって調べようかなーと思っていたら時間切れ。
あとこれは「面白い判定」をそんなに真面目にやってないので、試してないけど並びによってはたまたま面白い数が並んでしまう(例えば上図なら3と5のところ)と合算値が合わなくなる。残念賞ーーー!!
メモ化使いつつ計算するほうほうで再実装するか迷ってるんだけど、書いても無難になりそうなのでスキップ。久々に手ひどく負けたのでした……。
gowrtr で名前付き戻り値を扱う
先日リリースされた gowrtr が非常に便利でめちゃくちゃ助かってます。buf に自分で fmt.Fprintf とかしてたのが懐かしいです。
お気に入りポイントは、immutable なつくりになっているところと、goimports とかをかけた状態で出力できるところです。マジでいい
特に goimports をかけてくれるおかげでパッケージの import 文とかはすげえ適当に書いておいて後からツールに削ってもらうというのが可能になります。makeファイルに後処理書かなくて済んでうれしい。
さて、そんな go-wrtr で func の戻り値を定義しようとすると *FuncSignature
に対して AddReturnTypes
とすることになります。
package main import ( "fmt" "github.com/moznion/gowrtr/generator" ) func main() { g := generator.NewRoot() sig := generator.NewFuncSignature("foo") sig = sig.AddReturnTypes("int32", "error") g = g.AddStatements( generator.NewFunc( nil, sig, generator.NewReturnStatement("1, nil"), ), ) src, err := g.Gofmt("-s").Goimports().Generate(0) if err != nil { panic(err) } fmt.Print(src) }
$ go run tekitou.go func foo() (int32, error) { return 1, nil }
しかし自動生成するときには名前付き戻り値を使いたくなったりします。具体的にはライブラリの関数をラップする関数を書くとき、途中でエラーになったから早期リターンしたいが、各戻り値のデフォルト値を毎回書くのがダルいとかです。
// Foo() (int32, string, err) のようなシグネチャだとして func WrapFoo() (int32, string, error) { err := preprocess() if err != nil { return 0, "", err // ここでデフォルト値を書くのがめんどい } return Foo() } func WrapFoo() (i int32, s string, err error) { err = preprocess() if err != nil { return // こうしたい } return Foo() }
しかし、AddReturnTypes
は string しか受け付けません。これはどうするのかというと AddReturnTypes にそのまま文字列で渡しちゃうのがよさそうです。
package main import ( "fmt" "github.com/moznion/gowrtr/generator" ) func main() { g := generator.NewRoot() sig := generator.NewFuncSignature("foo") sig = sig.AddReturnTypes("i int32", "err error") // 名前も書いちゃう g = g.AddStatements( generator.NewFunc( nil, sig, generator.NewReturnStatement("1, nil"), ), ) src, err := g.Gofmt("-s").Goimports().Generate(0) if err != nil { panic(err) } fmt.Print(src) }
と、ここまで書いたところで気づいたのですが、実は多値を返さずに1つしか return しないときは生成時にエラーになっちゃいますね……。多値のときにお試しください。1
2019-01-21 00:54 ライブラリの名前が間違ってたので直しました(ハイフンとった)
-
パッチ書いてみる予定↩