]> Cypherpunks repositories - gostls13.git/commitdiff
A codewalk through a simple program that illustrates several aspects of Go functions...
authorJohn DeNero <denero@google.com>
Fri, 8 Apr 2011 01:05:15 +0000 (18:05 -0700)
committerRob Pike <r@golang.org>
Fri, 8 Apr 2011 01:05:15 +0000 (18:05 -0700)
R=adg, rsc, iant2, r
CC=golang-dev
https://golang.org/cl/4306045

doc/codewalk/functions.xml [new file with mode: 0644]
doc/codewalk/pig.go [new file with mode: 0644]

diff --git a/doc/codewalk/functions.xml b/doc/codewalk/functions.xml
new file mode 100644 (file)
index 0000000..986a017
--- /dev/null
@@ -0,0 +1,115 @@
+<codewalk title="First-Class Functions in Go">
+
+<step title="Introduction" src="doc/codewalk/pig.go">
+       Go supports first class functions, higher-order functions, user-defined
+       function types, function literals, closures, and multiple return values.
+  <br/><br/>
+
+       This rich feature set supports a functional programming style in a strongly
+       typed language.
+       <br/><br/>
+
+       In this codewalk we will look at a simple program that simulates a dice game
+       called <a href="http://en.wikipedia.org/wiki/Pig_(dice)">Pig</a> and evaluates
+       basic strategies.
+</step>
+
+<step title="Game overview" src="doc/codewalk/pig.go:/\/\/ A score/,/thisTurn int\n}/">
+  Pig is a two-player game played with a 6-sided die.  Each turn, you may roll or stay.
+       <ul>
+               <li> If you roll a 1, you lose all points for your turn and play passes to
+                       your opponent.  Any other roll adds its value to your turn score.  </li>
+               <li> If you stay, your turn score is added to your total score, and play passes
+                       to your opponent.  </li>
+       </ul>
+       
+       The first person to reach 100 total points wins.
+       <br/><br/>
+
+       The <code>score</code> type stores the scores of the current and opposing
+       players, in addition to the points accumulated during the current turn.
+</step>
+
+<step title="User-defined function types" src="doc/codewalk/pig.go:/\/\/ An action/,/bool\)/">
+       In Go, functions can be passed around just like any other value. A function's
+       type signature describes the types of its arguments and return values.
+       <br/><br/>
+
+       The <code>action</code> type is a function that takes a <code>score</code>
+       and returns the resulting <code>score</code> and whether the current turn is
+       over.
+       <br/><br/>
+
+  If the turn is over, the <code>player</code> and <code>opponent</code> fields
+  in the resulting <code>score</code> should be swapped, as it is now the other player's
+  turn.
+</step>
+
+<step title="Multiple return values" src="doc/codewalk/pig.go:/\/\/ roll returns/,/stay.*true\n}/">
+       Go functions can return multiple values.  
+       <br/><br/>
+
+       The functions <code>roll</code> and <code>stay</code> each return a pair of
+       values.  They also match the <code>action</code> type signature.  These
+       <code>action</code> functions define the rules of Pig.
+</step>
+
+<step title="Higher-order functions" src="doc/codewalk/pig.go:/\/\/ A strategy/,/action\n/">
+       A function can use other functions as arguments and return values.
+       <br/><br/>
+
+  A <code>strategy</code> is a function that takes a <code>score</code> as input
+  and returns an <code>action</code> to perform.  <br/>
+  (Remember, an <code>action</code> is itself a function.)
+</step>
+
+<step title="Function literals and closures" src="doc/codewalk/pig.go:/return func/,/return roll\n\t}/">
+       Anonymous functions can be declared in Go, as in this example.  Function
+       literals are closures: they inherit the scope of the function in which they
+       are declared.
+       <br/><br/>
+
+       One basic strategy in Pig is to continue rolling until you have accumulated at
+       least k points in a turn, and then stay.  The argument <code>k</code> is
+       enclosed by this function literal, which matches the <code>strategy</code> type
+       signature.
+</step>
+
+<step title="Simulating games" src="doc/codewalk/pig.go:/\/\/ play/,/currentPlayer\n}/">
+  We simulate a game of Pig by calling an <code>action</code> to update the
+  <code>score</code> until one player reaches 100 points.  Each
+  <code>action</code> is selected by calling the <code>strategy</code> function
+  associated with the current player.
+</step>
+
+<step title="Comparing functions" src="doc/codewalk/pig.go:/if action/,/currentPlayer\)\)\n\t\t}/">
+       Functions can be compared for equality in Go.  From the 
+       <a href="http://golang.org/doc/go_spec.html#Comparison_operators">language specification</a>:
+  Function values are equal if they refer to the same function or if both are <code>nil</code>.
+       <br/><br/>
+
+  We enforce that a <code>strategy</code> function can only return a legal
+  <code>action</code>: either <code>roll</code> or <code>stay</code>.
+</step>
+
+<step title="Simulating a tournament" src="doc/codewalk/pig.go:/\/\/ roundRobin/,/gamesPerStrategy\n}/">
+       The <code>roundRobin</code> function simulates a tournament and tallies wins.
+       Each strategy plays each other strategy <code>gamesPerSeries</code> times.
+</step>
+       
+<step title="Variadic function declarations" src="doc/codewalk/pig.go:/\/\/ ratioS/,/string {/">
+       Variadic functions like <code>ratioString</code> take a variable number of
+       arguments.  These arguments are available as a slice inside the function.
+</step>
+
+<step title="Simulation results" src="doc/codewalk/pig.go:/func main/,/\n}/">
+       The <code>main</code> function defines 100 basic strategies, simulates a round
+       robin tournament, and then prints the win/loss record of each strategy.
+       <br/><br/>
+
+       Among these strategies, staying at 25 is best, but the <a
+       href="http://www.google.com/search?q=optimal+play+pig">optimal strategy for
+       Pig</a> is much more complex.
+</step>
+
+</codewalk>
diff --git a/doc/codewalk/pig.go b/doc/codewalk/pig.go
new file mode 100644 (file)
index 0000000..9e415f5
--- /dev/null
@@ -0,0 +1,124 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+       "fmt"
+       "rand"
+)
+
+const (
+       win            = 100 // The winning score in a game of Pig
+       gamesPerSeries = 10  // The number of games per series to simulate
+)
+
+// A score includes scores accumulated in previous turns for each player,
+// as well as the points scored by the current player in this turn.
+type score struct {
+       player, opponent, thisTurn int
+}
+
+// An action transitions stochastically to a resulting score.
+type action func(current score) (result score, turnIsOver bool)
+
+// roll returns the (result, turnIsOver) outcome of simulating a die roll. 
+// If the roll value is 1, then thisTurn score is abandoned, and the players'
+// roles swap.  Otherwise, the roll value is added to thisTurn.
+func roll(s score) (score, bool) {
+       outcome := rand.Intn(6) + 1 // A random int in [1, 6]
+       if outcome == 1 {
+               return score{s.opponent, s.player, 0}, true
+       }
+       return score{s.player, s.opponent, outcome + s.thisTurn}, false
+}
+
+// stay returns the (result, turnIsOver) outcome of staying.
+// thisTurn score is added to the player's score, and the players' roles swap.
+func stay(s score) (score, bool) {
+       return score{s.opponent, s.player + s.thisTurn, 0}, true
+}
+
+// A strategy chooses an action for any given score.
+type strategy func(score) action
+
+// stayAtK returns a strategy that rolls until thisTurn is at least k, then stays.
+func stayAtK(k int) strategy {
+       return func(s score) action {
+               if s.thisTurn >= k {
+                       return stay
+               }
+               return roll
+       }
+}
+
+// play simulates a Pig game and returns the winner (0 or 1).
+func play(strategy0, strategy1 strategy) int {
+       strategies := []strategy{strategy0, strategy1}
+       var s score
+       var turnIsOver bool
+       currentPlayer := rand.Intn(2) // Randomly decide who plays first
+       for s.player+s.thisTurn < win {
+               action := strategies[currentPlayer](s)
+               if action != roll && action != stay {
+                       panic(fmt.Sprintf("Player %d is cheating", currentPlayer))
+               }
+               s, turnIsOver = action(s)
+               if turnIsOver {
+                       currentPlayer = (currentPlayer + 1) % 2
+               }
+       }
+       return currentPlayer
+}
+
+// roundRobin simulates a series of games between every pair of strategies.
+func roundRobin(strategies []strategy) ([]int, int) {
+       wins := make([]int, len(strategies))
+       for i := 0; i < len(strategies); i++ {
+               for j := i + 1; j < len(strategies); j++ {
+                       for k := 0; k < gamesPerSeries; k++ {
+                               winner := play(strategies[i], strategies[j])
+                               if winner == 0 {
+                                       wins[i]++
+                               } else {
+                                       wins[j]++
+                               }
+                       }
+               }
+       }
+       gamesPerStrategy := gamesPerSeries * (len(strategies) - 1) // no self play
+       return wins, gamesPerStrategy
+}
+
+// ratioString takes a list of integer values and returns a string that lists
+// each value and its percentage of the sum of all values.
+// e.g., ratios(1, 2, 3) = "1/6 (16.7%), 2/6 (33.3%), 3/6 (50.0%)"
+func ratioString(vals ...int) string {
+       total := 0
+       for _, val := range vals {
+               total += val
+       }
+       s := ""
+       for _, val := range vals {
+               if s != "" {
+                       s += ", "
+               }
+               pct := 100 * float64(val) / float64(total)
+               s += fmt.Sprintf("%d/%d (%0.1f%%)", val, total, pct)
+       }
+       return s
+}
+
+func main() {
+       strategies := make([]strategy, win)
+       for k := range strategies {
+               strategies[k] = stayAtK(k + 1)
+       }
+       wins, games := roundRobin(strategies)
+
+       for k := range strategies {
+               fmt.Printf("Wins, losses staying at k =% 4d: %s\n",
+                       k+1, ratioString(wins[k], games-wins[k]))
+       }
+}