Merge branch 'reflect_lecture' into 'master'
Reflect lecture See merge request slon/shad-go-private!29
This commit is contained in:
commit
8ddace7b75
9 changed files with 924 additions and 0 deletions
92
lectures/08-reflect/display/display.go
Normal file
92
lectures/08-reflect/display/display.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
// See page 333.
|
||||||
|
|
||||||
|
// Package display provides a means to display structured data.
|
||||||
|
package display
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
//!+Display
|
||||||
|
|
||||||
|
func Display(name string, x interface{}) {
|
||||||
|
fmt.Printf("Display %s (%T):\n", name, x)
|
||||||
|
display(name, reflect.ValueOf(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OMIT
|
||||||
|
|
||||||
|
// formatAtom formats a value without inspecting its internal structure.
|
||||||
|
// It is a copy of the the function in gopl.io/ch11/format.
|
||||||
|
func formatAtom(v reflect.Value) string {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return "invalid"
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16,
|
||||||
|
reflect.Int32, reflect.Int64:
|
||||||
|
return strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||||
|
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return strconv.FormatUint(v.Uint(), 10)
|
||||||
|
// ...floating-point and complex cases omitted for brevity...
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
case reflect.String:
|
||||||
|
return strconv.Quote(v.String())
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Ptr,
|
||||||
|
reflect.Slice, reflect.Map:
|
||||||
|
return v.Type().String() + " 0x" +
|
||||||
|
strconv.FormatUint(uint64(v.Pointer()), 16)
|
||||||
|
default: // reflect.Array, reflect.Struct, reflect.Interface
|
||||||
|
return v.Type().String() + " value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//!+display
|
||||||
|
func display(path string, v reflect.Value) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
fmt.Printf("%s = invalid\n", path)
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
|
||||||
|
display(fieldPath, v.Field(i))
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
for _, key := range v.MapKeys() {
|
||||||
|
display(fmt.Sprintf("%s[%s]", path,
|
||||||
|
formatAtom(key)), v.MapIndex(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OMIT
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
fmt.Printf("%s = nil\n", path)
|
||||||
|
} else {
|
||||||
|
display(fmt.Sprintf("(*%s)", path), v.Elem())
|
||||||
|
}
|
||||||
|
case reflect.Interface:
|
||||||
|
if v.IsNil() {
|
||||||
|
fmt.Printf("%s = nil\n", path)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
|
||||||
|
display(path+".value", v.Elem())
|
||||||
|
}
|
||||||
|
default: // basic types, channels, funcs
|
||||||
|
fmt.Printf("%s = %s\n", path, formatAtom(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OMIT
|
259
lectures/08-reflect/display/display_test.go
Normal file
259
lectures/08-reflect/display/display_test.go
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
package display
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopl.io/ch7/eval"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: we can't use !+..!- comments to excerpt these tests
|
||||||
|
// into the book because it defeats the Example mechanism,
|
||||||
|
// which requires the // Output comment to be at the end
|
||||||
|
// of the function.
|
||||||
|
|
||||||
|
func Example_expr() {
|
||||||
|
e, _ := eval.Parse("sqrt(A / pi)")
|
||||||
|
Display("e", e)
|
||||||
|
// Output:
|
||||||
|
// Display e (eval.call):
|
||||||
|
// e.fn = "sqrt"
|
||||||
|
// e.args[0].type = eval.binary
|
||||||
|
// e.args[0].value.op = 47
|
||||||
|
// e.args[0].value.x.type = eval.Var
|
||||||
|
// e.args[0].value.x.value = "A"
|
||||||
|
// e.args[0].value.y.type = eval.Var
|
||||||
|
// e.args[0].value.y.value = "pi"
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_slice() {
|
||||||
|
Display("slice", []*int{new(int), nil})
|
||||||
|
// Output:
|
||||||
|
// Display slice ([]*int):
|
||||||
|
// (*slice[0]) = 0
|
||||||
|
// slice[1] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_nilInterface() {
|
||||||
|
var w io.Writer
|
||||||
|
Display("w", w)
|
||||||
|
// Output:
|
||||||
|
// Display w (<nil>):
|
||||||
|
// w = invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_ptrToInterface() {
|
||||||
|
var w io.Writer
|
||||||
|
Display("&w", &w)
|
||||||
|
// Output:
|
||||||
|
// Display &w (*io.Writer):
|
||||||
|
// (*&w) = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_struct() {
|
||||||
|
Display("x", struct{ x interface{} }{3})
|
||||||
|
// Output:
|
||||||
|
// Display x (struct { x interface {} }):
|
||||||
|
// x.x.type = int
|
||||||
|
// x.x.value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_interface() {
|
||||||
|
var i interface{} = 3
|
||||||
|
Display("i", i)
|
||||||
|
// Output:
|
||||||
|
// Display i (int):
|
||||||
|
// i = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_ptrToInterface2() {
|
||||||
|
var i interface{} = 3
|
||||||
|
Display("&i", &i)
|
||||||
|
// Output:
|
||||||
|
// Display &i (*interface {}):
|
||||||
|
// (*&i).type = int
|
||||||
|
// (*&i).value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_array() {
|
||||||
|
Display("x", [1]interface{}{3})
|
||||||
|
// Output:
|
||||||
|
// Display x ([1]interface {}):
|
||||||
|
// x[0].type = int
|
||||||
|
// x[0].value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_movie() {
|
||||||
|
//!+movie
|
||||||
|
type Movie struct {
|
||||||
|
Title, Subtitle string
|
||||||
|
Year int
|
||||||
|
Color bool
|
||||||
|
Actor map[string]string
|
||||||
|
Oscars []string
|
||||||
|
Sequel *string
|
||||||
|
}
|
||||||
|
//!-movie
|
||||||
|
//!+strangelove
|
||||||
|
strangelove := Movie{
|
||||||
|
Title: "Dr. Strangelove",
|
||||||
|
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
|
||||||
|
Year: 1964,
|
||||||
|
Color: false,
|
||||||
|
Actor: map[string]string{
|
||||||
|
"Dr. Strangelove": "Peter Sellers",
|
||||||
|
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
|
||||||
|
"Pres. Merkin Muffley": "Peter Sellers",
|
||||||
|
"Gen. Buck Turgidson": "George C. Scott",
|
||||||
|
"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
|
||||||
|
`Maj. T.J. "King" Kong`: "Slim Pickens",
|
||||||
|
},
|
||||||
|
|
||||||
|
Oscars: []string{
|
||||||
|
"Best Actor (Nomin.)",
|
||||||
|
"Best Adapted Screenplay (Nomin.)",
|
||||||
|
"Best Director (Nomin.)",
|
||||||
|
"Best Picture (Nomin.)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
//!-strangelove
|
||||||
|
Display("strangelove", strangelove)
|
||||||
|
|
||||||
|
// We don't use an Output: comment since displaying
|
||||||
|
// a map is nondeterministic.
|
||||||
|
/*
|
||||||
|
//!+output
|
||||||
|
Display strangelove (display.Movie):
|
||||||
|
strangelove.Title = "Dr. Strangelove"
|
||||||
|
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
|
||||||
|
strangelove.Year = 1964
|
||||||
|
strangelove.Color = false
|
||||||
|
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
|
||||||
|
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
|
||||||
|
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
|
||||||
|
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
|
||||||
|
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
|
||||||
|
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
|
||||||
|
strangelove.Oscars[0] = "Best Actor (Nomin.)"
|
||||||
|
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
|
||||||
|
strangelove.Oscars[2] = "Best Director (Nomin.)"
|
||||||
|
strangelove.Oscars[3] = "Best Picture (Nomin.)"
|
||||||
|
strangelove.Sequel = nil
|
||||||
|
//!-output
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test ensures that the program terminates without crashing.
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
// Some other values (YMMV)
|
||||||
|
Display("os.Stderr", os.Stderr)
|
||||||
|
// Output:
|
||||||
|
// Display os.Stderr (*os.File):
|
||||||
|
// (*(*os.Stderr).file).fd = 2
|
||||||
|
// (*(*os.Stderr).file).name = "/dev/stderr"
|
||||||
|
// (*(*os.Stderr).file).nepipe = 0
|
||||||
|
|
||||||
|
var w io.Writer = os.Stderr
|
||||||
|
Display("&w", &w)
|
||||||
|
// Output:
|
||||||
|
// Display &w (*io.Writer):
|
||||||
|
// (*&w).type = *os.File
|
||||||
|
// (*(*(*&w).value).file).fd = 2
|
||||||
|
// (*(*(*&w).value).file).name = "/dev/stderr"
|
||||||
|
// (*(*(*&w).value).file).nepipe = 0
|
||||||
|
|
||||||
|
var locker sync.Locker = new(sync.Mutex)
|
||||||
|
Display("(&locker)", &locker)
|
||||||
|
// Output:
|
||||||
|
// Display (&locker) (*sync.Locker):
|
||||||
|
// (*(&locker)).type = *sync.Mutex
|
||||||
|
// (*(*(&locker)).value).state = 0
|
||||||
|
// (*(*(&locker)).value).sema = 0
|
||||||
|
|
||||||
|
Display("locker", locker)
|
||||||
|
// Output:
|
||||||
|
// Display locker (*sync.Mutex):
|
||||||
|
// (*locker).state = 0
|
||||||
|
// (*locker).sema = 0
|
||||||
|
// (*(&locker)) = nil
|
||||||
|
|
||||||
|
locker = nil
|
||||||
|
Display("(&locker)", &locker)
|
||||||
|
// Output:
|
||||||
|
// Display (&locker) (*sync.Locker):
|
||||||
|
// (*(&locker)) = nil
|
||||||
|
|
||||||
|
ips, _ := net.LookupHost("golang.org")
|
||||||
|
Display("ips", ips)
|
||||||
|
// Output:
|
||||||
|
// Display ips ([]string):
|
||||||
|
// ips[0] = "173.194.68.141"
|
||||||
|
// ips[1] = "2607:f8b0:400d:c06::8d"
|
||||||
|
|
||||||
|
// Even metarecursion! (YMMV)
|
||||||
|
Display("rV", reflect.ValueOf(os.Stderr))
|
||||||
|
// Output:
|
||||||
|
// Display rV (reflect.Value):
|
||||||
|
// (*rV.typ).size = 8
|
||||||
|
// (*rV.typ).ptrdata = 8
|
||||||
|
// (*rV.typ).hash = 871609668
|
||||||
|
// (*rV.typ)._ = 0
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// a pointer that points to itself
|
||||||
|
type P *P
|
||||||
|
var p P
|
||||||
|
p = &p
|
||||||
|
if false {
|
||||||
|
Display("p", p)
|
||||||
|
// Output:
|
||||||
|
// Display p (display.P):
|
||||||
|
// ...stuck, no output...
|
||||||
|
}
|
||||||
|
|
||||||
|
// a map that contains itself
|
||||||
|
type M map[string]M
|
||||||
|
m := make(M)
|
||||||
|
m[""] = m
|
||||||
|
if false {
|
||||||
|
Display("m", m)
|
||||||
|
// Output:
|
||||||
|
// Display m (display.M):
|
||||||
|
// ...stuck, no output...
|
||||||
|
}
|
||||||
|
|
||||||
|
// a slice that contains itself
|
||||||
|
type S []S
|
||||||
|
s := make(S, 1)
|
||||||
|
s[0] = s
|
||||||
|
if false {
|
||||||
|
Display("s", s)
|
||||||
|
// Output:
|
||||||
|
// Display s (display.S):
|
||||||
|
// ...stuck, no output...
|
||||||
|
}
|
||||||
|
|
||||||
|
// a linked list that eats its own tail
|
||||||
|
type Cycle struct {
|
||||||
|
Value int
|
||||||
|
Tail *Cycle
|
||||||
|
}
|
||||||
|
var c Cycle
|
||||||
|
c = Cycle{42, &c}
|
||||||
|
if false {
|
||||||
|
Display("c", c)
|
||||||
|
// Output:
|
||||||
|
// Display c (display.Cycle):
|
||||||
|
// c.Value = 42
|
||||||
|
// (*c.Tail).Value = 42
|
||||||
|
// (*(*c.Tail).Tail).Value = 42
|
||||||
|
// ...ad infinitum...
|
||||||
|
}
|
||||||
|
}
|
41
lectures/08-reflect/format/format.go
Normal file
41
lectures/08-reflect/format/format.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
// See page 332.
|
||||||
|
|
||||||
|
// Package format provides an Any function that can format any value.
|
||||||
|
//!+
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Any formats any value as a string.
|
||||||
|
func Any(value interface{}) string {
|
||||||
|
return formatAtom(reflect.ValueOf(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatAtom formats a value without inspecting its internal structure.
|
||||||
|
func formatAtom(v reflect.Value) string {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return "invalid"
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return strconv.FormatUint(v.Uint(), 10)
|
||||||
|
// ...floating-point and complex cases omitted for brevity...
|
||||||
|
case reflect.Bool:
|
||||||
|
return strconv.FormatBool(v.Bool())
|
||||||
|
case reflect.String:
|
||||||
|
return strconv.Quote(v.String())
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
|
||||||
|
return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
|
||||||
|
default: // reflect.Array, reflect.Struct, reflect.Interface
|
||||||
|
return v.Type().String() + " value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OMIT
|
24
lectures/08-reflect/format/format_test.go
Normal file
24
lectures/08-reflect/format/format_test.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
package format_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopl.io/ch12/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
// The pointer values are just examples, and may vary from run to run.
|
||||||
|
//!+time
|
||||||
|
var x int64 = 1
|
||||||
|
var d time.Duration = 1 * time.Nanosecond
|
||||||
|
fmt.Println(format.Any(x)) // "1"
|
||||||
|
fmt.Println(format.Any(d)) // "1"
|
||||||
|
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
|
||||||
|
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
|
||||||
|
//!-time
|
||||||
|
}
|
276
lectures/08-reflect/lecture.slide
Normal file
276
lectures/08-reflect/lecture.slide
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
reflect
|
||||||
|
Лекция 7
|
||||||
|
|
||||||
|
Короткий Фёдор
|
||||||
|
|
||||||
|
* Reflection
|
||||||
|
|
||||||
|
- Механизм для чтения и изменения значений, не зная их реальный тип момент компиляции.
|
||||||
|
- Пример использования `reflect`: `fmt`, `encoding/json`.
|
||||||
|
- Эти пакеты используют `reflect` в реализации, но *не*светят* reflect в интерфейсе.
|
||||||
|
|
||||||
|
* Why Reflection?
|
||||||
|
|
||||||
|
Иногда нам нужно API, которое работает со значениями, которые не
|
||||||
|
объединены общим интерфейсом.
|
||||||
|
|
||||||
|
func Sprint(x interface{}) string {
|
||||||
|
type stringer interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := x.(type) {
|
||||||
|
case stringer:
|
||||||
|
return x.String()
|
||||||
|
case string:
|
||||||
|
return x
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(x)
|
||||||
|
// ...similar cases for int16, uint32, and so on...
|
||||||
|
default:
|
||||||
|
// array, chan, func, map, pointer, slice, struct
|
||||||
|
return "???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* reflect.Type and reflect.Value
|
||||||
|
|
||||||
|
- Пакет `reflect` определяет два основных типа: `reflect.Type` и `reflect.Value`.
|
||||||
|
- Единственная реализация `reflect.Type` - _type_descriptor_.
|
||||||
|
|
||||||
|
func TypeOf(interface{}) Type
|
||||||
|
|
||||||
|
- Пример:
|
||||||
|
|
||||||
|
t := reflect.TypeOf(3) // a reflect.Type
|
||||||
|
fmt.Println(t.String()) // "int"
|
||||||
|
fmt.Println(t) // "int"
|
||||||
|
|
||||||
|
- `reflect.TypeOf` всегда возвращает конкретный тип.
|
||||||
|
|
||||||
|
var w io.Writer = os.Stdout
|
||||||
|
fmt.Println(reflect.TypeOf(w)) // "*os.File"
|
||||||
|
|
||||||
|
- `Sprintf` формат `%T` использует `TypeOf` внутри.
|
||||||
|
|
||||||
|
fmt.Printf("%T\n", 3) // "int"
|
||||||
|
|
||||||
|
* reflect.Type and reflect.Value
|
||||||
|
|
||||||
|
- `reflect.Value` хранит значение любого типа.
|
||||||
|
|
||||||
|
v := reflect.ValueOf(3)
|
||||||
|
fmt.Println(v) // "3"
|
||||||
|
fmt.Printf("%v\n", v) // "3"
|
||||||
|
fmt.Println(v.String()) // NOTE: "<int Value>"
|
||||||
|
|
||||||
|
- Метод `.Type()` возвращает тип значения, хранящегося внутри `Value`.
|
||||||
|
|
||||||
|
t := v.Type() // a reflect.Type
|
||||||
|
fmt.Println(t.String()) // "int"
|
||||||
|
|
||||||
|
- Метод `.Interface()` - обратная операция к `reflect.ValueOf`.
|
||||||
|
|
||||||
|
v := reflect.ValueOf(3) // a reflect.Value
|
||||||
|
x := v.Interface() // an interface{}
|
||||||
|
i := x.(int) // an int
|
||||||
|
fmt.Printf("%d\n", i) // "3"
|
||||||
|
|
||||||
|
* Разница между reflect.Value и interface{}
|
||||||
|
|
||||||
|
- Оба хранят любое значение.
|
||||||
|
- `interface{}` - хранит значение, но не даёт доступа к нему. Можно достать конкретный тип, через _type_assertion_.
|
||||||
|
- `reflect.Value` - предоставляет доступ к значению внутри, вне зависимости от типа этого значения.
|
||||||
|
|
||||||
|
* format.Any
|
||||||
|
|
||||||
|
.code format/format.go /func Any/,/OMIT/
|
||||||
|
|
||||||
|
* format.Any
|
||||||
|
|
||||||
|
var x int64 = 1
|
||||||
|
var d time.Duration = 1 * time.Nanosecond
|
||||||
|
fmt.Println(format.Any(x)) // "1"
|
||||||
|
fmt.Println(format.Any(d)) // "1"
|
||||||
|
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
|
||||||
|
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
|
||||||
|
|
||||||
|
* Debug Display
|
||||||
|
|
||||||
|
type Movie struct {
|
||||||
|
Title, Subtitle string
|
||||||
|
Year int
|
||||||
|
Color bool
|
||||||
|
Actor map[string]string
|
||||||
|
Oscars []string
|
||||||
|
Sequel *string
|
||||||
|
}
|
||||||
|
|
||||||
|
strangelove := Movie{
|
||||||
|
Title: "Dr. Strangelove",
|
||||||
|
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
|
||||||
|
Year: 1964,
|
||||||
|
Color: false,
|
||||||
|
Actor: map[string]string{
|
||||||
|
"Dr. Strangelove": "Peter Sellers",
|
||||||
|
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
|
||||||
|
"Pres. Merkin Muffley": "Peter Sellers",
|
||||||
|
"Gen. Buck Turgidson": "George C. Scott",
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
* Debug Display
|
||||||
|
|
||||||
|
Display("strangelove", strangelove)
|
||||||
|
|
||||||
|
Вывод:
|
||||||
|
|
||||||
|
Display strangelove (display.Movie):
|
||||||
|
strangelove.Title = "Dr. Strangelove"
|
||||||
|
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
|
||||||
|
strangelove.Year = 1964
|
||||||
|
strangelove.Color = false
|
||||||
|
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
|
||||||
|
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
|
||||||
|
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
|
||||||
|
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
|
||||||
|
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
|
||||||
|
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
|
||||||
|
strangelove.Oscars[0] = "Best Actor (Nomin.)"
|
||||||
|
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
|
||||||
|
strangelove.Oscars[2] = "Best Director (Nomin.)"
|
||||||
|
strangelove.Oscars[3] = "Best Picture (Nomin.)"
|
||||||
|
strangelove.Sequel = nil
|
||||||
|
|
||||||
|
* Debug Display
|
||||||
|
|
||||||
|
Display("os.Stderr", os.Stderr)
|
||||||
|
// Output:
|
||||||
|
// Display os.Stderr (*os.File):
|
||||||
|
// (*(*os.Stderr).file).fd = 2
|
||||||
|
// (*(*os.Stderr).file).name = "/dev/stderr"
|
||||||
|
// (*(*os.Stderr).file).nepipe = 0
|
||||||
|
|
||||||
|
* Debug Display
|
||||||
|
|
||||||
|
.code display/display.go /func Display/,/OMIT/
|
||||||
|
|
||||||
|
* Debug Display
|
||||||
|
|
||||||
|
.code display/display.go /func display/,/OMIT/
|
||||||
|
|
||||||
|
* Debug Display
|
||||||
|
|
||||||
|
.code display/display.go /case reflect.Ptr/,/OMIT/
|
||||||
|
|
||||||
|
* Setting Variables
|
||||||
|
|
||||||
|
- Некоторые выражения являются переменными: `x`, `x.f[1]`, `*p`.
|
||||||
|
- Некоторые выражения не являются переменными: `x`+`1`, `f(2)`.
|
||||||
|
- Переменные имеют _адрес_. Значение переменной можно изменить.
|
||||||
|
|
||||||
|
x := 2 // value type variable?
|
||||||
|
a := reflect.ValueOf(2) // 2 int no
|
||||||
|
b := reflect.ValueOf(x) // 2 int no
|
||||||
|
c := reflect.ValueOf(&x) // &x *int no
|
||||||
|
d := c.Elem() // 2 int yes
|
||||||
|
|
||||||
|
- `CanAddr`
|
||||||
|
|
||||||
|
fmt.Println(a.CanAddr()) // "false"
|
||||||
|
fmt.Println(b.CanAddr()) // "false"
|
||||||
|
fmt.Println(c.CanAddr()) // "false"
|
||||||
|
fmt.Println(d.CanAddr()) // "true"
|
||||||
|
|
||||||
|
* Setting Variables
|
||||||
|
|
||||||
|
x := 2
|
||||||
|
d := reflect.ValueOf(&x).Elem() // d refers to the variable x
|
||||||
|
px := d.Addr().Interface().(*int) // px := &x
|
||||||
|
*px = 3 // x = 3
|
||||||
|
fmt.Println(x) // "3"
|
||||||
|
|
||||||
|
- Или напрямую
|
||||||
|
|
||||||
|
d.Set(reflect.ValueOf(4))
|
||||||
|
fmt.Println(x) // "4"
|
||||||
|
|
||||||
|
- Или через специальный метод
|
||||||
|
|
||||||
|
d := reflect.ValueOf(&x).Elem()
|
||||||
|
d.SetInt(3)
|
||||||
|
fmt.Println(x) // "3"
|
||||||
|
|
||||||
|
- Нельзя менять _не_адресуемое_ значение.
|
||||||
|
|
||||||
|
x := 2
|
||||||
|
b := reflect.ValueOf(x)
|
||||||
|
b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value
|
||||||
|
|
||||||
|
* Setting Variables
|
||||||
|
|
||||||
|
Приватные поля можно читать, но нельзя менять.
|
||||||
|
|
||||||
|
stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var
|
||||||
|
fmt.Println(stdout.Type()) // "os.File"
|
||||||
|
fd := stdout.FieldByName("fd")
|
||||||
|
fmt.Println(fd.Int()) // "1"
|
||||||
|
fd.SetInt(2) // panic: unexported field
|
||||||
|
|
||||||
|
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"
|
||||||
|
|
||||||
|
* Struct Tags
|
||||||
|
|
||||||
|
.code search/main.go /func search/,/^}/
|
||||||
|
|
||||||
|
* Struct Tags
|
||||||
|
|
||||||
|
.code params/params.go /func Unpack/,/OMIT/
|
||||||
|
|
||||||
|
.code params/params.go /Build map of/,/OMIT/
|
||||||
|
|
||||||
|
* Struct Tags
|
||||||
|
|
||||||
|
.code params/params.go /Update/,/^}/
|
||||||
|
|
||||||
|
* Struct Tags
|
||||||
|
|
||||||
|
.code params/params.go /func populate/,/^}/
|
||||||
|
|
||||||
|
* Method Set
|
||||||
|
|
||||||
|
.code methods/methods.go /func Print/,/^}/
|
||||||
|
|
||||||
|
methods.Print(time.Hour)
|
||||||
|
// Output:
|
||||||
|
// type time.Duration
|
||||||
|
// func (time.Duration) Hours() float64
|
||||||
|
// func (time.Duration) Minutes() float64
|
||||||
|
// func (time.Duration) Nanoseconds() int64
|
||||||
|
// func (time.Duration) Seconds() float64
|
||||||
|
// func (time.Duration) String() string
|
||||||
|
|
||||||
|
* Interface
|
||||||
|
|
||||||
|
- Результат `ValueOf` - всегда конкретный тип.
|
||||||
|
|
||||||
|
var w io.Writer
|
||||||
|
t := reflect.TypeOf(w) // t == ???
|
||||||
|
|
||||||
|
var v interface{} = w // v == nil
|
||||||
|
t = reflect.TypeOf(v) // t == ???
|
||||||
|
|
||||||
|
- Чтобы получить `reflect.Type` равный интерфейсу, нужно использовать промежуточный указатель.
|
||||||
|
|
||||||
|
var w io.Writer
|
||||||
|
ptrT := reflect.TypeOf(&w) // ptrT == *io.Writer
|
||||||
|
t := ptrT.Elem() // t == io.Writer
|
||||||
|
|
||||||
|
* Заключение
|
||||||
|
|
||||||
|
- `reflect` создаёт хрупкий код. Там, где была бы ошибка компилятора, возникает `panic` во время исполнения.
|
||||||
|
- Поведение `reflect` кода нужно документировать отдельно, и оно не ясно из типов аргументов.
|
||||||
|
- `reflect` работает медленнее, чем специализация под конкретный тип.
|
||||||
|
|
29
lectures/08-reflect/methods/methods.go
Normal file
29
lectures/08-reflect/methods/methods.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
// See page 351.
|
||||||
|
|
||||||
|
// Package methods provides a function to print the methods of any value.
|
||||||
|
package methods
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//!+print
|
||||||
|
// Print prints the method set of the value x.
|
||||||
|
func Print(x interface{}) {
|
||||||
|
v := reflect.ValueOf(x)
|
||||||
|
t := v.Type()
|
||||||
|
fmt.Printf("type %s\n", t)
|
||||||
|
|
||||||
|
for i := 0; i < v.NumMethod(); i++ {
|
||||||
|
methType := v.Method(i).Type()
|
||||||
|
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
|
||||||
|
strings.TrimPrefix(methType.String(), "func"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//!-print
|
49
lectures/08-reflect/methods/methods_test.go
Normal file
49
lectures/08-reflect/methods/methods_test.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
package methods_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopl.io/ch12/methods"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExamplePrintDuration() {
|
||||||
|
methods.Print(time.Hour)
|
||||||
|
// Output:
|
||||||
|
// type time.Duration
|
||||||
|
// func (time.Duration) Hours() float64
|
||||||
|
// func (time.Duration) Minutes() float64
|
||||||
|
// func (time.Duration) Nanoseconds() int64
|
||||||
|
// func (time.Duration) Seconds() float64
|
||||||
|
// func (time.Duration) String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExamplePrintReplacer() {
|
||||||
|
methods.Print(new(strings.Replacer))
|
||||||
|
// Output:
|
||||||
|
// type *strings.Replacer
|
||||||
|
// func (*strings.Replacer) Replace(string) string
|
||||||
|
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
//!+output
|
||||||
|
methods.Print(time.Hour)
|
||||||
|
// Output:
|
||||||
|
// type time.Duration
|
||||||
|
// func (time.Duration) Hours() float64
|
||||||
|
// func (time.Duration) Minutes() float64
|
||||||
|
// func (time.Duration) Nanoseconds() int64
|
||||||
|
// func (time.Duration) Seconds() float64
|
||||||
|
// func (time.Duration) String() string
|
||||||
|
|
||||||
|
methods.Print(new(strings.Replacer))
|
||||||
|
// Output:
|
||||||
|
// type *strings.Replacer
|
||||||
|
// func (*strings.Replacer) Replace(string) string
|
||||||
|
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
|
||||||
|
//!-output
|
||||||
|
*/
|
94
lectures/08-reflect/params/params.go
Normal file
94
lectures/08-reflect/params/params.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
// See page 349.
|
||||||
|
|
||||||
|
// Package params provides a reflection-based parser for URL parameters.
|
||||||
|
package params
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//!+Unpack
|
||||||
|
|
||||||
|
// Unpack populates the fields of the struct pointed to by ptr
|
||||||
|
// from the HTTP request parameters in req.
|
||||||
|
func Unpack(req *http.Request, ptr interface{}) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OMIT
|
||||||
|
|
||||||
|
// Build map of fields keyed by effective name.
|
||||||
|
fields := make(map[string]reflect.Value)
|
||||||
|
v := reflect.ValueOf(ptr).Elem() // the struct variable
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
fieldInfo := v.Type().Field(i) // a reflect.StructField
|
||||||
|
tag := fieldInfo.Tag // a reflect.StructTag
|
||||||
|
name := tag.Get("http")
|
||||||
|
if name == "" {
|
||||||
|
name = strings.ToLower(fieldInfo.Name)
|
||||||
|
}
|
||||||
|
fields[name] = v.Field(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OMIT
|
||||||
|
|
||||||
|
// Update struct field for each parameter in the request.
|
||||||
|
for name, values := range req.Form {
|
||||||
|
f := fields[name]
|
||||||
|
if !f.IsValid() {
|
||||||
|
continue // ignore unrecognized HTTP parameters
|
||||||
|
}
|
||||||
|
for _, value := range values {
|
||||||
|
if f.Kind() == reflect.Slice {
|
||||||
|
elem := reflect.New(f.Type().Elem()).Elem()
|
||||||
|
if err := populate(elem, value); err != nil {
|
||||||
|
return fmt.Errorf("%s: %v", name, err)
|
||||||
|
}
|
||||||
|
f.Set(reflect.Append(f, elem))
|
||||||
|
} else {
|
||||||
|
if err := populate(f, value); err != nil {
|
||||||
|
return fmt.Errorf("%s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//!-Unpack
|
||||||
|
|
||||||
|
//!+populate
|
||||||
|
func populate(v reflect.Value, value string) error {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
v.SetString(value)
|
||||||
|
|
||||||
|
case reflect.Int:
|
||||||
|
i, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.SetInt(i)
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
b, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.SetBool(b)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported kind %s", v.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//!-populate
|
60
lectures/08-reflect/search/main.go
Normal file
60
lectures/08-reflect/search/main.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
|
||||||
|
// See page 348.
|
||||||
|
|
||||||
|
// Search is a demo of the params.Unpack function.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
//!+
|
||||||
|
|
||||||
|
import "gopl.io/ch12/params"
|
||||||
|
|
||||||
|
// search implements the /search URL endpoint.
|
||||||
|
func search(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Labels []string `http:"l"`
|
||||||
|
MaxResults int `http:"max"`
|
||||||
|
Exact bool `http:"x"`
|
||||||
|
}
|
||||||
|
data.MaxResults = 10 // set default
|
||||||
|
if err := params.Unpack(req, &data); err != nil {
|
||||||
|
http.Error(resp, err.Error(), http.StatusBadRequest) // 400
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...rest of handler...
|
||||||
|
fmt.Fprintf(resp, "Search: %+v\n", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
//!-
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/search", search)
|
||||||
|
log.Fatal(http.ListenAndServe(":12345", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
//!+output
|
||||||
|
$ go build gopl.io/ch12/search
|
||||||
|
$ ./search &
|
||||||
|
$ ./fetch 'http://localhost:12345/search'
|
||||||
|
Search: {Labels:[] MaxResults:10 Exact:false}
|
||||||
|
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming'
|
||||||
|
Search: {Labels:[golang programming] MaxResults:10 Exact:false}
|
||||||
|
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100'
|
||||||
|
Search: {Labels:[golang programming] MaxResults:100 Exact:false}
|
||||||
|
$ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming'
|
||||||
|
Search: {Labels:[golang programming] MaxResults:10 Exact:true}
|
||||||
|
$ ./fetch 'http://localhost:12345/search?q=hello&x=123'
|
||||||
|
x: strconv.ParseBool: parsing "123": invalid syntax
|
||||||
|
$ ./fetch 'http://localhost:12345/search?q=hello&max=lots'
|
||||||
|
max: strconv.ParseInt: parsing "lots": invalid syntax
|
||||||
|
//!-output
|
||||||
|
*/
|
Loading…
Reference in a new issue