Hello, Gophers!
Gophercon Opening Keynote
24 April 2014
Rob Pike
Google, Inc.
Rob Pike
Google, Inc.
A video of this talk was recorded at GopherCon in Denver.
2package main import "fmt" func main() { fmt.Printf("Hello, gophers!\n") }
This is a historic occasion.
Go has achieved a level of success worthy of a conference.
5Many factors contribute to that success.
A look back, focusing on code.
7A close look at two programs.
First is the first Go program you ever saw. Historic for you.
Second is the first Go program we ever saw. Historic for all gophers.
First up: "hello, world".
8main( ) { extrn a, b, c; putchar(a); putchar(b); putchar(c); putchar('!*n'); } a 'hell'; b 'o, w'; c 'orld';
First appeared in a 1972 B tutorial by Brian W. Kernighan.
(Not, as sometimes claimed, a few years earlier in BCPL.)
main() { printf("hello, world"); }
First appeared in
Programming in C: A Tutorial, by Brian W. Kernighan, 1974.
Came as a document with Unix v5.
main() { printf("hello, world\n"); }
First appeared in
The C Programming Language, by Brian W. Kernighan and Dennis M. Ritchie, 1978.
#include <stdio.h> main() { printf("hello, world\n"); }
Appeared in
The C Programming Language, Second Edition, (Based on Draft-Proposed ANSI C)
by Brian W. Kernighan and Dennis M. Ritchie, 1988.
#include <stdio.h> main(void) { printf("hello, world\n"); }
Appeared in
The C Programming Language, Second Edition, round two,
by Brian W. Kernighan and Dennis M. Ritchie, 1988.
"You've gotta put a void THERE?" -Ken Thompson
13(Skipping all the intermediate languages.)
Go discussions start in late 2007.
Specification first drafted in March 2008.
For experimentation and prototyping, compiler work already underway.
Initially generated C output.
Once the specification arose, compiler rewritten to generate native code.
package main func main() int { print "hello, world\n"; return 0; }
First checked-in test.
The print
builtin is all we have, and main
returns an int
.
Note: no parentheses on print
.
package main func main() { print "hello, world\n"; }
When main
returns, the program calls exit(0)
.
package main func main() { print("hello, world\n"); }
Parentheses now required: print
now a function not a primitive.
package main import "fmt" func main() { fmt.printf("hello, world\n"); }
The "printf as we know and love it" goes in.
(The test still uses print
not printf
; we've switched examples here.)
package main import "fmt" func main() { fmt.Printf("hello, world\n"); }
Upper case for export. "Casification."
19package main import "fmt" func main() { fmt.Printf("hello, world\n") }
No more semicolons.
A major change that occurs after the open source release (Nov 10, 2009).
The current version.
It took us a while to get here (32 years!).
A lot of history.
20
We "started with C" but Go is profoundly different.
Some of the languages that influenced and informed the design of Go:
C: statement and expression syntax
Pascal: declaration syntax
Modula 2, Oberon 2: packages
CSP, Occam, Newsqueak, Limbo, Alef: concurrency
BCPL: the semicolon rule
Smalltalk: methods
Newsqueak: <-
, :=
APL: iota
And others. Also some was invented whole: defer
, constants, for instance.
Plus lessons good and bad from all those plus:
C++, C#, Java, JavaScript, LISP, Python, Scala, ...
Which brings us to today.
package main import "fmt" func main() { fmt.Println("Hello, Gophers (some of whom know 日本語)!") }
Let's dig deeper, break this down.
22
package
main
import
"fmt"
func
main
(
)
{
fmt
.
Println
(
"Hello, Gophers (some of whom know 日本語)!"
)
}
Major topic in early design discussions: Key to scalability.
What is a package? Ideas from Modula-2 etc.
Why are there packages?
Hold all the information you need to build.
No circular dependencies (imports).
No subpackages.
Separation of package name and package path.
Visibility is package-level, not type-level.
Within a package, you have the whole language, outside only what you permit.
One place where C legacy shows through.
Was originally Main
for some forgotten reason.
Main
package, main
function.
Special because the root of the initialization tree.
Mechanism for loading a package.
Implemented by the compiler (as opposed to a text processor).
Worked hard to make it efficient and linear.
Imports a package, not a set of identifiers.
As for export: It used to be a keyword.
26
Package path is just a string, not a list of identifiers.
Allows the language to avoid defining what it means—adaptability.
From the beginning wanted a URL as an option.
Allows for future growth.
A keyword introduces functions (and types, variables, constants) for easy parsing.
Easy parsing is important with function literals (closures).
By the way, keyword was originally function
.
From: Ken Thompson <ken@google.com>
To: gri, r
larry and sergey came by tonight. we
talked about go for more than an hour.
they both said they liked it very much.
p.s. one of larrys comments was "why isnt function spelled func?"
---
From: Rob Pike <r@google.com>
To: ken, gri
fine with me. seems compatible with 'var'.
anyway we can always say, "larry said to call it 'func'"
29
Where program starts... except it isn't.
Separation of initialization from normal execution, long planned.
Where does initialization happen?
Feeds back to package design.
Look Ma, no void
.
No return value for main
: handled by runtime.
No function args (command line is in os
package).
No return value.
Return values and syntax.
31
Braces not spaces.
And not square brackets.
Why is the newline after the brace?
All imported identifiers are qualified by their import.
Every identifier is either local to package or func, or qualified by type or import.
Profound effect on readability.
Why fmt
not format
?
How many uses are there in Go for a period token? (Lots.)
The meaning of a.B
requires using the type system.
But it is clear to humans and very easy to read.
Autopromotion of pointers (no ->
operator).
Println
not println
: capitals for export.
Always knew it would be reflection-driven. (Safety, formatless printing.)
Variadic functions.
Argument type was (...)
; became (...interface{})
on Feb 1, 2010.
Traditional function syntax.
36
UTF-8 input source, so strings as literals are UTF-8 automatically.
But what is a string?
One of the first things written in the specification, hardly changed today.
No semicolon.
Semicolons went away shortly after release.
Much futzing around to try to cull them in early days.
Eventually accepted the BCPL approach.
Done.
39
Plus tools, ecosystem, community, ...:
Language is central but only part of the story.
Factors:
Finally: Commitment.
Go 1.0 locked down the language and libraries.
41Now watch similar evolution of a second program.
42
Problem specification from
Communicating Sequential Processes, by C. A. R. Hoare, 1978
"Problem: To print in ascending order all primes less than
10000. Use an array of processes, SIEVE, in which each
process inputs a prime from its predecessor and prints it.
The process then inputs an ascending stream of numbers
from its predecessor and passes them on to its successor,
suppressing any that are multiples of the original prime. "
Defined in the 1978 CSP paper.
(Note: not the sieve of Eratosthenes.)
"This beautiful solution was contributed by David Gries."
44In Hoare's 1978 CSP paper
[SIEVE(i:1..100):: p,mp:integer; SIEVE(i - 1)?p; print!p; mp := p; comment mp is a multiple of p; *[m:integer; SIEVE(i - 1)?m → *[m > mp → mp := mp + p]; [m = mp → skip ||m < mp → SIEVE(i + 1)!m ] ] ||SIEVE(0)::print!2; n:integer; n := 3; *[n < 10000 → SIEVE(1)!n; n := n + 2] ||SIEVE(101)::*[n:integer;SIEVE(100)?n → print!n] ||print::*[(i:0..101) n:integer; SIEVE(i)?n → ...] ]
No channels, just processes so number of primes is fixed by program.
45
circa 1988.
Language by Rob Pike, program by Tom Cargill via Doug McIlroy.
Uses channels, so length of run is programmable.
(Where did the idea of channels come from?)
counter:=prog(end: int, c: chan of int) { i:int; for(i=2; i<end; i++) c<-=i; }; filter:=prog(prime: int, listen: chan of int, send: chan of int) { i:int; for(;;) if((i=<-listen)%prime) send<-=i; };
sieve:=prog(c: chan of int) { for(;;){ prime:=<-c; print(prime, " "); newc:=mk(chan of int); begin filter(prime, c, newc); c=newc; } }; count:=mk(chan of int); begin counter(10000, count); begin sieve(count); "";
First version in a Go specification, probably the second non-trivial program written.
>
to send, <
to receive. Channels are pointers. Main
is capitalized.
package Main // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch *chan> int) { for i := 2; ; i++ { >ch = i; // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in *chan< int, out *chan> int, prime int) { for ; ; { i := <in; // Receive value of new variable 'i' from 'in'. if i % prime != 0 { >out = i; // Send 'i' to channel 'out'. } } }
// The prime sieve: Daisy-chain Filter processes together. func Sieve() { ch := new(chan int); // Create a new channel. go Generate(ch); // Start Generate() as a subprocess. for ; ; { prime := <ch; printf("%d\n", prime); ch1 := new(chan int); go Filter(ch, ch1, prime); ch = ch1; } } func Main() { Sieve(); }
-<
to send, <-
to receive. Channels still pointers. Now main
not capitalized.
package main // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch *chan-< int) { for i := 2; ; i++ { ch -< i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in *chan<- int, out *chan-< int, prime int) { for { i := <-in; // Receive value of new variable 'i' from 'in'. if i % prime != 0 { out -< i // Send 'i' to channel 'out'. } } }
// The prime sieve: Daisy-chain Filter processes together. func Sieve() { ch := new(chan int); // Create a new channel. go Generate(ch); // Start Generate() as a subprocess. for { prime := <-ch; printf("%d\n", prime); ch1 := new(chan int); go Filter(ch, ch1, prime); ch = ch1 } } func main() { Sieve() }
Communication operators now prefix and postfix <-
. Channels still pointers.
package main // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch *chan <- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in *chan <- int, out *<-chan int, prime int) { for { i := <-in; // Receive value of new variable 'i' from 'in'. if i % prime != 0 { out <- i // Send 'i' to channel 'out'. } } }
// The prime sieve: Daisy-chain Filter processes together. func Sieve() { ch := new(chan int); // Create a new channel. go Generate(ch); // Start Generate() as a subprocess. for { prime := <-ch; print(prime, "\n"); ch1 := new(chan int); go Filter(ch, ch1, prime); ch = ch1 } } func main() { Sieve() }
The make
builtin arrives. No pointers. Code wrong! (One *
left, bad argument types.)
package main // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch chan <- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in chan <- int, out *<-chan int, prime int) { for { i := <-in; // Receive value of new variable 'i' from 'in'. if i % prime != 0 { out <- i // Send 'i' to channel 'out'. } } }
// The prime sieve: Daisy-chain Filter processes together. func Sieve() { ch := make(chan int); // Create a new channel. go Generate(ch); // Start Generate() as a subprocess. for { prime := <-ch; print(prime, "\n"); ch1 := make(chan int); go Filter(ch, ch1, prime); ch = ch1 } } func main() { Sieve() }
First correct modern version. Also: capitalization gone. Uses fmt
.
package main import "fmt" // Send the sequence 2, 3, 4, ... to channel 'ch'. func generate(ch chan<- int) { for i := 2; ; i++ { ch <- i; // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func filter(src <-chan int, dst chan<- int, prime int) { for i := range src { // Loop over values received from 'src'. if i%prime != 0 { dst <- i; // Send 'i' to channel 'dst'. } } }
// The prime sieve: Daisy-chain filter processes together.
func sieve() {
ch := make(chan int); // Create a new channel.
go generate(ch); // Start generate() as a subprocess.
for {
prime := <-ch;
fmt.Print(prime, "\n");
ch1 := make(chan int);
go filter(ch, ch1, prime);
ch = ch1;
}
}
func main() {
sieve();
}
// +build ignore,OMIT
package main
import "fmt"
// Send the sequence 2, 3, 4, ... to channel 'ch'.
func generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i; // Send 'i' to channel 'ch'.
}
}
// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
for i := range src { // Loop over values received from 'src'.
if i%prime != 0 {
dst <- i; // Send 'i' to channel 'dst'.
}
}
}
// The prime sieve: Daisy-chain filter processes together. func sieve() { ch := make(chan int); // Create a new channel. go generate(ch); // Start generate() as a subprocess. for { prime := <-ch; fmt.Print(prime, "\n"); ch1 := make(chan int); go filter(ch, ch1, prime); ch = ch1; } } func main() { sieve(); }
Semicolons gone. Program as it is today.
package main import "fmt" // Send the sequence 2, 3, 4, … to channel 'ch'. func generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'src' to channel 'dst', // removing those divisible by 'prime'. func filter(src <-chan int, dst chan<- int, prime int) { for i := range src { // Loop over values received from 'src'. if i%prime != 0 { dst <- i // Send 'i' to channel 'dst'. } } }
// The prime sieve: Daisy-chain filter processes together.
func sieve() {
ch := make(chan int) // Create a new channel.
go generate(ch) // Start generate() as a subprocess.
for {
prime := <-ch
fmt.Print(prime, "\n")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
func main() {
sieve()
}
// +build ignore,OMIT
package main
import "fmt"
// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}
// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
for i := range src { // Loop over values received from 'src'.
if i%prime != 0 {
dst <- i // Send 'i' to channel 'dst'.
}
}
}
// The prime sieve: Daisy-chain filter processes together. func sieve() { ch := make(chan int) // Create a new channel. go generate(ch) // Start generate() as a subprocess. for { prime := <-ch fmt.Print(prime, "\n") ch1 := make(chan int) go filter(ch, ch1, prime) ch = ch1 } } func main() { sieve() }
"This beautiful solution was contributed by a decades-long process of design."
59select
The core connector for real concurrent applications. (A fact not always appreciated).
Origins in Dijkstra's guarded commands.
Made truly concurrent in Hoare's CSP.
Refined through Newsqueak, Alef, Limbo, and other routes.
Go's version specified on March 26, 2008.
Simplifications, clarifications, syntactic considerations.
Sieve program unchanged since late 2009—stability!
Open source systems are not always dependably compatible and stable.
Go is.
This is a very important reason for Go's success.
61Graphs in usage metrics show knee in curve at Go 1.0 release.
The factors for Go's success?
Obvious: Features and tools.
gofmt
Less obvious: process.
In short, an open source community that shares our mission,
coupled to a language designed for today's world.
From Go: the emerging language of cloud infrastructure by Donnie Berkholz, March 2014.
/s/emerging
This is where you come in!