2017/02/01


以前からずっと疑問に思っていた事があった。

ruby の後置 if/unless で条件が偽になった場合でも代入構文が実行されるのはどうしてだろう

例えば以下のコードを irb や pry で実行してみて欲しい。

a = 1 if false

続けて a をタイプする。すると nil が表示される。

僕のこれまでの理解だと後置if/unlessは、ステートメントに作用するのでそのステートメント自体が無効になる、つまり代入自体されなかった事になるという理解だった。ruby のパーサのソースコードを見ても後置ifはステートメントに作用している様だった。

        | stmt modifier_if expr_value
            {
            /*%%%*/
            $$ = new_if($3, remove_begin($1), 0);
            fixpos($$$3);
            /*%
            $$ = dispatch2(if_mod, $3, $1);
            %*/
            }
だって raise "foo" if false で例外が飛ばないなら、代入もされないでしょと思っていたけど nil が代入される。この ruby のコードを AST ダンプするとこうなる。
# @ NODE_SCOPE (line: 1)
# +- nd_tbl: :a
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_PRELUDE (line: 1)
#     +- nd_head:
#     |   (null node)
#     +- nd_body:
#     |   @ NODE_IF (line: 1)
#     |   +- nd_cond:
#     |   |   @ NODE_FALSE (line: 1)
#     |   +- nd_body:
#     |   |   @ NODE_DASGN_CURR (line: 1)
#     |   |   +- nd_vid: :a
#     |   |   +- nd_value:
#     |   |       @ NODE_LIT (line: 1)
#     |   |       +- nd_lit: 1
#     |   +- nd_else:
#     |       (null node)
#     +- nd_compile_option:
#         +- coverage_enabled: true

これを見ると、AST に落とし込んだ時点でノードテーブルに a が現れている。つまり、後置ifが偽であろうとも代入構文を認識しているという事になる。この AST をどう walk しても代入構文には到達しないよなーと悩んでソースを見たりしたけど良く分からなかったので Matz に直接聞いた。

つまり ruby はこの stmt modifier_if expr_value を見つけると、まずステートメントが何かを判定して代入構文であれば変数 a を用意し、そのあと後置ifを判定して最後に代入という動きを取る。もちろん後置ifが偽であれば代入はされない。よって a は初期化されたままの nil が格納されるという事になる。これを理解した上で以下を見ると、なぜ NameError: undefined local variable or method `a' for main:Object ではなく NoMethodError: undefined method `+' for nil:NilClass なのか理解できた。なるほど深い。

irb(main):001:0> a = a + 1
NoMethodError: undefined method `+' for nil:NilClass
        from (irb):1
        from c:/msys64/mingw64/bin/irb.cmd:19:in `<main>'
irb(main):002:0>

2017/01/25


golang にはパッケージマネージャが無数にあります。

PackageManagementTools · golang/go Wiki · GitHub

Home Articles Blogs Books BoundingResourceUse cgo ChromeOS CodeReview CodeReviewComments CodeTools C...

https://github.com/golang/go/wiki/PackageManagementTools

僕もその一つの gom というのを開発している訳ですが、どれもこれも一長一短でなかなか全ての要望を応えられる物がないのが現状だったりします。ただし、開発者がやりたい事は

今 GOPATH にある、うまくビルドできるバージョンに依存したい

ただこれだけなのです。なんで golang はオフィシャルがこの手のツールを出してくれないんだろう、そう思っていた人も多いと思います。が、それは昨日までの話。

GitHub - golang/dep: Go dependency tool

See the help text for much more detailed usage instructions. Note that the manifest and lock file fo...

https://github.com/golang/dep

でたー!みんな待ってた。そしていつもながら他のツールと名前がバッティングしそうなこの名前の短さ!

使い方も簡単です。まずプロジェクトを作ったら

$ dep init

を実行します。すると lock.jsonmanifest.json が生成されます。この時点では中身はほぼ空っぽです。試しに以下のコードを書いてみます。

package main

import (
    "github.com/mattn/go-runewidth"
)

func main() {
    println(runewidth.StringWidth("あ"))
}

そして以下を実行。

$ dep ensure

すると lock.json が更新されます。

{
    "memo": "e3dcff295c3782f2465033314eee2342535b0a792a61ebf41e0deeab5747a0e7",
    "projects": [
        {
            "name": "github.com/mattn/go-runewidth",
            "version": "v0.0.1",
            "revision": "d6bea18f789704b5f83375793155289da36a3c7f",
            "packages": [
                "."
            ]
        }
    ]
}

dep status を実行すると、go-runewidth の特定のバージョンで stick されているのが分かるかと思います。では試しに

PROJECT                        CONSTRAINT  VERSION  REVISION  LATEST  PKGS USED
github.com/mattn/go-runewidth  *           v0.0.1   d6bea18   v0.0.1  1  

試しに vendor ディレクトリを削除して、go-runewidth のリポジトリで別ブランチに切り替えておきます。そして再度 dep ensure を実行すると、ちゃんと stick したバージョンがよみがえる!

これや!ワイはこれが欲しかったんや!

今のところ、init と status と ensure と remove しかサブコマンドがありませんが、おそらく今後色々と増えてくるんじゃないかなーと思っています。チョー期待してます。

ちなみにいずれ go のサブコマンド(go dep)の様に取り込まれる可能性があるので慎重派の人はしばらくは遊ぶ程度にしておいた方が良さそう。


2017/01/19


go-bindata もいいけど、go-assets もいいよ。

Go でシングルバイナリな Web アプリを開発しているときに webpack --watch をうまいところやる - Diary

Go でシングルバイナリな Web アプリを開発しているときに webpack --watch をうまいところやる 個人的なアプリをつくるとき、だいたい以下のような環境で作業しています WAF は E...

http://diary.app.ssig33.com/166

みんなのGo言語にも書いた気がするのでそういうの興味ある人は買って下さい。

みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太
技術評論社 / ¥ 2,138 (2016-09-09)
 
発送可能時間:在庫あり。

バイナリに assets を埋め込む際には go-bindata が有名ですが、実は go-assets も便利です。

GitHub - jessevdk/go-assets: Simple embedding of assets in go

README.md go-assets go-assets is a simple embedding asset generator and consumer library for go. The...

https://github.com/jessevdk/go-assets

何が便利かというと、go-assets はファイルシステムを持っている。go-bindata だと自前で Assets から取り出し ResponseWriter に書き込む必要がありますが go-assets だと

package main

import (
    "net/http"
)

//go:generate go-assets-builder -s="/data" -o bindata.go data

func main() {
    http.Handle("/", http.FileServer(Assets))
    http.ListenAndServe(":3000"nil)
}

こんだけでいい。go-assets を使うには go-assets-builder を使う。

GitHub - jessevdk/go-assets-builder: Simple assets builder program for go-assets

Usage: go-assets-builder [OPTIONS] FILES... Help Options: -h, --help= Show this help message Applica...

https://github.com/jessevdk/go-assets-builder

上記のコードに go:generate が書いてあるので data の中に assets を放り込んで go generate とすると bindata.go が出来上がる。