Episode 8: Rotation and random new pieces
In Episode 8 (60min) I implement the rotation of the current piece. That worked out so fast that I also looked at generating random pieces instead of the hardcoded one when a new piece has to be created.
Check out the episode8 branch or the commit to see the changes I made during that episode.
Rotation
Adding the functionality to rotate the current piece on keypress was done much quicker than I anticipated. Thanks to Shauns implementation I didn’t need to figure out the actual calculations.
Random pieces with Commands
The second addition randomizes the selection of the next piece when the current piece can’t be dropped any further. For that, I had to make use of a concept that I had previously avoided: Commands.
They play a significant role in any real-life Elm application, so I want to summarize again what they are good for.
Commands are kind of a logical counterpart to subscriptions, which we looked at in episode 4. With subscriptions we could register our messages with events created outside of our application (timer, global keypress). With commands we trigger events outside of our application. Since our application code needs to be purely functional we need this construct to be able to interact with the outside, non-functional world.
In our Tetris case that’s the generation of random numbers. A function that returns a different result can, by definition not be pure, and consequently not exist/be called in Elm.
Another prime example of commands are HTTP requests. Establishing a TCP connection and waiting for a response cannot be modeled as a pure function.
- Elm can’t guarantee a call of such a function always returns the same result
- The call can take a long time, we don’t want to block the execution (what regular functions will do)
With commands we decouple what would otherwise be one function call into multiple steps:
- Creation of the command
- Reaction to the outcome/result of the execution of the command
With this approach, we avoid our application relying on commitments that Elm cannot guarantee.
To hand over commands that our application creates to the ‘outside world’ Elm offers multiple places.
The most important one is in the return value of the update
function.
update : Msg -> Model -> (Model, Cmd Msg)
When we processing a message we don’t just return a new model but possibly also a command.
These commands can be created with functions like generate
zur Generierung von Zufallszahlen or get
für Http Requests.
Most commands will create some form of event or result we want to process. With Random.generate
we ultimately want to react to the creation of a new random number to use to select the next piece.
For the processing of events we already have the update
function as the established pattern.
Our type Msg
defines the different variants of events that our application can process.
The functions that generate commands know what kind of data they generate. However, they don’t know what kind of messages our application can process. That’s why the Cmd
type has a variable type parameter.
When calling the generate
function we have to pass in a function that turns the data (an Int
) generated by the command into a variant of our Msg
type.
In our Tetris code we call generate
like so:
Random.generate NewCurrentPiece (Random.int 0 <| (length pieceDefinitions - 1))
NewCurrentPiece
has the following singature:
NewCurrentPiece : Int -> Msg
The signature of generate
as indicated in the package docs is the following:
generate : (a -> msg) -> Generator a -> Cmd msg
The lower case identifiers indicate type parameters. That means at the time of writing that function the library author does not know the type this stands for when it is going to be called. But when it is called all instances of one specific type parameter have to refer to the same type.
When we apply the types of our actual function call, we can evaluate to type parameters (a
and msg
) to the following:
generate : (Int -> Msg) -> Generator Int -> Cmd Msg
We won’t write that anywhere in our code, it’s just to demonstrate what the compiler does when we call that function the way we do. It is important to realize that the actual type of that expression resolves to Cmd Msg
(uppercase) because that’s exactly what the update
signature allows/requires.
The processing of the new random number takes place the ‘normal’ way with the NewCurrentPiece
branch of the update
function