Learning Outcome 11th - 18th Dec 2020

Introduction

In this talk by Rafal Ditwald on Solving problem the Closure way, he attempts to distill the particular blend of functional and data-driven programming that makes up "idiomatic Clojure", clarify what it looks like in practice (with real-world examples), and reflect on how Clojure's conventions came to be and how they continue to evolve. I have captured and aligned what I learned from his session into refactoring my TicTacToe game to reflect this approach.

What is Functional Programming essentially about?

Avoids using state, pass functions around, thinks of the program as input/output(what is the data we are expecting and how do we transform that data)

Functional programming avoids the mutating state by

  1. Minimizing _ Less state, less value to keep around, Lets only have little parts in code that change values, reduce the number of places we do side effects. How do we minimize?

  • Derive values - states that you want to keep, but you can actually avoid it eg: keeping track of whose player’s turn it is in a Tic Tac Toe game when you can get that by determining how many grids(cell) are taking  

  • Copy instead of Mutate-in-place -  E.g you can derive a new array from a previous array and perform an operation on the new array without affecting the previous array

  • Using Anonymous functions: Create functions dynamically that will remember values in their scope. Like Lambdas and higher-order functions that recursively handle an operation under the hood. 

In my TicTacToe Board class, I minimised with the `hasWinner` function where I separated concerns(rows, columns and diagonals). From my previous implementation we were overly mutating state. this is what

Screenshot 2021-01-01 at 18.32.40.png

What my hasWinner(), checkRowWin(), checkColumnWin() and checkDiagonalWin() functions previously looked like

Screenshot 2021-01-01 at 18.32.13.png

versus What they now look like after minimizing by adopting the - Copy instead of Mutate

2. Concentrate_ if we have a value, let’s keep them in one place rather than throughout the program, If we need to have mutations, let’s keep them together rather than spread throughout the program, concentrate on the places we do side effects.

I concentrated by extracting the inbuilt readLine interface from the askUserForMove() and askToPlayAgain() (now askToRestartGame()) into a CommandLineIO class. With that I now have one source of truth for CLI interaction I can re-use.

Screenshot 2021-01-01 at 18.50.43.png

I was previously creating the readLine.create Interface in both the askUserForMove() and the askToPlayAgain(), thereby duplicating, since I am trying to achieve the same thing which is to get the user’s input from the CLI by invoking the question() function on the interface variable readCLI

Screenshot 2021-01-01 at 18.49.49.png

Here you will see that I have abstracted the readLine interface from the both functions above and only creates the interface inside the getUserInput() function in the CommandLineIO class

Screenshot 2021-01-01 at 18.50.04.png

Here I am now calling the getUserInput() in both the askUserForMove() and askToRestartGame() function which provides for me the users input.

3. Defer _ Defer mutating states or side effects to possibly the last step of the program, or to a completely separate system

Screenshot 2021-01-01 at 19.21.19.png

I think I deferred mutating out state by mocking out the console interface in our test with the MockIO. Instead, I mimicked the behaviour of the game state for valid and invalid scenarios.

Screenshot 2021-01-01 at 19.33.16.png

Here I am passing the MyIOMock class into the ConsoleInteraction class instead of the main CommandLineIO class where I created the readLine Interface that does the interaction with the CLI, to avoid bocking when I run my test, and now testing the behaviour of the getUserInput() function in the MyIOMock class instead of the getUserInput() function in the main ConsoleInteraction class.

Bonus:
A. I also minimized with the print board(now constructBoard()) by making a copy of the 2D board returned by the rows() function and constructing a new flat board representation with strings 1 - 9 which is been rendered in the console.

Screenshot 2021-01-01 at 19.46.07.png
Screenshot 2021-01-01 at 19.52.03.png

I also deferred side effects when we pulled out all console.log() from my playGame() function and handed the responsibility to the show() in the display class, which calls the log() in the CommandLineIO class that prints values to the CLI

Screenshot 2021-01-01 at 19.54.46.png
Screenshot 2021-01-01 at 19.54.27.png

What are some pros of the Functional Programming approach?

  1. Pure functions are good and Functional Programming is the ability to program with as many pure functions as possible and figuring out the details

  2. Pure functions are easy to test

  3. Pure functions are easy to understand

  4. Pure functions are easy to use

What makes pure functions easy to understand, test, and use?

Well I will simply say because they do not have side-effects, they always return the same result giving by the parameter passed and they don't depend on or modify state outside their scope.

Data-Driven Programming

What is Data-driven programming?

  • Data first approach 

  • It is better to have a common simple way of transferring data around your system rather than typing it all

  • Configuration driving development - Store your code as data and 

    So one will as what does data first mean? It simply means that the data itself controls the behavior of the program and not the program logic

More Explanation into the IO interface

It mocks a getUserInput() function which returns a promise of type string and the log() which is a void function the logs to the CLI

I also created a Class CommandLineIO that implements the IO interface, in the CommandLineIO, I implement an asynchronous getUserInput() function which wraps the inbuilt javascript readLine IO, that interacts with the CLI. I now have a generic interface that can have different implementations in our askUserForMove() Function and any other function that needs to get user input from the CLI.

Notice I have a created sleep function on line 4, this function returns a promise giving a certain timeout range, I adopted this approach because Nodejs is non-blocking, and by awaiting the sleep function on line 20 will cause the readLine stdin to wait for the user’s input, until the value of answer is defined. If the waitTimeInMin elapse without the promise resolving, the while loop invokes the sleep function again, and refreshes the time, again, and again until the promise is resolved.

In the async askUserForMove() function, I awaited for the user input which is returned from the getUserInput() when the promise resolves, convert it to a number, do a check to make sure is valid and then return a promise of type number. If it is not a number, a recursion takes effect on line 33, and this will invoke the getUserInput() function again and prompt the user for another input

In my test, I am now mocking the CommandineIO class by creating a MyIOMock class that also implements the IO interface and stubbing the behaviour of the getUserInput(), which represents the main function we want to test inside the CommandLineIO class

Challenges

Screenshot 2021-01-04 at 08.25.15.png

I had a challenge constructing how I wanted my game board to appear in the CLI, this was what I initially had, I was almost there but this is flawed because every time I make a move on the board, it gets overridden by the counter because I was explicitly overriding the positions of the board with the value of the counter each time I called the printboard().

This function also has side effects (console.log()) also not testable tricky to test, giving that i was returning the counter instead of the new array of result

Screenshot 2021-01-04 at 08.23.15.png

After refactoring the logic and better naming the function to what it actually does. It works and makes more sense with what I was trying to achieve.

Notice the conditional on line 18 to 22. now I am checking that only positions that are empty or have the default board state, should be assigned the value from the counter at any giving iteration. This worked perfectly.

Screenshot 2021-01-04 at 08.23.39.png

A glimpse of how my board looks like in its default state.

For my askToPlayAgain() function, I had added 2 test case scenarios to test for a valid and invalid response, however, the test seems un-useful since the response the function returns is needed as an argument in the playAgain() function.

Screenshot 2021-01-01 at 21.26.20.png

I previously had a function I named playAgain() which was using the value returned from askToPlayAgain()(now askUserFroMove()) function. I had challenges figuring out how to test these functions because the functions previously did multiple things and had side effects

  1. It converts the user input to uppercase

  2. It does a check for a valid symbol of Y

Screenshot 2021-01-01 at 21.39.02.png

3. It was previously been invoked to restart the game at the end of every win or draw if the symbol provided is Y otherwise, return nothing to break the loop

Screenshot 2021-01-01 at 21.43.02.png

I also had issues in passing a dynamic message variable in the readLine.question(), I decided to write a condition that checks if the available counts in the board are greater than zero, this means the game is still ongoing, then show the askPosition() message, otherwise if there are no more available positions on the board, show the playAgain() message. I now passed a variable message as the first argument into the function. This didn't work because I was doing a check against a new board instance that has a different board state in the playGame() function.

I figured out some of these challenges with the help of my mentor's feedback and some over our sync while pair programming.

Dependency Injection

Dependency injection is basically providing the objects that an object needs (its dependencies) instead of having it construct them itself. It's a very useful technique for testing since it allows dependencies to be mocked or stubbed out.

Screenshot 2021-01-01 at 21.50.54.png

A typical example will be in my game class. I injected objects of the board, display and message class into its constructor

Dependency Inversion

A class should depend on abstractions (e.g interface, abstract classes), not specific details (implementations).

Screenshot 2021-01-01 at 22.00.40.png

A typical example will be in my ConsoleInteraction class which implements the contracts of the display interface.

What the display interface looks like

Cohesion and Coupling 

  • Cohesion is a way of describing how closely the activities within a single module are related to each other.

  • Coupling is used to refer to the degree of interdependence among the different parts of a system.

Screenshot 2021-01-01 at 22.08.40.png

A typical example will be in my Board class, where I have only functions that related to one another in terms of operations on the game board, no function relating to the console is found in this module.

The degree of interdependency(coupling) amongst various parts of the system can be seen in the AppRunner class, where different modules that make up a complete game function is brought together in a parent module

Law of Demeter

The Law states that a module should not have the knowledge of the inner details of the objects it manipulates. In other words, a software component or an object should not have the knowledge of the internal working of other objects or components.

Previously, I had the readLine interface that does studin and studout in the askUserForMove() and askToPlayAgain()functions in the CommandLineInteraction class.

Screenshot 2021-01-01 at 21.09.01.png

A typical example where the Law of Demeter was practiced was by refactoring the askUserForMove() in the CommandLineInteraction class and pulling out the readline interface into its separate CommandLineIO class

Screenshot 2021-01-01 at 22.27.22.png

Thereby, giving the askUserForMove() function in the CommandLineInteraction class, least or no knowledge of the internal working of the getUserInput() function in the CommandIO class, all it cares about is the users input been returned and not how the operation is been performed.

Previous
Previous

Learning Outcome 15th January 2020

Next
Next

Learning Outcome