A journey in software development.
Or, how to do what you want and get away with it.
8 March 2018
Dmitri Shuralyov
Dmitri Shuralyov
A video of this talk was recorded at University of Ontario Institute of Technology (UOIT) on March 8, 2018.
Video: www.youtube.com/watch?v=XQqi6BwPhV8
2Which ones have you used? Heard of?
Which ones do you like?
What about frontend?
3Preface:
You may have heard of Go.
It's my favorite language. I think you'll like it, too.
6An open source (BSD licensed) project:
gc
and gccgo
),Go is Object Oriented, but not in the usual way.
The result: simple pieces connected by small interfaces.
8Go provides CSP-like concurrency primitives.
The result: comprehensible concurrent code.
9Go is about composition, concurrency, and gophers.
Keep that in mind.
11package main import "fmt" func main() { fmt.Println("Hello, Go.") }
package main import ( "fmt" "log" "net" ) const listenAddr = "localhost:4000" func main() { l, err := net.Listen("tcp", listenAddr) if err != nil { log.Fatal(err) } for { c, err := l.Accept() if err != nil { log.Fatal(err) } fmt.Fprintln(c, "Hello!") c.Close() } }
Hey neato! We just used Fprintln
to write to a net connection.
That's because a Fprintln
writes to an io.Writer
, and net.Conn
is an io.Writer
.
fmt.Fprintln(c, "Hello!")
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
type Writer interface { Write(p []byte) (n int, err error) }
type Conn interface { Read(b []byte) (n int, err error) Write(b []byte) (n int, err error) Close() error // ... some additional methods omitted ... }
package main import ( "io" "log" "net" ) const listenAddr = "localhost:4000" func main() { l, err := net.Listen("tcp", listenAddr) if err != nil { log.Fatal(err) } for { c, err := l.Accept() if err != nil { log.Fatal(err) } io.Copy(c, c) } }
Goroutines are lightweight threads that are managed by the Go runtime. To run a function in a new goroutine, just put "go"
before the function call.
package main import ( "fmt" "time" ) func main() { go say("let's go!", 3) go say("ho!", 2) go say("hey!", 1) time.Sleep(4 * time.Second) } func say(text string, secs int) { time.Sleep(time.Duration(secs) * time.Second) fmt.Println(text) }
package main import ( "io" "log" "net" ) const listenAddr = "localhost:4000" func main() { l, err := net.Listen("tcp", listenAddr) if err != nil { log.Fatal(err) } for { c, err := l.Accept() if err != nil { log.Fatal(err) } go io.Copy(c, c) } }
Let's look at a simple program, based on the "chat roulette" site.
In short:
The chat program is similar to the echo program. With echo, we copy a connection's incoming data back to the same connection.
For chat, we must copy the incoming data from one user's connection to another's.
Copying the data is easy. The hard part is matching one partner with another.
19Goroutines communicate via channels. A channel is a typed conduit that may be synchronous (unbuffered) or asynchronous (buffered).
package main import "fmt" func main() { ch := make(chan int) go fibs(ch) for i := 0; i < 20; i++ { fmt.Println(<-ch) } } func fibs(ch chan int) { i, j := 0, 1 for { ch <- j i, j = j, i+j } }
A select statement is like a switch, but it selects over channel operations (and chooses exactly one of them).
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(time.Millisecond * 250) boom := time.After(time.Second * 1) for { select { case <-ticker.C: fmt.Println("tick") case <-boom: fmt.Println("boom!") return } } }
In the accept loop, we replace the call to io.Copy
:
for { c, err := l.Accept() if err != nil { log.Fatal(err) } go io.Copy(c, c) }
with a call to a new function, match
:
for { c, err := l.Accept() if err != nil { log.Fatal(err) } go match(c) }
The match
function simultaneously tries to send and receive a connection on a channel.
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) } }
The match
function simultaneously tries to send and receive a connection on a channel.
var partner = make(chan io.ReadWriteCloser) func match(c io.ReadWriteCloser) { func match(c io.ReadWriteCloser) { fmt.Fprint(c, "Waiting for a partner...") fmt.Fprint(c, "Waiting for a partner...") select { select { case partner <- c: case partner <- c: // now handled by the other goroutine // now handled by the other goroutine case p := <-partner: case p := <-partner: chat(p, c) chat(p, c) } } } }
The chat function sends a greeting to each connection and then copies data from one to the other, and vice versa.
Notice that it launches another goroutine so that the copy operations may happen concurrently.
func chat(a, b io.ReadWriteCloser) { fmt.Fprintln(a, "Found one! Say hi.") fmt.Fprintln(b, "Found one! Say hi.") go io.Copy(a, b) io.Copy(b, a) }
It's important to clean up when the conversation is over. To do this we send the error value from each io.Copy
call to a channel, log any non-nil errors, and close both connections.
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 }
"Cute program," you say, "but who wants to chat over a raw TCP connection?"
Good point. Let's modernize it by turning it into a web app.
30package main import ( "fmt" "log" "net/http" ) const listenAddr = "localhost:4000" func main() { http.HandleFunc("/", handler) err := http.ListenAndServe(listenAddr, nil) if err != nil { log.Fatal(err) } } func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, web.") }
Want to see WebSockets, markov chains, learning bots, TCP and HTTP at once?
Video: vimeo.com/53221560
Slides: talks.golang.org/2012/chat.slide
32Started programming in teen years.
Excited by the ability to create video games.
34Moved towards C++ because it's fast, cross-platform, compiles native binaries.
41Master's degree.
I realized I wanted to create tools that help people (more directly than games).
Also grew frustrated with C++, extremely motivated to do something about it.
43Bret Victor gave an incredible talk on following a guiding principle.
Video: vimeo.com/36579366
44Started to work on an experimental project.
Submitted it to a Live Programming Contest at LIVE 2013 Workshop. Won 1st place.
Got to visit San Francisco.
It helped me land my first job at a startup in San Francisco.
Startups with lots of raised money are bottlenecked on people.
A real-life story about a star.
55A story about a talented engineer's ups and downs.
60I like open source because people aren't locked out from being able to contribute.
It's normal to start small to test the waters.
Go is probably my first programming language experience with a "community".
There are conferences, meetups, talks, slack group, etc.
66Working full time on open source. Including personal projects (changes app, Go Package Store, etc.).
Contributing to Go, GopherJS.
69I want to use Go, but it's hard to ignore the browser in 2018.
71Go already runs on many platforms.
# Desktop OSes. GOOS=darwin GOARCH=arm64 go build GOOS=linux GOARCH=amd64 go build GOOS=windows GOARCH=arm64 go build GOOS=plan9 GOARCH=amd64 go build # Plan 9. GOOS=linux GOARCH=s390x go build # Linux on IBM z Systems. # Mobile OSes. GOOS=darwin GOARCH=arm64 go build # iOS. GOOS=android GOARCH=arm go build # Android.
Go already runs on many platforms.
73How about one more?
74GopherJS is a compiler that compiles Go to JavaScript, which runs in browsers.
//gopherjs:blocking
.//gopherjs:blocking
.Video: www.thedotpost.com/2016/10/dmitri-shuralyov-go-in-the-browser
Slides: dmitri.shuralyov.com/talks/2016/Go-in-the-browser/Go-in-the-browser.slide
76