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 ライブラリの名前が間違ってたので直しました(ハイフンとった)
-
パッチ書いてみる予定↩
gocode での補完をやめるが vim-go は使う
どうも vim 界隈でも lsp の機運が高まっており gocode ではなく golsp などを使うことを推奨されているっぽい。
一方で今はもうすでに vim-go に手が馴染んでいるわけだが、と思って腰が重かったのだけど、補完部分だけを golsp にすることができた(気がする)
雰囲気で gocode とか使ってそうなところを消している。
実際書いてるとこんな感じに golsp が vim から立ち上がってるのがわかるのでまあよさそう。
$ pstree -s golsp -+= 00001 root /sbin/launchd \-+= 01328 hkdnet tmux \-+= 29150 hkdnet -zsh \-+= 45875 hkdnet vim \--= 46034 hkdnet golsp -mode stdio
なお最近は GoLand で書いている
低レイヤを知りたい人のための Cコンパイラを Rust で作っている
↑の続きというわけで Rust 版を作っている
Rust 化して気づいたのは Vec
また、Node について binary operator を考えると当然 Node<Node, Node> のようなものにならざるを得ないのだが1、この場合に Node が再帰的に定義されてしまうため、sizeof(Node)
が一意に定まらないという問題があることがわかった。この場合に Rust 的には Box
あー、Aに必要なメモリを確保しようとしたときに、再帰的にAをもってると何バイト必要なのかが定まらなくて困るのか。そこでヒープに確保してそこへのポインタをもつようにしてやればサイズが決定されると
— はくどー (@HKDnet) January 4, 2019
Rust 特有のアレコレについて悩みながらもやっている。特にCが書けるようになりたいわけでもないので2このまま Rust 版を育てるかもしれない。
『低レイヤを知りたい人のための Cコンパイラ作成入門』感想
インターネット上で公開されている範囲については実装が終わりました。
内容について
非常によかったです。わかりやすかったです。僕は技術書の場合、だいたい冒頭だけ読んで、実装してみて、本の内容と比較するという読み方をしています。この本については、ある機能を実装するにあたって、「このへんは今はやらなくていいや」と割り切るのが上手いと感じました。実装していると「これもやらなきゃじゃん」「こうなってるほうがいいよなー」みたいな気持ちがどんどん生まれますが、実際にを読んでみると「これは後ででいいです」とバッサリ。これはCコンパイラを複数回作った経験とコンピューターサイエンスに関する体系的な知識があるがゆえにできているのかなーと思っています。
このコンテンツを読む前に期待している点としては、「言語実装に関する理解が深まること」と「アセンブリ、機械語を生成するフェイズの知識が得られること」の2点でした。やってみた感じとしては、前者は特に深まったなーと思っています。tokenize, parse というフェイズごとに何をしているのかという点や実際のバイナリをつくるまでの過程というのが理解できました。
一方で後者は、いまだによくわかりません。吐くアセンブリの意味を正しく理解できているかというとあやしいし……。これはどちらかというと文章としての問題ではなく、読み手としてアセンブリと仲良くなる努力がまだ足りてないのかなーと思っています。どうにかしないとなー。いい教材みたいなのがあると助かります。fizzbuzzみたいなのを自分で書いてみるか。
これから
上記のアセンブリと仲良くなるっていうのと、あとはCで書くのがダルいので他言語で実装しなおすかもしれません。あるいはもうちょい自力ですすめるか。あと解析フェイズの理解が進んだせいで TaPL の言語実装が適当すぎることに気づいたのでそっちを直したくもなっています。どうしよう。
動いているようす
こんな感じになります。ちゃんと動いてますね。中間生成物のアセンブリもチラ見せ。
$ make test bin/hkdcc -test OK ./test.sh 0; => 0 42; => 42 1+2; => 3 1+10+2; => 13 3-1; => 2 23-12; => 11 3 - 1; => 2 70+2*5*5; => 120 4/2; => 2 5+6*7; => 47 5*(9-6); => 15 (3+5)/2; => 4 1; 2; => 2 a = 1; => 1 a = 1; a; => 1 a = 1; a + 1; => 2 a = b = 1; a + b; => 2 1 == 1; => 1 a = 1; 1 == a; => 1 a = 2; 1 == a; => 0 a = 2; a == 1; => 0 a = 2; a == a; => 1 a = 1 == 1; a; => 1 a = 0 == 1; a; => 0 1 != 1; => 0 2 != 1; => 1 OK $ cat tmp.s .intel_syntax noprefix .global _main _main: push rbp mov rbp, rsp sub rsp, 208 push 2 push 1 pop rax pop rdi cmp rdi, rax setne al movzx rax, al push rax pop rax mov rsp, rbp pop rbp ret
signal.Notify したチャンネルを close すると死ぬ(可能性がある)
TL; DR
signal.Stop
しよう
https://golang.org/pkg/os/signal/#Stop
検証
ダメな例
package main import ( "fmt" "os" "os/signal" "syscall" "time" ) func main() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) close(c) fmt.Println("C-c plz") time.Sleep(10 * time.Second) }
$ go run main.go C-c plz ^Cpanic: send on closed channel goroutine 5 [running]: os/signal.process(0x10dbc60, 0xc00002aa30) /usr/local/Cellar/go/1.11.1/libexec/src/os/signal/signal.go:227 +0x163 os/signal.loop() /usr/local/Cellar/go/1.11.1/libexec/src/os/signal/signal_unix.go:23 +0x52 created by os/signal.init.0 /usr/local/Cellar/go/1.11.1/libexec/src/os/signal/signal_unix.go:29 +0x41 exit status 2
ある channel がクローズ済かは送信者はわからないので仕方がない。
大丈夫な例
package main import ( "fmt" "os" "os/signal" "syscall" "time" ) func main() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) signal.Stop(c) // signal.Stop を追加した close(c) fmt.Println("C-c plz") time.Sleep(10 * time.Second) }
$ go run main.go C-c plz ^Csignal: interrupt
OK
背景
graceful restart 的なことをするときに、シグナルでハンドリングをしていた。のだが、場合によっては即座に死んでほしくないパターンがあった。API通信相手がメンテ中だとわかっている場合とかは即死すると即死→メンテ→即死→メンテの無限ループって怖くね?状態になる。
しょうがないのでメンテのときはある程度待ってから死ぬようにしてみるか、と思って書いているわけだが、シグナルハンドリングが終わったあとの息が長くなるとリソースの解放漏れが気になってきた。そこでさくっと close しようとしたら、これ死ぬんじゃね?って思って調べて今に至る。
ここまで書いた後でさっさと死んで起動直後にメンテ中か確認すれば特に悩む必要なかったなって思った。おしまい。
オフラインリアルタイムどう書く E29 の回答 (Golang)
問題 : http://nabetani.sakura.ne.jp/hena/orde29unes/
実装リンク集 : https://qiita.com/Nabetani/items/f2db9b916c0a301b744f
勝ちました。珍しく Golang で。
なんかかっこいいやり方あんのかなーと思ったけど、1文字ずつ素直に読んでけばいいやって感じに。こういうの1文字ずつ読んでなんかの区切りで返すのって lexer だよねって思ってそんな名前にしております。TaPLの言語を実装している経験が役にたっている。
l.cur += 2
のところは slash
の mode を用意すればいいということを他の人の回答から学んだ。なるほど。
byte リテラルの書き方がわからなくてなんかアレな感じになっている。
package e29 import ( "errors" "fmt" "strings" ) type lexer struct { cur int text string mode lexerMode } type lexerMode int32 const ( normal lexerMode = iota singleQuote doubleQuote ) var slashByte, dQuoteByte, sQuoteByte byte var validChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\"'/") const noAnswer = "-" type result struct { output string err error } func init() { tmp := "/\"'" slashByte = tmp[0] dQuoteByte = tmp[1] sQuoteByte = tmp[2] } func solve(input string) result { l := lexer{text: input} var tokens []string for l.Next() { token, err := l.NextToken() if err != nil { return result{output: noAnswer, err: err} } tokens = append(tokens, token) } if l.mode != normal { return result{output: noAnswer, err: errors.New("unterminated quote")} } if len(tokens) == 0 { return result{output: noAnswer, err: errors.New("no entries")} } output := strings.Join(tokens, ",") return result{output: output, err: nil} } func (l *lexer) Next() bool { if l.cur == len(l.text) { return false } return true } func isValidByte(b byte) bool { for _, bb := range validChars { if b == bb { return true } } return false } func (l *lexer) NextToken() (string, error) { var ret []byte beg := l.cur loop: for { if l.cur >= len(l.text) { return "", errors.New("idx") } c := l.text[l.cur] if !isValidByte(c) { return "", fmt.Errorf("invalid char: %v", string(c)) } switch c { case slashByte: if l.mode == normal { if l.cur+1 == len(l.text) { // 終わり return "", errors.New("should not end with /") } if l.text[l.cur+1] == slashByte { // スラッシュ2個のパターン ret = append(ret, c) l.cur += 2 } else { l.cur++ break loop } } else { // quote 中は / として扱う l.cur++ ret = append(ret, c) } case dQuoteByte: if l.mode == normal { l.mode = doubleQuote l.cur++ } else if l.mode == doubleQuote { l.mode = normal l.cur++ } else if l.mode == singleQuote { l.cur++ ret = append(ret, c) } case sQuoteByte: if l.mode == normal { l.mode = singleQuote l.cur++ } else if l.mode == doubleQuote { l.cur++ ret = append(ret, c) } else if l.mode == singleQuote { l.mode = normal l.cur++ } default: l.cur++ ret = append(ret, c) } if l.cur == len(l.text) { break loop } } entry := string(ret) if entry == "" { return "", fmt.Errorf("entry should not be blank %d-%d", beg, l.cur) } return entry, nil }
Rails で全部のモデルについて何かの処理をしたい
eager_load = true にした上で ↓ のスクリプトを bundle exec rails r
する
ar_classes = [] ObjectSpace.each_object do |obj| next if obj.class.name.to_s != 'Class' if obj.ancestors.include?(ActiveRecord::Base) && obj != ActiveRecord::Base ar_classes << obj end end # 本体の処理
今回は実際にはテーブル名がほしかったのでこんな処理を。
table_names = ar_classes.map do |klass| klass.respond_to?(:table_name) && klass.table_name end puts table_names.compact.sort
SwitchPoint
使ってるとそれの子供が釣れるみたいだけど気にしない。
Rails だったら descendants 使う方が良さそう
— たにみち (@ttanimichi) 2018年11月29日
完全にせやな、であった