https://golang.org/src/sort/sort.go
文字列整形のように、ソートはよく使われる。 最小のクイックソートは15行でかけるとはいえ、強固な実装はもっと長くなるし、欲しい種類のコードではなかった場合、新しく書くことになる。
sort.Sortは連続なデータ型でなくてもいい。
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)
とかけるようになっている。
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
}
playlistをテーブルとして表示する。
text/tabwrite
packageを使う。
*tabwriter.Writer
はio.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))
のように使う
逆順にソートするときはさらに新しい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
- req.URL.Pathの値をswitch-case。
- 複数のハンドルを登録する(後述)
req.URL.Query().Get("item")
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にテキストを書き込む前に呼び出しておく必要がある。
(http.ResponseWriter
はio.Writer
にhttp response header用のメソッドが追加されている)
utilty関数
http.Error(w, msg, http.StatusNotFound)
現実の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
をみると、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))
web serverは新しいgoroutineの中で各handlerを呼び出す。 なので、handlerは同じハンドラへの他のリクエストも含め、他のgorutineがアクセスしている変数にアクセスするときには、lockするなどの予防策をとる必要があります。
#7.8 The error Interface
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
Varがexportedの理由は後述。
変数を含む式を評価するために、変数名と値のマップであるenvironmentが必要になる。
type Env map[Var]float64
Eval.go
与えられたenvironmentの評価値を返すEval methodを定義するために、それれぞれの種類の式が必要です。
各式にはEvalメソッドが必要なので、ExprインタフェースにEvalメソッドを追加します。
このpackageはExpr
,Env
,Var
だけをエクスポートします。
クライアントは他の型にアクセスすることなく、評価することができる。
func (v Var) Eval(env Env) float64 {
return env[v] //存在しない場合は0
}
func (l literal) Eval(_ Env) float64 {
return float64(l) //型変換するだけ
}
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))
}
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のテストを書いてみましょう。
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が具象型なら、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型のとき、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)
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を隠す。