Skip to content

Instantly share code, notes, and snippets.

@tokubass
Last active November 17, 2016 09:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tokubass/fecd84603a6dd08faf7d6f03518aec96 to your computer and use it in GitHub Desktop.
Save tokubass/fecd84603a6dd08faf7d6f03518aec96 to your computer and use it in GitHub Desktop.

7.6 Sorting With sort.Interface

https://golang.org/src/sort/sort.go

文字列整形のように、ソートはよく使われる。 最小のクイックソートは15行でかけるとはいえ、強固な実装はもっと長くなるし、欲しい種類のコードではなかった場合、新しく書くことになる。

sortパッケージ

sort.Sortは連続なデータ型でなくてもいい。

sort.Interface

in-place sortに必要なのは、

  • 列の長さ
  • 2要素の比較方法
  • 2要素のswap方法

の3つ。これらをsort.Interfaceに記述する。

package sort

type Interface interface {
  Len() int
  Less(i, j int) bool
  Swap (i,j int)
}
type StringSlice []string

func (p StringSlice) Len() int{ return len(p) }
func (p StringSlice) Less(i,j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i,j int) { p[i], p[j] = p[j],p[i] }

stringのスライスもソートできる。namesという変数だったとして、、、 sort.Sort(StringSlice(names))

この変換はnamesと同じcapacity, length, underlying arrayをもつslice値を生み出すが、sortのための3つのメソッドをもつ型を伴う。

上記のような文字列スライスのソートのために sort.Strings(name)とかけるようになっている。

music playlistをソートして、tableとして表示する実行例

https://github.com/adonovan/gopl.io/blob/master/ch7/sorting/main.go

以下のコードの、tracksはポインタで間接的にTrackにアクセスしている。 ポインタにしなくても正常にsortは完了するが、sortはswapが発生するのでポインタにしておいたほうが速い。各要素が8byte以上であれば、より速く動作します。

type Track struct {
    Title  string
    Artist string
    Album  string
    Year   int
    Length time.Duration
}

var tracks = []*Track{
	{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
	{"Go", "Moby", "Moby", 1992, length("3m37s")},
	{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
	{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
}

func length(s string) time.Duration {
	d, err := time.ParseDuration(s)
	if err != nil {
		panic(s)
	}
	return d
}

printTracks関数

playlistをテーブルとして表示する。 text/tabwrite packageを使う。 *tabwriter.Writerio.Writerを満たしている。 Flushメソッドはテーブル全体をフォーマット(カラムの幅を計算)し、os.Stdoutに書き出す。

func printTracks(tracks []*Track) {
	const format = "%v\t%v\t%v\t%v\t%v\t\n"
	tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
	fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
	fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
	for _, t := range tracks {
		fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
	}
	tw.Flush() // calculate column widths and print table
}

Artist fieldでソートしたい場合、StringSliceのときのように、新しいslice型を定義する。(Len,Less,Swapメソッドをもっている)

type byArtist []*Track

func (x byArtist) Len() int           { return len(x) }
func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
func (x byArtist) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

sort.Sort(byArtist(tracks)) のように使う

reverse

逆順にソートするときはさらに新しいsliceを定義する必要はなく、 sort.Sort(sort.Reverse(byArtist(tracks)))と書ける。

sort.Reverse関数はインポートのアイデアに6.3節のcompositionを使っているので、もっとしっかり見る必要がある。 sort パッケージはunexportedなreverse構造体をもっており、それはsort.Interfaceを組み込んでいる。reverseのLessメソッドは、中でsort.Interface値のLessを呼んでいる。

type reverse struct {
// This embedded Interface permits Reverse to use the methods of another Interface implementation.
		Interface
}

// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
	return r.Interface.Less(j, i)
}
	
// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
	return &reverse{data}
}

LenとSwapメソッドは暗黙的にsort.Interface値からreverseへ提供されている。これは組みこみfiledだから。 Reverse関数は、sort.Interface値を含んだreverse型のインスタンスが戻り値になる。

//*sort.reverse
fmt.Printf("%T\n", sort.Reverse(byArtist(tracks)))

カスタムソート

ソートしたいカラムが増えるたびに、新しい型を定義する必要があるので、 それを回避する例を提示する。

customSortは関数とスライスを結合している。 ちなみに、sort.Interfaceの具象型は常にsliceではない。今回のcustomSortは構造体。

type customSort struct {
	t    []*Track
	less func(x, y *Track) bool
}

func (x customSort) Len() int           { return len(x.t) }
func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) }
func (x customSort) Swap(i, j int)      { x.t[i], x.t[j] = x.t[j], x.t[i] }

Title,Yearの順にソート sortは無名関数をコールする。

sort.Sort(customSort{tracks, func(x, y *Track) bool {
	if x.Title != y.Title {
		return x.Title < y.Title
	}
	if x.Year != y.Year {
		return x.Year < y.Year
	}
	if x.Length != y.Length {
		return x.Length < y.Length
	}
	return false
}})

[]int,[]string,[]float64については、関数が提供されている。

   289	// Convenience wrappers for common cases
   290	
   291	// Ints sorts a slice of ints in increasing order.
   292	func Ints(a []int) { Sort(IntSlice(a)) }
   293	
   294	// Float64s sorts a slice of float64s in increasing order.
   295	func Float64s(a []float64) { Sort(Float64Slice(a)) }
   296	
   297	// Strings sorts a slice of strings in increasing order.
   298	func Strings(a []string) { Sort(StringSlice(a)) }
   299	
   300	// IntsAreSorted tests whether a slice of ints is sorted in increasing order.
   301	func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) }
   302	
   303	// Float64sAreSorted tests whether a slice of float64s is sorted in increasing order.
   304	func Float64sAreSorted(a []float64) bool { return IsSorted(Float64Slice(a)) }
   305	
   306	// StringsAreSorted tests whether a slice of strings is sorted in increasing order.
   307	func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) }

参考 http://qiita.com/Jxck_/items/fb829b818aac5b5f54f7

#7.7 The http.Handler Interface

以下はdatabase型がServerHTTPメソッドをもっていて、 これはhttp.Handlerのインタフェースを満たす。

func main() {
	db := database{"shoes": 50, "socks": 5}
	log.Fatal(http.ListenAndServe("localhost:8000", db))
}

type dollars float32

func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }

type database map[string]dollars

func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	for item, price := range db {
		fmt.Fprintf(w, "%s: %s\n", item, price)
	}
}

https://github.com/adonovan/gopl.io/blob/master/ch7/http1/main.go

複数のURLで処理をわける

  • req.URL.Pathの値をswitch-case。
  • 複数のハンドルを登録する(後述)

querystring

req.URL.Query().Get("item")

item=foo&item=barのとき

https://golang.org/pkg/net/url/#URL.Query https://golang.org/pkg/net/url/#Values req.URL.Query()はValues型が戻り値。

mapにはkey名itemでfooとbarの入った配列が格納されるが、 Get("item")では最初の要素だけが戻り値になる。 foo,barどっちが最初? ->決まってない。実装に依存すべきでない

https://golang.org/pkg/net/http/#Request

w.WriteHeader(http.StatusNotFound)

これはwにテキストを書き込む前に呼び出しておく必要がある。 (http.ResponseWriterio.Writerにhttp response header用のメソッドが追加されている)

http.Error関数

utilty関数 http.Error(w, msg, http.StatusNotFound)

ServerMux

現実のweb applicationでは、

  • ロジック定義はそれぞれの関数に分離できたほうがいい。
  • 関連するパスは似たロジックが必要になる

ServerMuxはhttp.Handlerの集合を一つのhttp.Handlerに集約する。

Rails,Djangoのようなフレームワークはないが、Goの標準ライブラリは十分に使える。プロジェクトの初期はフレームワークは便利だが、長期にわたってメンテナンスするには、複雑性が増して困難。

https://github.com/adonovan/gopl.io/blob/master/ch7/http3/main.go

dbはhandlerのような振る舞いをするが、http.Handlerインタフェースを満たしていないので、http.HandlerFuncで型変換してから、mux.Handleに登録

(db.listはmethod value. 値に格納されたメソッド。)

mux := http.NewServeMux()
mux.Handle("/list", http.HandlerFunc(db.list))
mux.Handle("/price", http.HandlerFunc(db.price))

HandlerFunc

https://github.com/golang/go/blob/9f9d83404f938a0dfb98d3f4a4d420261606069a/src/net/http/server.go#L1928

HandlerFuncをみると、Goのインタフェース機構が独特な特徴をもっていることがわかる。 HandlerFuncは関数型で、メソッドをもち、http.Handlerインタフェースを満たす。 ServeHTTPメソッドの振る舞いは規定関数を呼び出すこと。 ゆえにHandlerFuncは関数値のアダプターで、interfaceを満たす。関数とインタフェースの唯一のメソッドは同じシグネチャーをもっている。 実際には、このトリックはdatabaseのようにひとつの型で、http.Handlerインタフェースを異なる方法で満たすことができる。 (list,priceメソッドなど。)

ショートカットできる

mux.HandleFunc("/list", db.list)
mux.HandleFunc("/price", db.price)

net/httpはglobalなServeMuxインスタンスを提供していて、DefaultServeMuxと呼ばれている。またhttp.Handle,http.handleFuncとよばれるpackage-level関数も提供する。 これらを使うとさらにシンプルにかける

http.HandleFunc("/list", db.list)
http.HandleFunc("/price", db.price)
log.Fatal(http.ListenAndServe("localhost:8000", nil))

important

web serverは新しいgoroutineの中で各handlerを呼び出す。 なので、handlerは同じハンドラへの他のリクエストも含め、他のgorutineがアクセスしている変数にアクセスするときには、lockするなどの予防策をとる必要があります。

#7.8 The error Interface

error型

error型は、エラメッセージを返す一つのメソッドをともなうインタフェース

type error interface {
 Error() string
}

errors.Newを呼ぶと簡単にerrorを生成できる。 errors packageの実装は以下だけしかない。

package errors

func New(text string) error {
	return &errorString{text}
}

type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}

stringではなくerrorString構造体なのは不注意なUpdateを防ぐため。 errorStringのポインタを使っているのはNewのたびに異なるErrorインスタンスを生成するため。

ショートカット。

func Errorf(format string, a ...interface{}) error {
	return errors.New(Sprintf(format, a...))
}

errorStringはもっともシンプルな型で、他にもsyscallパッケージのErrno型がある。

var err error = syscall.Errno(2)型は syscall.Errnoで、interface値は2になる。

fmt.Println( err.Error()) // "no such file or directory"
fmt.Println( err) // "no such file or directory" ??

#7.9 Example: Expression Evaluator

数値計算の評価機を作成

仕様

  • 浮動小数点数リテラル
  • 二項演算子(四則演算)
  • 単項演算子 -x,+x
  • 変数(x, pi)
  • pow,sin,sqrt関数
  • 括弧と演算子の優先順位
  • すべての値はfloat64

Ast.go

Varがexportedの理由は後述。

変数を含む式を評価するために、変数名と値のマップであるenvironmentが必要になる。

type Env map[Var]float64 Eval.go

与えられたenvironmentの評価値を返すEval methodを定義するために、それれぞれの種類の式が必要です。 各式にはEvalメソッドが必要なので、ExprインタフェースにEvalメソッドを追加します。 このpackageはExpr,Env,Varだけをエクスポートします。 クライアントは他の型にアクセスすることなく、評価することができる。

Var,literal の具体的なEval実装

func (v Var) Eval(env Env) float64 {
	return env[v] //存在しない場合は0
}

func (l literal) Eval(_ Env) float64 {
	return float64(l) //型変換するだけ
}

unary, binary

Evalメソッドは再帰的に評価する。 ゼロと無限による除算エラーは扱わない。それらの結果は有限ではないので。

func (u unary) Eval(env Env) float64 {
	switch u.op {
	case '+':
		return +u.x.Eval(env)
	case '-':
		return -u.x.Eval(env)
	}
	panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
}

func (b binary) Eval(env Env) float64 {
	switch b.op {
	case '+':
		return b.x.Eval(env) + b.y.Eval(env)
	case '-':
		return b.x.Eval(env) - b.y.Eval(env)
	case '*':
		return b.x.Eval(env) * b.y.Eval(env)
	case '/':
		return b.x.Eval(env) / b.y.Eval(env)
	}
	panic(fmt.Sprintf("unsupported binary operator: %q", b.op))
}

call

pow,sin,sqrtは対応するmathパッケージの関数を呼び出します。

func (c call) Eval(env Env) float64 {
	switch c.fn {
	case "pow":
		return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))
	case "sin":
		return math.Sin(c.args[0].Eval(env))
	case "sqrt":
		return math.Sqrt(c.args[0].Eval(env))
	}
	panic(fmt.Sprintf("unsupported function call: %s", c.fn))
}

存在しない関数や、間違った引数での呼び出しや、サポートしていない演算子を評価した場合は、Evalはpanicを引き起こします。 その他の、environment内にVarが存在しないなどのエラーは、たんに間違った結果が返ってきます。 これらのエラーは評価前にExprを調べることで見つけられる。 これはCheckメソッドの仕事。しかし、ここではまずEvalのテストを書いてみましょう。

test

https://github.com/adonovan/gopl.io/blob/master/ch7/eval/eval_test.go

データドリブンのテスト。 式をパースし、評価し、結果をプリント。Parse関数については割愛する。 go test -v vをつけないと、成功したテストは表示されない。

##check literalとVarはfailできない(?)ので、nilを返す。 unaryとbinaryは最初にoperatorをチェックし、次に再帰的にオペランドをチェックします。 https://github.com/adonovan/gopl.io/blob/master/ch7/eval/check.go

Parse関数は、syntax error Check関数は、semantic error を報告する。

#7.10 Type Asertion

type assertionはinterface valueに適用するoperation. x.(T)はasserted型と呼ばれる。

  • xはinterface型の式。
  • Tは型

Tが具象型

Tが具象型なら、type assertionはxの動的型がTと同一かチェックします。 もしチェックが成功したら、type assertionの結果は、xの動的型になります。 (もちろんこの型はT)

つまり、具象型へのtype assertionは、そのオペランドから具象値を取り出します。もしチェックに失敗すると、panicになります。

var w io.Writer
w = os.Stdout
f := w.(*os.File) // success f == os.Stdout
c := w.(*bytes.Buffer) //panic

Tがinterface型

Tがinterface型のとき、type assertionはxの動的型がTを満たすかをチェックします。 チェックが成功した時、動的値は取り出されません。 結果はまだ同じ型と値をもつinterface値です。 しかし、結果はinterface型を持ちます。 つまり、interface型へのtype assertionは式の型を変更します。 異なる(通常よりは大きい)メソッドのセットをアクセス可能にしますが、interface値の中に動的な型と値のコンポーネントを保持します。

w,rwはともにos.Stdoutを保持しており、両方とも*os.Fileの動的型を持っている。 しかしwはio.Writerなので、Writeメソッドだけ露出しており、rwはReadメソッドも露出している。

var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter)

オペランドがnil

assertされたどのような型であっても、もしオペランドがnil interface値ならばそのassertionは失敗します。 interface型を緩和するtype assertionは、代入と同様な振る舞いのため、必要になることはめったにありません。(nillの場合を除く)

w = rw // io.ReadWriterはio.Writerを満たすので代入できる
w = rw.(io.Writer) //io.Writerへの変換。代入と同じだが、rwがnillのときに失敗する。

interface値の動的型に確信がもてないことがよくあり、特定の型であるかどうかテストしたいと思う。 もしtype assertionが2つの結果を期待する代入にあらわれたら、次のような宣言のとき、operationはfailしてもpanicにならない。 しかし、下記のokにbooleanが返ってきて、成功かどうかわかる。

var w io.Writer = os.Stdout
f, ok := w.(*os.File) // ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // !ok, b == nil

2つめの結果を代入する変数名は慣例的にokと名付けられる。 もし失敗した場合、1つ目の結果は、asserted typeのzero valueになる。上記の例だと、*bytes.Bufferのzero valueはnil.

得られたokはすぐに判定に使いたいので、次のように書くと便利。

if f , ok := w.(*.os.File); ok {
    // ..use f ...
}

また上記のようにwの変換後にfという新しい名前をつけなくても、同じ名前でもいい。

if w , ok := w.(*.os.File); ok {
   /// ..use w ...
}

ifの中のwは、type assertionのオペランドであるwを隠す。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment