Skip to content

Instantly share code, notes, and snippets.

@busypeoples
Last active July 11, 2018 10:23
Show Gist options
  • Save busypeoples/70d06ba2e2aae65bdd4d5ebbb2fe1488 to your computer and use it in GitHub Desktop.
Save busypeoples/70d06ba2e2aae65bdd4d5ebbb2fe1488 to your computer and use it in GitHub Desktop.
Chapter 1: Using Flow

Back To The Basics

Chapter 1: Using Flow

Introduction

Why does it make sense to use FlowType or TypeScript when working with JavaScript? A good approach in answering this question is to build a small game or application to make the benefits clear.

So here it is. Part One. We will build TicTacToe in Javascript. Although it might sound trivial or too simple, it will enable us to see how we can leverage FlowType to make our application more predictable and reliable.

Getting Started

If you don't have Flow installed yet, check the installation guide.

So let's begin with thinking about how we want to structure our game. First of all, we will need a board, remember it's 3x3 fields that we will need. Should we model it as an array containing 3 rows (containing 3 cells each) or as a flat list containing 9 cells?

The are benefits and tradeoffs to both approaches. Modelling it as a flat list gives the advantage to quickly retrieve and update any cell, by accessing the list via indexes. The disadvantage is that we can't be sure that someone might be changing the structure by appending or removing a cell. With the rows approach we gain the ability to model the board to reflect the exact structure. Three rows containing three cells. On the other hand we will need to do more transformation work when accessing or updating a cell.

To make it impossible to define an arbitrary structure, we will take the explicit route.

First of all let's define a Cell type, the smallest unit we will neeed. A Cell can either be a Circle, a Cross or Empty. So let's define all the possible types.

type Circle = {type: 'Circle'}
type Cross = {type: 'Cross'}
type Empty = {type: 'Empty'}

type Cell
  = Circle
  | Cross
  | Empty

Just by looking at the Cell type, we can see what the possible types are.

Now that we have the Cell type defined, we can continue and add Row and Board types.

type Row = [Cell, Cell, Cell]
type Board = [Row, Row, Row]

These type definitions are self explanatory. We can see that a Board consists of three rows.

Now that we have type definitions in place, let's start to build the game. We will use React to render the game to the screen. First off, let's just write a component that renders "TicTacToe".

import React from 'react'
import { render } from 'react-dom'

class TicTacToe extends React.Component<any> {
  render() {
    return <div>TicTacToe</div>
  }
}

render(<TicTacToe />, document.getElementById('root'))

Good. We can start to build up the game as we have an entry point now. One interesting thing to note is that we needed to give Flow a type definition for the React Component class TicTacToe extends React.Component<any>. For now we can pass any to prevent Flow from complaining, but we will refine the definition as we move further in our implementation.

The Board

It's time to build the board. Our TicTacToe component will keep state of the current game status as well as the board. The status includes which players turn it is as well as if the game has ended and if there is a winner. We will need to keep track of this information as the view has to reflect these facts.

So, next let's define a Player type, which we can trivially model as 0 or 1.

type Player = 0 | 1

What's still missing is defining the actual game status, is the game still active, has it ended and do we have a winner. Here's a try at a possible definition:

type Status = Result | {type: 'Running'}

What can Result be? It either contain a winner or no winner at all and the fact that the game has ended. Let's add a Maybe type that can be paramerized.

type Maybe<A> = Just<A> | Nothing

And Just and Maybe can be defined like so:

type Just<A> = {type: 'Just', result: A}
type Nothing = {type: 'Nothing'}

Now we can check the type later in the code and react acordingly.

So our Result can be defined as:

type Result = Maybe<[Player, Row]>

This might look confusing at first, but we're actually defining a tuple containing a Player and a Row. By doing so, we can highlight the winning combination and the player and display these facts on the screen. In case the game has finished but there is no winner, the Nothing type will be the indicator.

Now that we have everything in place, let's render an empty board next. Our TicTacToe component will need an initial state. So let's also define the component's state shape.

type State = {
  board: Board,
  player: Player,
  status: Status
}

We should tell our Compoenent what the state shape is. If we take a look at the Flow documentation, we will notice that we can pass in a prop and a style definition. Component<Props, State>. We don't have any props, so we'll just use a placeholder and define the `State shape.

class TicTacToe extends React.Component<*, State>

So let's add our initial state.

class TicTacToe extends React.Component<*, State> {
  state = {
    board: [],
    status: {type: 'Running'},
    player: 0,
  }
  ...
}

Flow will complain:

board: [],
  ^^ empty array literal. Tuple arity mismatch. This tuple has 0 elements and cannot flow to the 3 elements of
board: Board,
  ^^^^^ tuple type

So let's fix this by defining a row contianing three empty rows.

const empty : Empty = {type: 'Empty'}
const emptyRow : Row = [empty, empty, empty]
const board : Board = [emptyRow, emptyRow, emptyRow]

And updating our initial state accordingly.

class TicTacToe extends React.Component<*, State> {
  state = {
    board: board,
    status: {type: 'Running'},
    player: 0,
  }
  ...
}

So this already great. We finally have defined our initial state and are ready to actually render the board now.

class TicTacToe extends React.Component<*, State> {
  state = {
    board: board,
    status: {type: 'Running'},
    player: 0,
  }
  render() {
    const {board} = this.state
    return <div>{
      board.map((row, i) => {
        return <div style={{width: '600px', height: '150px'}} key={i}>
          {row.map((cell, j) => {
            return <div style={{
              float: 'left',
              textAlign: 'center',
              border: '1px solid #eee',
              padding: '75px'
            }} key={j}>
              cell
            </div>
          })}
        </div>
      })
    }</div>
  }
}

Neglect the styling part for now, but you can see that we map over the board array first and then map over the row array to display the cells. For now we will only render the word 'cell'.

Our next steps will include refactoring the board and cells to their own respective components and we will add interactivity, so player's can take turn and start playing.

Although it seems like a lot of work is involved upfront for definining and displaying a simple 3 x 3 board, we can already guarantee that the board has 3 rows containing 3 cells. Try to define a different shape and Flow will complain.

const board : Board = [emptyRow, emptyRow, null]

Flow tells us that we have a mismatch.

const board : Board = [emptyRow, emptyRow, []]
  ^^ empty array literal. Tuple arity mismatch. This tuple has 0 elements and cannot flow to the 3 elements of
type Board = [Row, Row, Row]
  ^^^ tuple type

Summary

That's it for part one. In the next part we will see more benefits by defining everything upfront.

// @flow
// Example implementation from Chapter One
import React from 'react'
import { render } from 'react-dom'
type Circle = {type: 'Circle'}
type Cross = {type: 'Cross'}
type Empty = {type: 'Empty'}
type Cell
= Circle
| Cross
| Empty
type Row = [Cell, Cell, Cell]
type Board = [Row, Row, Row]
type Player = 0 | 1
type Just<A> = {type: 'Just', result: A}
type Nothing = {type: 'Nothing'}
type Maybe<A> = Just<A> | Nothing
type Result = Maybe<[Player, Row]>
type Status = Result | {type: 'Running'}
type State = {
board: Board,
player: Player,
status: Status
}
const empty : Empty = {type: 'Empty'}
const emptyRow : Row = [empty, empty, empty]
const board : Board = [emptyRow, emptyRow, emptyRow]
class TicTacToe extends React.Component<*, State> {
state = {
board: board,
status: {type: 'Running'},
player: 0,
}
render() {
const {board} = this.state
return <div>{
board.map((row, i) => {
return <div style={{width: '600px', height: '150px'}} key={i}>
{row.map((cell, j) => {
return <div style={{
float: 'left',
textAlign: 'center',
border: '1px solid #eee',
padding: '75px'
}} key={j}>
cell
</div>
})}
</div>
})
}</div>
}
}
render(<TicTacToe />, document.getElementById('root'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment