go.talks: changes to "code that grows with grace"
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6774048
diff --git a/2012/chat.slide b/2012/chat.slide
index 97b2411..e723167 100644
--- a/2012/chat.slide
+++ b/2012/chat.slide
@@ -12,20 +12,6 @@
It's my favorite language. I think you'll like it, too.
-* Why Go?
-
-Needed a language for building servers* at Google, with an emphasis on:
-
-- simplicity,
-- ease of use,
-- readability,
-- comprehensibility,
-- efficiency,
-- concurrency,
-- scale.
-
-(* Go has since proven to be a great general-purpose language.)
-
* What is Go?
An open source (BSD licensed) project:
@@ -39,10 +25,35 @@
As of September 2012 we have more than 300 contributors.
-* Go has gophers
+* Go is about composition
+
+Go is Object Oriented, but not in the usual way.
+
+- no classes (methods may be declared on any type)
+- no subtype inheritance
+- interfaces are satisfied implicitly (structural typing)
+
+The result: simple pieces connected by small interfaces.
+
+* Go is about concurrency
+
+Go provides CSP-like concurrency primitives.
+
+- lightweight threads (goroutines)
+- typed thread-safe communication and synchronization (channels)
+
+The result: comprehensible concurrent code.
+
+* Go is about gophers
.image chat/gophers.jpg
+* Core values
+
+Go is about composition, concurrency, and gophers.
+
+Keep that in mind.
+
* Hello, go
.play chat/support/hello.go
@@ -170,6 +181,11 @@
.play chat/support/hello-web.go
+* Hello, WebSocket
+
+.code chat/support/websocket.js
+.play chat/support/websocket.go
+
* Using the http and websocket packages
.code chat/http/chat.go /package/,/^}/
@@ -185,17 +201,21 @@
We can't just use a `websocket.Conn` instead of the `net.Conn`, because a `websocket.Conn` is held open by its handler function. Here we use a channel to keep the handler running until the socket's `Close` method is called.
-.code chat/http/chat.go /type.socket/,/^}/
-.code chat/http/chat.go /func.+socket.+Close/,/^}/
-.code chat/http/chat.go /func.socketHandler/,/^}/
-
-* Demo
+.code chat/http-noembed/chat.go /type.socket/,/END/
* Struct embedding
-Go supports a kind of "mix-in" functionality with a feature known as "struct embedding". The embedding struct inherits the embedded type's methods.
+Go supports a kind of "mix-in" functionality with a feature known as "struct embedding". The embedding struct delegates calls to the embedded type's methods.
-.code chat/support/embed.go /type/,$
+.play chat/support/embed.go /type/,$
+
+* Embedding the websocket connection
+
+By embedding the `*websocket.Conn` as an `io.ReadWriter`, we can drop the explicit `socket` `Read` and `Write` methods.
+
+.code chat/http/chat.go /type.socket/,/END/
+
+* Demo
* Relieving loneliness
@@ -248,6 +268,15 @@
* Demo
+* One more thing
+
+* TCP and HTTP at the same time
+
+.code chat/both/chat.go /func main/,/^}/
+.code chat/both/chat.go /func netListen/,/^}/
+
+* Demo
+
* Discussion
* Further reading
diff --git a/2012/chat/both/chat.go b/2012/chat/both/chat.go
new file mode 100644
index 0000000..9a9d702
--- /dev/null
+++ b/2012/chat/both/chat.go
@@ -0,0 +1,117 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "time"
+
+ "code.google.com/p/go.net/websocket"
+)
+
+const listenAddr = "localhost:4000"
+
+func main() {
+ go netListen() // HL
+ http.HandleFunc("/", rootHandler)
+ http.Handle("/socket", websocket.Handler(socketHandler))
+ err := http.ListenAndServe(listenAddr, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func netListen() {
+ l, err := net.Listen("tcp", "localhost:4001")
+ if err != nil {
+ log.Fatal(err)
+ }
+ for {
+ c, err := l.Accept()
+ if err != nil {
+ log.Fatal(err)
+ }
+ go match(c)
+ }
+}
+
+type socket struct {
+ io.Reader
+ io.Writer
+ done chan bool
+}
+
+func (s socket) Close() error {
+ s.done <- true
+ return nil
+}
+
+var chain = NewChain(2) // 2-word prefixes
+
+func socketHandler(ws *websocket.Conn) {
+ r, w := io.Pipe()
+ go func() {
+ _, err := io.Copy(io.MultiWriter(w, chain), ws)
+ w.CloseWithError(err)
+ }()
+ s := socket{r, ws, make(chan bool)}
+ go match(s)
+ <-s.done
+}
+
+var partner = make(chan io.ReadWriteCloser)
+
+func match(c io.ReadWriteCloser) {
+ fmt.Fprint(c, "Waiting for a partner...")
+ select {
+ case partner <- c:
+ // now handled by the other goroutine
+ case p := <-partner:
+ chat(p, c)
+ case <-time.After(5 * time.Second):
+ chat(Bot(), c)
+ }
+}
+
+func chat(a, b io.ReadWriteCloser) {
+ fmt.Fprintln(a, "Found one! Say hi.")
+ fmt.Fprintln(b, "Found one! Say hi.")
+ errc := make(chan error, 1)
+ go cp(a, b, errc)
+ go cp(b, a, errc)
+ if err := <-errc; err != nil {
+ log.Println(err)
+ }
+ a.Close()
+ b.Close()
+}
+
+func cp(w io.Writer, r io.Reader, errc chan<- error) {
+ _, err := io.Copy(w, r)
+ errc <- err
+}
+
+// Bot returns an io.ReadWriteCloser that responds to
+// each incoming write with a generated sentence.
+func Bot() io.ReadWriteCloser {
+ r, out := io.Pipe() // for outgoing data
+ return bot{r, out}
+}
+
+type bot struct {
+ io.ReadCloser
+ out io.Writer
+}
+
+func (b bot) Write(buf []byte) (int, error) {
+ go b.speak()
+ return len(buf), nil
+}
+
+func (b bot) speak() {
+ time.Sleep(time.Second)
+ msg := chain.Generate(10) // at most 10 words
+ b.out.Write([]byte(msg))
+}
diff --git a/2012/chat/both/html.go b/2012/chat/both/html.go
new file mode 100644
index 0000000..7ab0779
--- /dev/null
+++ b/2012/chat/both/html.go
@@ -0,0 +1,66 @@
+package main
+
+import "html/template"
+import "net/http"
+
+func rootHandler(w http.ResponseWriter, r *http.Request) {
+ rootTemplate.Execute(w, listenAddr)
+}
+
+var rootTemplate = template.Must(template.New("root").Parse(`
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<script>
+
+var input, output, websocket;
+
+function showMessage(m) {
+ var p = document.createElement("p");
+ p.innerHTML = m;
+ output.appendChild(p);
+}
+
+function onMessage(e) {
+ showMessage(e.data);
+}
+
+function onClose() {
+ showMessage("Connection closed.");
+}
+
+function sendMessage() {
+ var m = input.value;
+ input.value = "";
+ websocket.send(m + "\n");
+ showMessage(m);
+}
+
+function onKey(e) {
+ if (e.keyCode == 13) {
+ sendMessage();
+ }
+}
+
+function init() {
+ input = document.getElementById("input");
+ input.addEventListener("keyup", onKey, false);
+
+ output = document.getElementById("output");
+
+ websocket = new WebSocket("ws://{{.}}/socket");
+ websocket.onmessage = onMessage;
+ websocket.onclose = onClose;
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+</head>
+<body>
+<input id="input" type="text">
+<div id="output"></div>
+</body>
+</html>
+`))
diff --git a/2012/chat/both/markov.go b/2012/chat/both/markov.go
new file mode 100644
index 0000000..6efee10
--- /dev/null
+++ b/2012/chat/both/markov.go
@@ -0,0 +1,79 @@
+package main
+
+// This Markov chain code is taken from the "Generating arbitrary text"
+// codewalk: http://golang.org/doc/codewalk/markov/
+
+import (
+ "bytes"
+ "fmt"
+ "math/rand"
+ "strings"
+ "sync"
+)
+
+// Prefix is a Markov chain prefix of one or more words.
+type Prefix []string
+
+// String returns the Prefix as a string (for use as a map key).
+func (p Prefix) String() string {
+ return strings.Join(p, " ")
+}
+
+// Shift removes the first word from the Prefix and appends the given word.
+func (p Prefix) Shift(word string) {
+ copy(p, p[1:])
+ p[len(p)-1] = word
+}
+
+// Chain contains a map ("chain") of prefixes to a list of suffixes.
+// A prefix is a string of prefixLen words joined with spaces.
+// A suffix is a single word. A prefix can have multiple suffixes.
+type Chain struct {
+ chain map[string][]string
+ prefixLen int
+ mu sync.Mutex
+}
+
+// NewChain returns a new Chain with prefixes of prefixLen words.
+func NewChain(prefixLen int) *Chain {
+ return &Chain{
+ chain: make(map[string][]string),
+ prefixLen: prefixLen,
+ }
+}
+
+// Write parses the bytes into prefixes and suffixes that are stored in Chain.
+func (c *Chain) Write(b []byte) (int, error) {
+ br := bytes.NewReader(b)
+ p := make(Prefix, c.prefixLen)
+ for {
+ var s string
+ if _, err := fmt.Fscan(br, &s); err != nil {
+ break
+ }
+ key := p.String()
+ c.mu.Lock()
+ c.chain[key] = append(c.chain[key], s)
+ c.mu.Unlock()
+ p.Shift(s)
+ }
+ return len(b), nil
+}
+
+// Generate returns a string of at most n words generated from Chain.
+func (c *Chain) Generate(n int) string {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ p := make(Prefix, c.prefixLen)
+ var words []string
+ for i := 0; i < n; i++ {
+ choices := c.chain[p.String()]
+ if len(choices) == 0 {
+ break
+ }
+ next := choices[rand.Intn(len(choices))]
+ words = append(words, next)
+ p.Shift(next)
+ }
+ return strings.Join(words, " ")
+}
diff --git a/2012/chat/http-noembed/chat.go b/2012/chat/http-noembed/chat.go
new file mode 100644
index 0000000..06d3254
--- /dev/null
+++ b/2012/chat/http-noembed/chat.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+
+ "code.google.com/p/go.net/websocket"
+)
+
+const listenAddr = "localhost:4000"
+
+func main() {
+ http.HandleFunc("/", rootHandler)
+ http.Handle("/socket", websocket.Handler(socketHandler))
+ err := http.ListenAndServe(listenAddr, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+type socket struct {
+ conn *websocket.Conn
+ done chan bool
+}
+
+func (s socket) Read(b []byte) (int, error) { return s.conn.Read(b) }
+func (s socket) Write(b []byte) (int, error) { return s.conn.Write(b) }
+
+func (s socket) Close() error {
+ s.done <- true
+ return nil
+}
+
+func socketHandler(ws *websocket.Conn) {
+ s := socket{conn: ws, done: make(chan bool)}
+ go match(s)
+ <-s.done
+}
+
+// END OMIT
+
+var partner = make(chan io.ReadWriteCloser)
+
+func match(c io.ReadWriteCloser) {
+ fmt.Fprint(c, "Waiting for a partner...")
+ select {
+ case partner <- c:
+ // now handled by the other goroutine
+ case p := <-partner:
+ chat(p, c)
+ }
+}
+
+func chat(a, b io.ReadWriteCloser) {
+ fmt.Fprintln(a, "Found one! Say hi.")
+ fmt.Fprintln(b, "Found one! Say hi.")
+ errc := make(chan error, 1)
+ go cp(a, b, errc)
+ go cp(b, a, errc)
+ if err := <-errc; err != nil {
+ log.Println(err)
+ }
+ a.Close()
+ b.Close()
+}
+
+func cp(w io.Writer, r io.Reader, errc chan<- error) {
+ _, err := io.Copy(w, r)
+ errc <- err
+}
diff --git a/2012/chat/http-noembed/html.go b/2012/chat/http-noembed/html.go
new file mode 100644
index 0000000..2e54af2
--- /dev/null
+++ b/2012/chat/http-noembed/html.go
@@ -0,0 +1,66 @@
+package main
+
+import "html/template"
+import "net/http"
+
+func rootHandler(w http.ResponseWriter, r *http.Request) {
+ rootTemplate.Execute(w, listenAddr)
+}
+
+var rootTemplate = template.Must(template.New("root").Parse(`
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<script>
+
+var input, output, websocket;
+
+function showMessage(m) {
+ var p = document.createElement("p");
+ p.innerHTML = m;
+ output.appendChild(p);
+}
+
+function onMessage(e) {
+ showMessage(e.data);
+}
+
+function onClose() {
+ showMessage("Connection closed.");
+}
+
+function sendMessage() {
+ var m = input.value;
+ input.value = "";
+ websocket.send(m);
+ showMessage(m);
+}
+
+function onKey(e) {
+ if (e.keyCode == 13) {
+ sendMessage();
+ }
+}
+
+function init() {
+ input = document.getElementById("input");
+ input.addEventListener("keyup", onKey, false);
+
+ output = document.getElementById("output");
+
+ websocket = new WebSocket("ws://{{.}}/socket");
+ websocket.onmessage = onMessage;
+ websocket.onclose = onClose;
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+</head>
+<body>
+<input id="input" type="text">
+<div id="output"></div>
+</body>
+</html>
+`))
diff --git a/2012/chat/http/chat.go b/2012/chat/http/chat.go
index 31938b5..3ff0e9f 100644
--- a/2012/chat/http/chat.go
+++ b/2012/chat/http/chat.go
@@ -21,7 +21,7 @@
}
type socket struct {
- io.ReadWriter
+ io.ReadWriter // HL
done chan bool
}
@@ -31,11 +31,13 @@
}
func socketHandler(ws *websocket.Conn) {
- s := socket{ws, make(chan bool)}
+ s := socket{ws, make(chan bool)} // HL
go match(s)
<-s.done
}
+// END OMIT
+
var partner = make(chan io.ReadWriteCloser)
func match(c io.ReadWriteCloser) {
diff --git a/2012/chat/markov/chat.go b/2012/chat/markov/chat.go
index 6e64485..05b0436 100644
--- a/2012/chat/markov/chat.go
+++ b/2012/chat/markov/chat.go
@@ -54,7 +54,7 @@
// now handled by the other goroutine
case p := <-partner:
chat(p, c)
- case <-time.After(10 * time.Second): // HL
+ case <-time.After(5 * time.Second): // HL
chat(Bot(), c) // HL
}
}
diff --git a/2012/chat/support/embed.go b/2012/chat/support/embed.go
index d7725c8..6dc0033 100644
--- a/2012/chat/support/embed.go
+++ b/2012/chat/support/embed.go
@@ -2,17 +2,19 @@
import "fmt"
-type A struct {
- B
+type A struct{}
+
+func (A) Hello() {
+ fmt.Println("Hello!")
}
-type B struct{}
-
-func (b B) String() string {
- return "B comes after A"
+type B struct {
+ A
}
+// func (b B) Hello() { b.A.Hello() } // (implicitly!)
+
func main() {
- var a A
- fmt.Println(a) // Println calls String to format a
+ var b B
+ b.Hello()
}
diff --git a/2012/chat/support/websocket.go b/2012/chat/support/websocket.go
new file mode 100644
index 0000000..6d1547d
--- /dev/null
+++ b/2012/chat/support/websocket.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "code.google.com/p/go.net/websocket"
+ "fmt"
+ "net/http"
+)
+
+func main() {
+ http.Handle("/", websocket.Handler(handler))
+ http.ListenAndServe("localhost:4000", nil)
+}
+
+func handler(c *websocket.Conn) {
+ var s string
+ fmt.Fscan(c, &s)
+ fmt.Println("Received:", s)
+ fmt.Fprint(c, "How do you do?")
+}
diff --git a/2012/chat/support/websocket.js b/2012/chat/support/websocket.js
new file mode 100644
index 0000000..f9809d6
--- /dev/null
+++ b/2012/chat/support/websocket.js
@@ -0,0 +1,3 @@
+var sock = new WebSocket("ws://localhost:4000/");
+sock.onmessage = function(m) { console.log("Received:", m.data); }
+sock.send("Hello!\n")