Chapter 7 Interfaces

Exercise 7.1: Using the ideas from ByteCounter, implement counters for words and for lines. You will find bufio.ScanWordsuseful.

type WordCounter int

// ScanWords is a split function for a Scanner that returns each
// space/punct-separated word of text, with surrounding spaces deleted. It will
// never return an empty string. The definition of space is set by
// unicode.IsSpace.
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error) {
    // Skip leading spaces/punct.
    start := 0
    for width := 0; start < len(data); start += width {
        var r rune
        r, width = utf8.DecodeRune(data[start:])
        if !unicode.IsSpace(r) && !unicode.IsPunct(r) {
            break
        }
    }
    // Scan until space, marking end of word.
    for width, i := 0, start; i < len(data); i += width {
        var r rune
        r, width = utf8.DecodeRune(data[i:])
        if unicode.IsSpace(r) || unicode.IsPunct(r) {
            return i + width, data[start:i], nil
        }
    }
    // If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
    if atEOF && len(data) > start {
        return len(data), data[start:], nil
    }
    // Request more data.
    return start, nil, nil
}

func (c *WordCounter) Write(p []byte) (int, error) {
    // range a string, you get every rune
    s := bufio.NewScanner(bytes.NewBuffer(p))

    // word split
    s.Split(ScanWords)
    for s.Scan() {
        // debug only
        //fmt.Println(s.Text())
        *c++
    }

    return len(p), s.Err()
}

func (c *WordCounter) String() string {
    return fmt.Sprintf("%d word(s)", *c)
}

// line counter
type LineCounter int

func (l *LineCounter) Write(p []byte) (int, error) {
    s := bufio.NewScanner(bytes.NewBuffer(p))

    // line counter
    for s.Scan() {
        *l++
    }
    return len(p), s.Err()
}

func (l *LineCounter) String() string {
    return fmt.Sprintf("%d line(s)", *l)
}

Exercise 7.2: Write a function CountingWriter with the signature below that, given an io.Writer, returns a newWriter that wraps the original, and a pointer to an int64 variable that at any moment contains the number of bytes written to the newWriter.

func CountingWriter(w io.Writer) (io.Writer, *int64)
type CounterWriter struct {
    counter int64
    writer  io.Writer
}

// must be pointer type in order to count
func (cw *CounterWriter) Write(p []byte) (int, error) {
    cw.counter += int64(len(p))
    return cw.writer.Write(p)
}

// newWriter is a Writer Wrapper, return original Writer
// and a Counter which record bytes have written
func CountingWriter(w io.Writer) (io.Writer, *int64) {
    cw := CounterWriter{0, w}
    return &cw, &cw.counter
}

Exercise 7.3: Write a String method for the *tree type in gopl.io/ch4/treesort(§4.4) that reveals the sequence of values in the tree.

func (root *tree) String() string {
    var buf bytes.Buffer
    buf.WriteByte('[')

    for _, v := range appendValues(nil, root) {
        buf.WriteString(strconv.Itoa(v))
        buf.WriteByte(' ')
    }

    // delete last ' ' in case
    if buf.Len() > len("[") {
        buf.Truncate(buf.Len() - 1)
    }

    buf.WriteByte(']')
    return buf.String()
}

Exercise 7.4: The strings.NewReader function returns a value that satisfies the io.Reader interface (and others) by reading from its argument, a string. Implement a simple version of NewReader yourself, and use it to make the HTML parser (§5.2) take input from a string.

// a simple version of strings.Reader implement io.Reader interface
type StringReader struct {
    s string
    i int64
}

func (r *StringReader) Read(p []byte) (n int, err error) {
    // if p is nil or empty, return (0, nil)
    if len(p) == 0 {
        return 0, nil
    }

    // copy() guarantee copy min(len(p),len(r.s[r.i:])) bytes
    n = copy(p, r.s[r.i:])
    if r.i += int64(n); r.i >= int64(len(r.s)) {
        err = io.EOF
    }
    return
}

// NewReader return a StringReader with s
func NewReader(s string) *StringReader {
    return &StringReader{s, 0}
}

Exercise 7.5: The LimitReader function in the io package accepts an io.Reader r and a number of bytesn, and returns another Reader that reads from r but reports an end-of-file condition after n bytes. Implement it.

func LimitReader(r io.Reader, n int64) io.Reader
// library "io" also have a type named LimitedReader
type LimitedReader struct {
    r io.Reader
    n int64
}

func LimitReader(r io.Reader, n int64) io.Reader {
    return &LimitedReader{r, n}
}

// a wrapper
func (l *LimitedReader) Read(p []byte) (n int, err error) {
    if l.n <= 0 {
        return 0, io.EOF
    }

    if int64(len(p)) > l.n {
        p = p[:l.n]
    }
    n, err = l.r.Read(p)
    l.n -= int64(n)
    return
}

Exercise 7.6: Add support for Kelvin temperatures to tempflag.

type Celsius float64
type Fahrenheit float64
type Kelvin float64

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) }
func KToC(k Kelvin) Celsius     { return Celsius(k - 273.15) }

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }

func (f *celsiusFlag) Set(s string) error {
    var unit string
    var value float64
    fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed
    switch unit {
    case "C", "°C":
        f.Celsius = Celsius(value)
        return nil
    case "F", "°F":
        f.Celsius = FToC(Fahrenheit(value))
        return nil
    case "K", "°K":
        f.Celsius = KToC(Kelvin(value))
        return nil
    }
    return fmt.Errorf("invalid temperature %q", s)
}

Exercise 7.7: Explain why the help message contains °C when the default value of 20.0 does not.

help会调用Celsius类型的String()方法显示,默认值则必须是合法的float64类型

Exercise 7.8: Many GUIs provide a table widget with a stateful multi-tier sort: the primary sort key is the most recently clicked column head, the secondary sort key is the second-most recently clicked column head, and so on. Define an implementation of sort.Interface for use by such a table. Compare that approach with repeated sorting using sort.Stable.

type multier struct {
    t         []*Track
    primary   string
    secondary string
    third     string
}

func (x *multier) Len() int      { return len(x.t) }
func (x *multier) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }

func (x *multier) Less(i, j int) bool {
    key := x.primary
    for k := 0; k < 3; k++ {
        switch key {
        case "Title":
            if x.t[i].Title != x.t[j].Title {
                return x.t[i].Title < x.t[j].Title
            }
        case "Year":
            if x.t[i].Year != x.t[j].Year {
                return x.t[i].Year < x.t[j].Year
            }
        case "Length":
            if x.t[i].Length != x.t[j].Length {
                return x.t[i].Length < x.t[j].Length
            }
        }
        if k == 0 {
            key = x.secondary
        } else if k == 1 {
            key = x.third
        }
    }
    return false
}

// update primary sorting key
func setPrimary(x *multier, p string) {
    x.primary, x.secondary, x.third = p, x.primary, x.secondary
}

// if x is *multiple type, then update ordering keys
func SetPrimary(x sort.Interface, p string) {
    if x, ok := x.(*multier); ok {
        setPrimary(x, p)
    }
}

// return a new multier
func NewMultier(t []*Track, p, s, th string) sort.Interface {
    return &multier{
        t:         t,
        primary:   p,
        secondary: s,
        third:     th,
    }
}


// Compare that approach with repeated sorting using sort.Stable: same effect

Exercise 7.9: Use the html/template package (§4.6) to replaceprintTrackswith a function that displays the tracks as an HTML table. Use the solution to the previous exercise to arrange that each click on a column head makes an HTTP request to sort the table.

// ref: https://stackoverflow.com/questions/25824095/order-by-clicking-table-header
var trackTable = template.Must(template.New("Track").Parse(`
<h1> Tracks </h1>
<table>
<tr style='text-align: left'>
    <th onclick="submitform('Title')">Title
        <form action="" name="Title" method="post">
            <input type="hidden" name="orderby" value="Title"/>
        </form>
    </th>
    <th>Artist
        <form action="" name="Artist" method="post">
            <input type="hidden" name="orderby" value="Artist"/>
        </form>
    </th>
    <th>Album
        <form action="" name="Album" method="post">
            <input type="hidden" name="orderby" value="Album"/>
        </form>
    </th>
    <th onclick="submitform('Year')">Year
        <form action="" name="Year" method="post">
            <input type="hidden" name="orderby" value="Year"/>
        </form>
    </th>
    <th onclick="submitform('Length')">Length
        <form action="" name="Length" method="post">
            <input type="hidden" name="orderby" value="Length"/>
        </form>
    </th>
</tr>
{{range .T}}
<tr>
    <td>{{.Title}}</td>
    <td>{{.Artist}}</td>
    <td>{{.Album}}</td>
    <td>{{.Year}}</td>
    <td>{{.Length}}</td>
</tr>
{{end}}
</table>

<script>
function submitform(formname) {
    document[formname].submit();
}
</script>
`))

type multier struct {
    T         []*Track // exported
    primary   string
    secondary string
    third     string
}

//!+printTracks
func printTracks(w io.Writer, x sort.Interface) {
    if x, ok := x.(*multier); ok {
        trackTable.Execute(w, x)
    }
}

func main() {
    // default sort by "Title"
    multi := NewMultier(tracks, "Title", "", "")
    sort.Sort(multi)

    // start a simple server
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if err := r.ParseForm(); err != nil {
            fmt.Printf("ParseForm: %v\n", err)
        }
        for k, v := range r.Form {
            if k == "orderby" {
                SetPrimary(multi, v[0])
            }
        }
        sort.Sort(multi)
        printTracks(w, multi)
    })
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

Exercise 7.10: Thesort.Interfacetype can be adapted to other uses. Write a functionIsPalindrome(s sort.Interface) boolthat reports whether the sequencesis a palindrome, in other words, reversing the sequence would not change it. Assume that the elements at indicesiandjare equal if!s.Less(i, j) && !s.Less(j, i).

// IsPalindrome report whether s is a palindrome
func IsPalindrome(s sort.Interface) bool {
    i, j := 0, s.Len()-1
    for j > i {
        // Less() only
        if !s.Less(i, j) && !s.Less(j, i) {
            i++
            j--
        } else {
            return false
        }
    }
    return true
}

Exercise 7.11: Add additional handlers so that clients can create, read, update, and delete database entries. For example, a request of the form/update?item=socks&price=6will update the price of an item in the inventory and report an error if the item does not exist or if the price is invalid. (Warning: this change introduces concurrent variable updates.)

var mux sync.Mutex

// create and update
func (db database) createUpdate(w http.ResponseWriter, req *http.Request) {
    // syntax: /create?item=cpu?price=5
    item := req.URL.Query().Get("item")
    price := req.URL.Query().Get("price")

    if item != "" && price != "" {
        p, err := strconv.ParseFloat(price, 32)
        if err != nil {
            w.WriteHeader(http.StatusBadRequest)
            fmt.Fprintf(w, "item: %q, price: %q\n", item, price)
            return
        }
        // create item
        mux.Lock()
        db[item] = dollars(p)
        mux.Unlock()
        w.WriteHeader(http.StatusCreated)
    } else {
        w.WriteHeader(http.StatusBadRequest)
    }
    fmt.Fprintf(w, "item: %q, price: %q\n", item, price)
}

// read
func (db database) price(w http.ResponseWriter, req *http.Request) {
    item := req.URL.Query().Get("item")
    if price, ok := db[item]; ok {
        fmt.Fprintf(w, "%s\n", price)
    } else {
        w.WriteHeader(http.StatusNotFound) // 404
        fmt.Fprintf(w, "no such item: %q\n", item)
    }
}

// delete
func (db database) deleate(w http.ResponseWriter, req *http.Request) {
    item := req.URL.Query().Get("item")
    if _, ok := db[item]; ok {
        mux.Lock()
        delete(db, item)
        mux.Unlock()
    } else {
        w.WriteHeader(http.StatusNotFound) // 404
        fmt.Fprintf(w, "no such item: %q\n", item)
    }
}

Exercise 7.12: Change the handler for/listto print its output as an HTML table, not text. You may find thehtml/templatepackage (§4.6) useful.

// ref: https://stackoverflow.com/questions/21302520/iterating-through-map-in-template
var itemTable = template.Must(template.New("Items").Parse(`
<h1>Items</h1>
<table>
    <tr>
        <th> Item </th>
        <th> Price </th>
    </tr>
    {{ range $k, $v := . }}
        <tr>
            <td>{{ $k }}</td>
            <td>{{ $v }}</td>
        </tr>
    {{end}}
</table>
`))

func (db database) list(w http.ResponseWriter, req *http.Request) {
    itemTable.Execute(w, db)
}

Exercise 7.13: Add aStringmethod toExprto pretty-print the syntax tree. Check that the results, when parsed again, yield an equivalent tree.

func (v Var) String() string {
    return string(v)
}

func (l literal) String() string {
    return fmt.Sprintf("%g", l)
}

func (u unary) String() string {
    return fmt.Sprintf("(%c%v)", u.op, u.x)
}

func (b binary) String() string {
    return fmt.Sprintf("(%v %c %v)", b.x, b.op, b.y)
}

func (c call) String() string {
    buf := new(bytes.Buffer)
    fmt.Fprintf(buf, "%s(", c.fn)
    for i, arg := range c.args {
        if i > 0 {
            buf.WriteString(", ")
        }
        write(buf, arg)
    }
    buf.WriteByte(')')
    return buf.String()
}

Exercise 7.14: Define a new concrete type that satisfies theExprinterface and provides a new operation such as computing the minimum value of its operands. Since theParsefunction does not create instances of this new type, to use it you will need to construct a syntax tree directly (or extend the parser).

// add {a,b,c,d...}-> sum(a,b,c,d)

type sum struct {
    args []Expr
}

func (s sum) Check(vars map[Var]bool) error {
    var err error
    for _, a := range s.args {
        err = a.Check(vars)
        if err != nil {
            return err
        }
    }
    return err
}


// {arg1, arg2, arg3...}, sum
func Sum(a ...float64) float64 {
    sum := 0.0
    for _, a := range a {
        sum += a
    }
    return sum
}

func (s sum) Eval(env Env) float64 {
    args := []float64{}

    for _, a := range s.args {
        args = append(args, a.Eval(env))
    }
    return Sum(args...)
}

func parsePrimary(lex *lexer) Expr {
    //...
    case '{':
        lex.next() // consume '{'
        var args []Expr
        if lex.token != '}' {
            for {
                args = append(args, parseExpr(lex))
                if lex.token != ',' {
                    break
                }
                lex.next() // consume ','
            }
            if lex.token != '}' {
                msg := fmt.Sprintf("got %q, want ')'", lex.token)
                panic(lexPanic(msg))
            }
        }
        lex.next() // consume '}'
        return sum{args}
    }
    //...
}

func (s sum) String() string {
    buf := new(bytes.Buffer)
    fmt.Fprint(buf, "{")
    for i, arg := range s.args {
        if i > 0 {
            buf.WriteString(", ")
        }
        write(buf, arg)
    }
    buf.WriteByte('}')
    return buf.String()
}

// test
// {"10*{a,2,{c,4}}", Env{"a": 1, "c": 3}, "100"},

Exercise 7.15: Write a program that reads a single expression from the standard input, prompts the user to provide values for any variables, then evaluates the expression in the resulting environment. Handle all errors gracefully.

package main

import (
    "fmt"
    "gopl.io/gopl-solutions/ch7/7.134/eval"
    "bufio"
    "os"
    "strings"
    "strconv"
)

//!+parseAndCheck
func parseAndCheck(s string) (eval.Expr, map[eval.Var]bool, error) {
    if s == "" {
        return nil, nil, fmt.Errorf("empty expression")
    }
    expr, err := eval.Parse(s)
    if err != nil {
        return nil, nil, err
    }
    vars := make(map[eval.Var]bool)
    if err := expr.Check(vars); err != nil {
        return nil, nil, err
    }
    return expr, vars, nil
}

func main() {
    var env = make(eval.Env)
    input := bufio.NewReader(os.Stdin)
    for {
        fmt.Fprint(os.Stdout, "=> ")
        s, _ := input.ReadString('\n')
        if s = strings.TrimSpace(s); s == "" {
            continue
        }

        expr, vars, err := parseAndCheck(s)
        if err != nil {
            fmt.Printf("parse %s failed: %v\n", s, err)
            continue
        }

        for k, _ := range vars {
            if _, ok := env[k]; !ok {
                fmt.Fprintf(os.Stdout, "=> %v is ? ", k)
                var n float64
                for {
                    d, _ := input.ReadString('\n')
                    d = strings.TrimSpace(d)

                    n, err = strconv.ParseFloat(d, 64)
                    if err != nil {
                        fmt.Printf("=> %g is invalid\n", n)
                        continue
                    }
                    break
                }
                env[k] = n
            }
        }
        fmt.Printf("%v = %v\n", expr, expr.Eval(env))
    }
}

Exercise 7.16: Write a web-based calculator program.

// skip for now

Exercise 7.17: Extendxmlselectso that elements may be selected not just by name, but by their attributes too, in the manner of CSS, so that, for instance, an element like<div id="page" class="wide">could be selected by a matchingidorclassas well as its name.

func main() {
    dec := xml.NewDecoder(os.Stdin)
    var found bool
    for {
        tok, err := dec.Token()
        if err == io.EOF {
            break
        } else if err != nil {
            fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err)
            os.Exit(1)
        }
        switch tok := tok.(type) {
        case xml.StartElement:
            for _, v := range tok.Attr {
                if v.Name.Local == "class" && v.Value == "enumar" {
                    found = true
                }
            }
        case xml.EndElement:
            found = false
        case xml.CharData:
            if found {
                fmt.Printf("%s\n", tok)
            }
        }
    }
}

Exercise 7.18: Using the token-based decoder API, write a program that will read an arbitrary XML document and construct a tree of generic nodes that represents it. Nodes are of two kinds:CharDatanodes represent text strings, andElementnodes represent named elements and their attributes. Each element node has a slice of child nodes. You may find the following declarations helpful.

import "encoding/xml"

type Node interface{} // CharData or *Element

type CharData string

type Element struct {
    Type     xml.Name
    Attr     []xml.Attr
    Children []Node
}
// skip for now

Notes:

  • Interfaces: Interface types express generalizations or abstractions about the behaviors of other types.

  • Contrete Type: When you have a value of a concrete type, you know exactly what it is and what you can do with it.

  • Interface Type: An interface is anabstract type. It doesn’t expose the representation or internal structure of its values, or the set of basic operations they support; it reveals only some of their methods. When you have a value of an interface type, you know nothing about what it is; you know only what it can do, or more precisely, what behaviors are provided by its methods.

  • Empty interface type: So what does the type interface{}, which has no methods at all, tell us about the concrete types that satisfy it? Nothing. Because the empty interface type places no demands on the types that satisfy it, we can assign any value to the empty interface.

  • Interface Values: interface value, has two components, a concrete type and a value of that type. These are called the interface’s dynamic type and dynamic value.

  • For a statically typed language like Go, types are a compile-time concept, so a type is not a value. In our conceptual model, a set of values called type descriptors provide information about each type, such as its name and methods. In an interface value, the type component is represented by the appropriate type descriptor.

  • An interface value is described as nil or non-nil based on its dynamic type.

  • In general, we cannot know at compile time what the dynamic type of an interface value will be, so a call through an interface must use dynamic dispatch. Instead of a direct call, the compiler must generate code to obtain the address of the method named Write from the type descriptor, then make an indirect call to that address.

  • An interface value can hold arbitrarily large dynamic values. Conceptually, the dynamic value always fits inside the interface value, no matter how large its type. (This is only a conceptual model; a realistic implementation is quite different.)

  • Interface values may be compared using == and != . Two interface values are equal if both are nil, or if their dynamic types are identical and their dynamic values are equal according to the usual behavior of == for that type. Because interface values are comparable, they may be used as the keys of a map or as the operand of a switch statement. However, if two interface values are compared and have the same dynamic type, but that type is not comparable (a slice, for instance), then the comparison fails with a panic. In this respect, interface types are unusual. Other types are either safely comparable (like basic types and pointers) or not comparable at all (like slices, maps, and functions), but when comparing interface values or aggregate types that contain interface values, we must be aware of the potential for a panic. A similar risk exists when using interfaces as map keys or switch operands. Only compare interface values if you are certain that they contain dynamic values of comparable types.

  • Caveat: An Interface Containing a Nil Pointer Is Non-Nil: A nil interface value, which contains no value at all, is not the same as an interface value containing a pointer that happens to be nil. This subtle distinction creates a trap into which every Go programmer has stumbled.

  • The web server invokes each handler in a new goroutine, so handlers must take precautions such as _locking _when accessing variables that other goroutines, including other requests to the same handler, may be accessing.

  • A type assertion is an operation applied to an interface value. In other words, a type assertion to a concrete type extracts the concrete value from its operand. There are two possibilities.

    • First, if the asserted type T is a concrete type, then the type assertion checks whether x’s dynamic type is identical to T. If this check succeeds, the result of the type assertion is x’s dynamic value, whose type is of course T. In other words, a type assertion to a concrete type extracts the concrete value from its operand.
    • Second, if instead the asserted type T is an interface type, then the type assertion checks whether x’s dynamic type satisfies T. If this check succeeds, the dynamic value is not extracted; the result is still an interface value with the same type and value components, but the result has the interface type T. In other words, a type assertion to an interface type changes the type of the expression, making a different (and usually larger) set of methods accessible, but it preserves the dynamic type and value components inside the interface value.
  • Type Switch: Interfaces are used in two distinct styles.

    • In the first style, exemplified by io.Reader, io.Writer, fmt.Stringer, sort.Interface,http.Handler, and error, an interface’s methods express the similarities of the concrete types that satisfy the interface but hide the representation details and intrinsic operations of those concrete types. The emphasis is on the methods, not on the concrete types.
    • The second style exploits the ability of an interface value to hold values of a variety of concrete types and considers the interface to be the union of those types. Type assertions are used to discriminate among these types dynamically and treat each case differently. In this style, the emphasis is on the concrete types that satisfy the interface, not on the interface’s methods (if indeed it has any), and there is no hiding of information. We’ll describe interfaces used this way as discriminated unions.
  • The purpose of a traditional interface like io.Reader is to hide details of the concrete types that satisfy it so that new implementations can be created; each concrete type is treated uniformly. By contrast, the set of concrete types that satisfy a discriminated union is fixed by the design and exposed, not hidden.

  • Go has great support for the object-oriented style of programming, but this does not mean you need to use it exclusively. Not everything need be an object; standalone functions have their place, as do unencapsulated data types.

results matching ""

    No results matching ""