詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

Go で reflect パッケージを使わずにジェネリックな関数を実現する

あけおめ〜。Go 界においてジェネリクスを求めるのは(今のところ)はかない望みでしかないし、もちろん重々承知していることですが、それでもときどき複雑なものを書こうとするとどうしても複数の型に対応する関数が欲しくなる。そこでこの冬休みになにかうまい方法はないかと考えて、作ってみました。

要件

今回はこんな関数が実現したくなりました。(擬似コードです)

// map[string]int とか map[string]foo.Bar を受けつける
func keys(m map[string]T) []string {
    keys := make([]string, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

そしてこれを実現するためのアプローチを以下のように定めました。

  1. reflect パッケージを使わず、コード生成で対応する。
  2. コード生成前のコードもコンパイルできるようにする。
    • 書いている途中でも補完などが効くように。
  3. コード生成の際、対象の関数以外には手を加えない。
    • 呼び出し元はすべて同じ keys 関数の呼び出しになっていて、keys_bool(m) とか keys(wrapper(m)) とかに書き換えられない。

コンパイルできるが実行時にエラーとなるコードを、コンパイルできて実行時にもエラーとならないコードに変換する、という寸法です。実行時の安全性は変換器によって保証します。

1 を実現しているツールとして、例えば gengen があります。

方法

上記の keys 関数のシグネチャでは汎用的な型の引数を受け付けられないので interface{} に変更し、代わりに型を type switch で指定するように変更します。以下はそのスタイルで書いた完全なコードです。

// keys.go
package main

import “fmt”

type T interface{}

func keys(m interface{}) []string {
    switch m := m.(type) {
    case map[string]T:
        keys := make([]string, 0, len(m))
        for key := range m {
            keys = append(keys, key)
        }
        return keys
    default:
        panic(fmt.Sprintf("unexpected value of type %T", m))
    }
}

プレースホルダ的な型として、interface{} である T という型を定義します。(interface{} である、大文字で構成された型をプレースホルダとみなすことにします。)

これは他の場所から keys(map[string]bool{}) のように呼び出してもコンパイルできるコードになっていて、実行時に panic します。ならばあとは case 節を実際の型にあわせて増やせばよい、というわけでコード生成の出番です。この関数がどんな引数で呼び出されているかを解析し、それにあわせて case 節を増やしてやります。

実装

ここまでの話を実装したのが motemen/go-typeswitch-gen です。これに tsgen というコマンドが含まれています:

go get github.com/motemen/go-typeswitch-gen/cmd/tsgen

使い方は簡単で、汎用化したい、type switch を含んだ関数の定義されているファイルをコマンド引数として渡します。

tsgen keys.go

自動的に同じパッケージからの呼び出しを検出し、型のパターン(map[string]T など)を実際の引数の型(map[string]bool など)と突き合わせて case 節を追加した type switch を生成し、変更後のファイルの内容を標準出力に印字します。また -w を与えるとファイルを直接書き換えます。

上の keys の例だと、以下のように展開されます。

func keys(m interface{}) []string {
    switch m := m.(type) {
    case map[string]bool:
        keys := make([]string, 0, len(m))
        for key := range m {
            keys = append(keys, key)
        }
        return keys
    case map[string]T:
        keys := make([]string, 0, len(m))
        for key := range m {
            keys = append(keys, key)
        }
        return keys
    default:
        panic(fmt.Sprintf("unexpected value of type %T", m))
    }
}

またあまり出番はないかもしれませんが var x T のように書いてあった場合も var x bool として展開されます。

リポジトリの _example ディレクトリに例が同梱されているので、それを使ってみると簡単です:

% ghq get motemen/go-typeswitch-gen
% ghq look motemen/go-typeswitch-gen
% cd _example/keys
% tsgen -w keys.go
% git diff

また呼び出し元の解析のためには main 関数かテストが必要なので、対象のファイルと同一パッケージにそれらがない場合には -main 引数で指定します。

% tsgen -main github.com/motemen/foo ./util/util.go

go generate と組み合わせる

Go 1.4 から導入された go generate と組み合わせて使うのも簡単です。ファイルの先頭に以下のように書くだけ:

//go:generate tsgen -w $GOFILE

go generate すると当該のファイルが書き換わります。

既知の問題・今後の課題

import が自動的に増えない

Tio.Reader などがマッチした場合 case io.Reader: が生えますが、import "io" は追加されません。とりあえず goimports でしのげます。

コメントの位置が変になる

ベースとなる case 節にコメントを書いていた場合、生成後のコードでコメントの位置が変になります。

返り値を汎用化できない

さまざまな引数の型で同じ関数を呼び出せるようにしている都合上、返り値を汎用化させることはできません。呼び出し元の type assertion を解析するというのはできそう。 現在のところはコールバック関数を引数として与えることで任意の型を受け取れます。foreach の例:

func foreach(a interface{}, cb interface{}) {
    switch a := a.(type) {
    case []T:
        switch cb := cb.(type) {
        case func(int, T):
            for i, e := range a {
                cb(i, e)
            }
        }
}

「任意の数値型」のようなプレースホルダを定義できない

プレースホルダが interface{} だと加算が定義されていないので、数値の配列の総和、のような関数が定義できません。たぶんプレースホルダを type NumberT float64 のように定義できればよさそうだと思ってます。

終わり

tsgen で、既存の関数をベースにしてさまざまな引数の型に対して同じような処理を行える関数を生成できることを紹介しました。Go の静的解析ネタ、いろいろあるのでどこかで書くか話すかできればよいなと思ってます。

みなさま 2015 年もよろしくお願いいたします。

The Way to Go: A Thorough Introduction to the Go Programming Language (English Edition)

The Way to Go: A Thorough Introduction to the Go Programming Language (English Edition)

【想定はてブ数つき】2014 年のボツネタを一挙公開!!!

飽きっぽい性格がそうさせるのか、何かこれをやるぞ! と決めたことをやっている間に他のことを思いついてとりあえずメモっている間にどんどん積み上がっていって、果たせないままに今年も大晦日が近づいてきました。残された人生の時間と話し合って、自分が手を出すのはやめとこうと思ったアイデアをここに記しておきます。ブログに書くときの記事タイトルと想定はてブ数を併記しますので、参考にしてください。

「Perl プログラムを変数名で静的解析する」67 users

YAPC::Asia 2013 で Perl の静的解析の話が盛り上がっていて考えた。Perl のような言語に静的解析のために型アノーテーションをつけるのは煩雑で間違いを起こしやすいと考えられるけれど、静的型のない言語は名前付けが相対的に重要であることを利用して、変数名を型アノーテーション代わりに使用するというもの。例をあげると

sub get_req {
    ...
    return $req;
}

my $res = get_req(); # return $req された変数を <del>$req</del>$res で受けている!

というような齟齬を静的解析によって検出できないか、というもの。

  • やらなかった理由: Perl 最近書いてなかった。
  • ブクマ数の根拠: Perl を題材にしたのがよくなかった。Ruby か JavaScript ならもうちょっとブックマークがついたはずである。

「ソビエトロシアではブラウザがあなたをオートメーションする! または humandriver で自動テストを手動化する」44 users

ブラウザ拡張。ブラウザオートメーションの API である WebDriver の実装を人間が行う。WebDriver API では E2E テストなどで、クライアントが送る「このセレクタで得られる要素をクリックして」みたいな要求を実際にブラウザを(プログラム的に)操作することで達成する。この「プログラム的に」という部分を人間が実際に行うことで機械に使われてる感じがして面白い……だけでなく、WebDriver を使うスクリプトの検証に利用できる。

動作イメージはこんな感じ。

// 雰囲気コード
driver.findElement('button#submit').click();

たとえば E2E テストで上のように書かれていたとすると、起動中の Chrome にポップアップなどで「button#submit をクリックしてください; OK」と通知される。その通りに実行したのち OK を押すとクライアント側に操作が戻りテストが進む。

  • やらなかった理由: WebDriver API を尽くすのが大変そうだった。
  • ブクマ数の根拠: 記事タイトルがややウケだったが、実用性があまり認められず伸び悩んだ。

「機械にレシピを生成させ、食べる」229 users

機械にレシピを生成させてクックパッドに投稿し、つくれぽを教師として学習し、新たなレシピを考案する。飲んでる最中のアイデアだったけど同僚が即座にこういう文献があるね、と出してきたのが面白かった。

  • やらなかった理由: 調理中の画像を用意するのが面倒そうだった。怒られそう。
  • ブクマ数の根拠: 誰もが知っているウェブサービスと、料理という馴染み深い題材による裾野の広さでエンジニア以外にも注目された。初期のいかにもまずそうなレシピにつくれぽがつかなかったため自分たちで作って食べた、というエピソードもうけた。

「Go でジェネリクス的なことを実現する(import を利用して)」89 users

go get は Git リポジトリなりからコードを取得するので、Git サーバを自作すれば動的にコード生成することができる。そこで import 時に適当なパラメータを与えることで書き換えられたコードをダウンロードさせることができないか……と考えた。

  • やらなかった理由: Under Construction | Random とかぶった……。
  • ブクマ数の根拠: なかなか面白いアイデアだったけど、実用的にはあまり広がりがなかった。

「hubot-ramen で定時退社を促進する」51 users

hubot を使い出したので考えたやつ。Tumblr の ramen タグ からラーメン画像を探しだして、チャットに投稿する。定時とともにラーメン画像を投稿することで仕事のやる気をなくし、結果的に定時退社を促進するというソリューションの提案。

  • やらなかった理由: やりゃあ一瞬だが別にそんなにラーメン好きではないのだった。
  • ブクマ数の根拠: お前らラーメンとか好きなんだろ? という態度が透けて見え、かえって反感を誘ってしまった。

以上、総計 500 ブクマ(想定)ちかいエントリたちの紹介でした。いかがでしたか? もし気に入って実現してみた人は僕と飲みましょう! その際にはおごっていただけると幸いです。

またこのエントリを書く際にいくつかのアイデアをサルベージし、やる気を復活させることができました。みなさんもこの年末、過去のアイデアを棚卸ししてみてはいかがでしょうか?

http://www.amazon.co.jp/gp/registry/wishlist/3OFWD7Q729FDR/

ウェブ開発チームのマネジメント:『モチベーション3.0』を読んだ

モチベーション3.0 持続する「やる気!」をいかに引き出すか

モチベーション3.0 持続する「やる気!」をいかに引き出すか

年末なのでウィッシュリストを棚卸しして、『モチベーション3.0』を読んだ。今は開発チームのマネージャ的な立ち位置で働いているので、メンバーに如何に気持よく働いてもらえるか、というのが最近気になっている。

前提

人間をつき動かすモチベーションには三種類ある。

  • モチベーション 1.0: 生存のため、必要にかられて行動を起こす。動物的なもの。
  • モチベーション 2.0: 「これをすればかくかくの報酬がある」「これをしなければかくかくの処罰がある」という予測に基づいて行動を起こす。アメとムチによりコントロールされる。
  • モチベーション 3.0: 自らの内的な価値観に基づいて行動を起こす。

著者はこの本の中で、これまでの社会活動、とくに企業が労働者をマネジメントする仕組みはモチベーション 2.0に基いて進められてきたが、最近の世界的な情勢や研究を鑑みると、これが期待どおりに働かなかったり、逆効果であったりすることが分かってきたのだという。そこでモチベーション 2.0をモチベーション 3.0にアップグレードすべきだ、という話。 ちなみに 2.0 とか 3.0 というのはモチベーションに関する考え方を OS になぞらえていて、OS みたいな抽象的なものが喩えとして使われるのか、となんだか面白かった。

この主張のひとつの証左として、「幼稚園の迎えに遅刻する親に罰金を課すようにしたところ、かえって遅刻が増えてしまった」という面白いエピソードがある(そういえば NHK でもこの話やってた、と思いながら読んだ)。また、内発的動機づけによる行為に対して外発的な動機をあたえるとモチベーションが低下するという話は、この本では名前が出ていなかったけれど「アンダーマイニング効果」と呼ばれる現象らしい。

内発的な動機のためになにができるか

まあ上に書いたことは知ってた、ような話で、いわゆるマネージャ的な肩書を持っている自分としては、じゃあ内発的な動機づけとそこから来る行動のためになにができるか、というのが気になるところだ。筆者によるとこれには三つの柱がある:

  • 自律性
  • マスタリー(熟達)
  • 目的

自律性は自己決定。「何をするのか」「いつするのか」「どのようにするのか」「誰とするのか」を自ら考え、決定し、実行できるようにすることが、個々人のパフォーマンスを高めることにつながる。

マスタリー(熟達)は何か価値あることに上達したいという欲求。これは苦痛でもあり終わりない戦いでもあるが、これは自己充足的で、それ自体報酬となるプロセスでもある。

正直このふたつはインターネットでオープンソース活動なり個人活動をしている開発者(エンジニアやデザイナ)なら実感としてすでに知っているようなことだろう。現在の職場もそういった背景を持っている人びとが多く、この本で時代遅れとされているような、マネージャが部下の時間を時間やタスクを監視し、完全に管理しようとするような働きかたとは程遠い(と思う)。

そこでおそらく最も重要なのが三番目の目的。これは外的な報酬や処罰とは次元を異にする、社会的価値だとか人生の意味だとかいった、いわば高邁な目標だ。そして個人活動では達成できないことを、会社やチームで働くことで達成する意義はここにこそあらわれるはずだ。お給金のため……ももろんなのだけれど、それだけじゃない チームを率い、メンバーの生産性を最大化する責務があるマネージャは特にここに重心をおき、組織とチームの存在理由を十分に浸透させる仕事をする必要があるだろう。そうすれば自律したメンバーのアウトプットと会社の望む方向とのズレも最小化できるはずだ。

どうしてもやらなければならない仕事は

それでも組織が生存するために、つまらないルーチンワークをお願いしなければならないこともある。申し訳ない話だけれどそれでもやっぱりある。そういうときはどうすべきか? という話もこの本には載っていて、こういうことだった:

  • その仕事が必要であることを論理的に示し、
  • その仕事が退屈であることを認め、
  • その進め方にある程度裁量を与える。

また創造的な仕事でも環境が許さず自律・マスタリー・目的を構築できない場合にも、以下のように気をつけるとよい:

  • 仕事が終わったあとに、「思いがけない報酬」を与える。
  • 具体的な報酬よりも、賞賛やフィードバックを。

その他面白かった話

  • アトラシアンのフェデックス・デーの話がおもしろかった。24時間でなにか作って出す(デリバーする)、というもの。今は ShipIt Day と呼ばれてるのだろうか。
  • 偉大な仕事をした人物は、一文で表せるもの。自分を一文で表現すると何か? と問いかけてみる。
  • (子供に)自分で目標を立て、自分で成績表をつけさせてみると、内発的動機づけにつながる。

モチベーション3.0 持続する「やる気!」をいかに引き出すか

モチベーション3.0 持続する「やる気!」をいかに引き出すか

はてなで一緒に働きませんか?