tictactoe groovy

35
© ASERT 2006-2013 Dr Paul King @paulk_asert Director, ASERT, Brisbane, Australia http:/slideshare.net/paulk_asert/tictactoe-groovy https://github.com/paulk-asert/tictactoe-groovy Coding TicTacToe: A coding style challenge! ** Coming soon!

Upload: paul-king

Post on 22-Apr-2015

16.846 views

Category:

Technology


4 download

DESCRIPTION

Explores how to write a tic-tac-toe API that meets some interesting static typing constraints. Specifically, programs using the API may fail to compile, depending on the state of play in the game, such as trying to call move() with an already completed game board. The real theme of the presentation is not so much solving the tic-tac-toe problem but, rather, pushing static typing to its limits (and some might argue beyond its useful limits—you will have to judge for yourself).

TRANSCRIPT

Page 1: tictactoe groovy

© A

SE

RT

2006-2

013

Dr Paul King @paulk_asert

Director, ASERT, Brisbane, Australia http:/slideshare.net/paulk_asert/tictactoe-groovy

https://github.com/paulk-asert/tictactoe-groovy

Coding TicTacToe:

A coding style

challenge!

** Coming

soon!

Page 2: tictactoe groovy

Topics

Introduction

• Dynamic solution

• Options for increasing type safety

• Groovy challenge solution

• Other language implementations

• Interlude: solving tic tac toe

• Going beyond the challenge

© A

SE

RT

2006-2

013

Page 3: tictactoe groovy

Tic Tac Toe • Players take it in turn to

place “marks” on the

board

• By convention, Player 1

uses “X” and starts

first; Player 2 uses “O”

• The game finishes once

one of the players has

three of their marks in a

row

• Also called noughts

and crosses (and other

names) with 4 x 4, 3D

and other variants

Page 4: tictactoe groovy

Challenge: Tic Tac Toe API • move: given a board and position returns a

board with the current player at that position – Can only be called on a board that is in-play; calling move on a

game board that is finished is a compile-time type error

• whoWon: given a finished board indicates if the

game was a draw or which player won – Can only be called on a board that is finished; calling whoWon

on a game board that is in-play is a compile-time type error

• takeBack: takes either a finished board or a

board in-play that has had at least one move

and returns a board in-play – It is a compile-time type error when used on an empty board

• playerAt: takes any board and position and

returns the (possible) player at the position The main example for this talk is based on and inspired by:

https://github.com/tonymorris/course-answers/blob/master/src/TicTacToe/TicTacToe.md

Page 5: tictactoe groovy

But what’s this talk really about?

• Not really about solving/coding TicTacToe

• Looking at the Dynamic <-> Static typing

spectrum

• Looking at the OO <-> functional style

spectrum

• Looking at the pros and cons of different

programming styles

Page 6: tictactoe groovy

But what’s this talk really about?

• We all have the same goal

– Productively writing “bug-free”

maintainable code that “solves” some

real world users problem

– Types are one trick in my toolkit but I

also have tests, invariants

– Numerous ways to benefit from types

– Types are a burden on the programmer

– When writing DSLs, certain scripts,

certain test code, types may not justify

the burden

Page 7: tictactoe groovy

…DSL example...

© A

SE

RT

2006-2

012

class FluentApi { def action, what def the(what) { this.what = what; this } def of(arg) { action(what(arg)) } } show = { arg -> println arg } square_root = { Math.sqrt(it) } please = { new FluentApi(action: it) } please show the square_root of 100 // => 10.0

DSL usage

Page 8: tictactoe groovy

…DSL example...

© A

SE

RT

2006-2

012

please show the square_root of 100

please(show).the(square_root).of(100)

Inspiration for this example came from …

Page 9: tictactoe groovy

...DSL example

© A

SE

RT

2006-2

012

// Japanese DSL using GEP3 rules Object.metaClass.を = Object.metaClass.の = { clos -> clos(delegate) } まず = { it } 表示する = { println it } 平方根 = { Math.sqrt(it) } まず 100 の 平方根 を 表示する // First, show the square root of 100 // => 10.0

source: http://d.hatena.ne.jp/uehaj/20100919/1284906117

also: http://groovyconsole.appspot.com/edit/241001

Page 10: tictactoe groovy

Topics

• Introduction

Dynamic solution

• Options for increasing type safety

• Groovy challenge solution

• Other language implementations

• Interlude: solving tic tac toe

• Going beyond the challenge

© A

SE

RT

2006-2

013

Page 11: tictactoe groovy

Show me the code

DynamicTTT.groovy

Page 12: tictactoe groovy

Topics

• Introduction

• Dynamic solution

Options for increasing type safety

• Groovy challenge solution

• Other language implementations

• Interlude: solving tic tac toe

• Going beyond the challenge

© A

SE

RT

2006-2

013

Page 13: tictactoe groovy

Java Typing limitations

long freezingC = 0 // 0 °C long boilingF = 212 // 212 °F long delta = boilingF - freezingC long heavy = 100 // 100 kg

Page 14: tictactoe groovy

Using JScience @GrabResolver('http://maven.obiba.org/maven2') @Grab('org.jscience:jscience:4.3.1') import ... //@TypeChecked def main() { Amount<Temperature> freezingC = valueOf(0L, CELSIUS) def boilingF = valueOf(212L, FAHRENHEIT) printDifference(boilingF, freezingC) def heavy = valueOf(100L, KILO(GRAM)) printDifference(heavy, boilingF) } def <T> void printDifference(Amount<T> arg1, Amount<T> arg2) { println arg1 - arg2 }

(180.00000000000006 ± 1.4E-14) °F javax.measure.converter.ConversionException: °F is not compatible with kg

Page 15: tictactoe groovy

Using JScience & @TypeChecked ... @TypeChecked def main() { Amount<Temperature> freezingC = valueOf(0L, CELSIUS) def boilingF = valueOf(212L, FAHRENHEIT) printDifference(boilingF, freezingC) def heavy = valueOf(100L, KILO(GRAM)) printDifference(heavy, boilingF) } def <T> void printDifference(Amount<T> arg1, Amount<T> arg2) { println arg1 - arg2 }

[Static type checking] - Cannot call ConsoleScript46#printDifference(org.jscience.physics.amount.Amount <T>, org.jscience.physics.amount.Amount <T>) with arguments [org.jscience.physics.amount.Amount <javax.measure.quantity.Mass>, org.jscience.physics.amount.Amount <javax.measure.quantity.Temperature>]

Page 16: tictactoe groovy

Mars Orbiter (artists impression)

Page 17: tictactoe groovy

…Typing…

import groovy.transform.TypeChecked import experimental.SprintfTypeCheckingVisitor @TypeChecked(visitor=SprintfTypeCheckingVisitor) void main() { sprintf('%s will turn %d on %tF', 'John', new Date(), 21) }

[Static type checking] - Parameter types didn't match types expected from the format String: For placeholder 2 [%d] expected 'int' but was 'java.util.Date' For placeholder 3 [%tF] expected 'java.util.Date' but was 'int'

sprintf has an Object varargs

parameter, hence not normally

amenable to further static checking

but for constant Strings we can do

better using a custom type checking

plugin.

Page 18: tictactoe groovy

Show me the code

Type safe builder, phantom types, dependent types: Rocket, HList

Page 19: tictactoe groovy

© A

SE

RT

2006-2

012

GContracts

@Grab('org.gcontracts:gcontracts-core:1.2.10') import org.gcontracts.annotations.* @Invariant({ speed >= 0 }) class Rocket { @Requires({ !started }) @Ensures({ started }) def start() { /* ... */ } @Requires({ started }) @Ensures({ old.speed < speed }) def accelerate() { /* ... */ } /* ... */ } def r = new Rocket() r.start() r.accelerate()

Page 20: tictactoe groovy

Topics

• Introduction

• Dynamic solution

• Options for increasing type safety

Groovy challenge solution

• Other language implementations

• Interlude: solving tic tac toe

• Going beyond the challenge

© A

SE

RT

2006-2

013

Page 21: tictactoe groovy

Show me the code

TicTacToe

Page 22: tictactoe groovy

Topics

• Introduction

• Dynamic solution

• Options for increasing type safety

• Groovy challenge solution

Other language implementations

• Interlude: solving tic tac toe

• Going beyond the challenge

© A

SE

RT

2006-2

013

Page 23: tictactoe groovy

Show me the code

Haskell, Scala

Page 24: tictactoe groovy

Topics

• Introduction

• Dynamic solution

• Options for increasing type safety

• Groovy challenge solution

• Other language implementations

Interlude: solving tic tac toe

• Going beyond the challenge

© A

SE

RT

2006-2

013

Page 25: tictactoe groovy

Interlude: Solving Tic Tac Toe • Use a game tree to

map out all possible

moves

• Solve the game tree

using brute force or

with various

optimisation

algorithms

Page 26: tictactoe groovy

Interlude: Tic Tac Toe game tree

Source: http://en.wikipedia.org/wiki/Game_tree

Page 27: tictactoe groovy

Interlude: Tic Tac Toe game tree • Brute force

• Minimax

– Reduced

lookahead, e.g.

2 layers/plies

• Alpha beta pruning

– Improved efficiency

• Iterative deepening

– Often used with

alpha beta pruning

• But for Tic Tac Toe only 100s of

end states and 10s of thousands

of paths to get there

Source: http://en.wikipedia.org/wiki/Game_tree

Page 28: tictactoe groovy

Topics

• Introduction

• Dynamic solution

• Options for increasing type safety

• Groovy challenge solution

• Other language implementations

• Interlude: solving tic tac toe

Going beyond the challenge

© A

SE

RT

2006-2

013

Page 29: tictactoe groovy

Static Type Checking: Pluggable type system…

import groovy.transform.TypeChecked import checker.BoringNameEliminator @TypeChecked(visitor=BoringNameEliminator) class Foo { def method1() { 1 } }

import groovy.transform.TypeChecked import checker.BoringNameEliminator @TypeChecked(visitor=BoringNameEliminator) class Foo { def method() { 1 } }

[Static type checking] - Your method name is boring, I cannot allow it!

Groovy 2.1+

Page 30: tictactoe groovy

…Static Type Checking: Pluggable type system

package checker import org.codehaus.groovy.ast.* import org.codehaus.groovy.control.SourceUnit import org.codehaus.groovy.transform.stc.* class BoringNameEliminator extends StaticTypeCheckingVisitor { BoringNameEliminator(SourceUnit source, ClassNode cn, TypeCheckerPluginFactory pluginFactory) { super(source, cn, pluginFactory) } final message = "Your method name is boring, I cannot allow it!" @Override void visitMethod(MethodNode node) { super.visitMethod(node) if ("method".equals(node.name) || "bar".equals(node.name)) { addStaticTypeError(message, node) } } }

Groovy 2.1+

Page 31: tictactoe groovy

…Typing…

import groovy.transform.TypeChecked import tictactoe.* Import static tictactoe.Position.* @TypeChecked(visitor=TicTacToeTypeVisitor) void main() { Board.empty().move(NW).move(C).move(W).move(SW).move(SE) }

package tictactoe enum Position { NW, N, NE, W, C, E, SW, S, SE } class Board { static Board empty() { new Board() } Board move(Position p) { this } }

Page 32: tictactoe groovy

…Typing… package tictactoe import fj.* import fj.data.List import fj.data.Option import fj.data.TreeMap import static fj.P.p import static fj.data.List.list import static fj.data.List.nil import static fj.data.Option.none import static tictactoe.GameResult.Draw import static tictactoe.Player.Player1 import static tictactoe.Player.toSymbol import static tictactoe.Position.* final class Board extends BoardLike { private final List<P2<Position, Player>> moves private final TreeMap<Position, Player> m private static final Ord<Position> positionOrder = Ord.comparableOrd() private Board(final List<P2<Position, Player>> moves, final TreeMap<Position, Player> m) { this.moves = moves this.m = m } Player whoseTurn() { moves.head()._2().alternate() } boolean isEmpty() { false } List<Position> occupiedPositions() { m.keys() } int nmoves() { m.size() } Option<Player> playerAt(Position pos) { m.get(pos) } TakenBack takeBack() { moves.isEmpty() ? TakenBack.isEmpty() : TakenBack.isBoard(new Board(moves.tail(), m.delete(moves.head()._1()))) } @SuppressWarnings("unchecked") MoveResult moveTo(final Position pos) { final Player wt = whoseTurn() final Option<Player> j = m.get(pos) final TreeMap<Position, Player> mm = m.set(pos, wt) final Board bb = new Board(moves.cons(p(pos, wt)), mm) final List<P3<Position, Position, Position>> wins = list( p(NW, W, SW), p(N, C, S), p(NE, E, SE), p(NW, N, NE), p(W, C, E), p(SW, S, SE), p(NW, C, SE), p(SW, C, NE) ) final boolean isWin = wins.exists(new F<P3<Position, Position, Position>, Boolean>() { public Boolean f(final P3<Position, Position, Position> abc) { return list(abc._1(), abc._2(), abc._3()).mapMOption(mm.get()).exists(new F<List<Player>, Boolean>() { public Boolean f(final List<Player> ps) { return ps.allEqual(Equal.<Player> anyEqual()) } }) } }) final boolean isDraw = Position.positions().forall(new F<Position, Boolean>() { Boolean f(final Position pos2) { mm.contains(pos2) } }) j.isSome() ? MoveResult.positionAlreadyOccupied() : isWin ? MoveResult.gameOver(new FinishedBoard(bb, GameResult.win(wt))) : isDraw ? MoveResult.gameOver(new FinishedBoard(bb, Draw)) : MoveResult.keepPlaying(bb) } // …

// … @Override String toString() { toString(new F2<Option<Player>, Position, Character>() { Character f(final Option<Player> pl, final Position _) { pl.option(p(' '), toSymbol) } }) + "\n[ " + whoseTurn().toString() + " to move ]" } static final class EmptyBoard extends BoardLike { private EmptyBoard() {} @SuppressWarnings("unchecked") Board moveTo(final Position pos) { new Board(list(p(pos, Player1)), TreeMap.<Position, Player> empty(positionOrder).set(pos, Player1)) } private static final EmptyBoard e = new EmptyBoard() static EmptyBoard empty() { e } Player whoseTurn() { Player1 } boolean isEmpty() { true } List<Position> occupiedPositions() { nil() } int nmoves() { 0 } Option<Player> playerAt(Position pos) { none() } } static final class FinishedBoard extends BoardLike { private final Board b private final GameResult r private FinishedBoard(final Board b, final GameResult r) { this.b = b this.r = r } Board takeBack() { b.takeBack().fold( Bottom.<Board> error_("Broken invariant: board in-play with empty move list. This is a program bug"), Function.<Board> identity() ) } Player whoseTurn() { b.whoseTurn() } boolean isEmpty() { false } List<Position> occupiedPositions() { b.occupiedPositions() } int nmoves() { b.nmoves() } Option<Player> playerAt(final Position pos) { b.playerAt(pos) } GameResult result() { r } @Override String toString() { b.toString() + "\n[[" + r.toString() + " ]]" } } }

Page 33: tictactoe groovy

…Typing

import groovy.transform.TypeChecked import tictactoe.* Import static tictactoe.Position.* @TypeChecked(visitor=TicTacToeTypeVisitor) void main() { Board.empty().move(NW).move(C).move(W).move(SW).move(SE) }

package tictactoe enum Position { NW, N, NE, W, C, E, SW, S, SE }

[Static type checking] - Attempt to call suboptimal move SE not allowed [HINT: try NE]

Custom type checker which fails

compilation if programmer attempts

to code a suboptimal solution. Where

suboptimal means doesn’t agree with

what is returned by a minimax,

alpha-beta pruning, iterative

deepening solving engine.

Page 34: tictactoe groovy

Show me the code

Page 35: tictactoe groovy

More Information: Groovy in Action