axelerator.de

Episode 8: Rotation and random new pieces

4 minutes
#elm
4 September 2021

Rotation 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:

  1. Creation of the command
  2. 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