By Karran Besen
Translation: Crazy geek
Original text: cheesecakelabs.com/blog/functi…
Reproduced without permission
The functional programming paradigm has been hot for some time, and there are many wonderful books and articles about it on the Internet, but it’s not easy to find real-world examples of programs. So I decided to try out Javascript (the most popular programming language today) and create a game that followed its concepts. In this article, I’ll share some of my experiences and tell you if it’s worth it.
What is functional programming?
In short, functional programming (FP) is a paradigm that attempts to reproduce the concept of mathematical functions as relationships between sets of fields (efficient inputs) and co-domains (efficient outputs). The output of a mathematical function is always associated with only one input, so as long as the same input is used to evaluate the mathematical function, it will return the same output. This is one of the most important concepts of functional programming, also known as determinism.
Examples of indeterminate functions:
let x = 1
const nonDeterministicAdd = y= > x + y
nonDeterministicAdd(2) / / 3
x = 2
nonDeterministicAdd(2) / / 4
Copy the code
Examples of deterministic functions:
const deterministicAdd = (x, y) = > x + y
deterministicAdd(1.2) / / 3
Copy the code
In addition to determinism, functions in FP seek not to cause modifications beyond their scope. These types of functionality are called pure. Last but not least, the data in FP must be immutable, meaning that its value cannot be changed after creation. These concepts make testing, caching, and parallelism easier.
In addition to these basic concepts, I also tried to use a point-free style during game development, which makes the code simpler because it eliminates unnecessary parameters and the use of parameters. The following two links provide a good reference for you.
- Jrsinclair.com/articles/20…
- www.freecodecamp.org/news/how-po…
This project is a game that runs in a browser. Because Javascript (JS) is a language I’m familiar with and is a multi-paradigm language, I chose it as the project language.
I recommend two excellent books on FP:
- Functional Light JS
- Mostly adequate guide to FP
project
Our project is a turn-based spaceship game. In the game, each player has three ships, and each turn must choose where they want to move their ships within their reach and in which direction they want to shoot. When the ship is shot, it will lose part of its shield. If the spaceship does not have a shield it will be destroyed and the player who loses all the spaceship will lose the match.
So far, the game has only allowed one player to participate and control the three spaceships at the top of the screen against a script controlling the three spaceships at the bottom, which randomizes the positions and targets of its spaceships. For the graphics part I used the PixiJS package to control rendering, which was the only dependency of the project, and I also used spaceship Sprites freely available from Hype Studio on the OpenGameart website.
Basic and auxiliary functions
To start, we create a file that contains the basic functions that will be used in almost any project file. Some of these basic functions are native to JS, such as Map and Reduce. JS has several other features that fit the FP paradigm by not changing the input values and have been used in projects, such as filter, find, some, every. A good source for discovering these features is Does it Mutate. To follow the point-free style, the following basic functions must also be implemented:
- Curry: Allows a function to receive its arguments at a separate time
const add = curry((x, y) = > x + y)
add(1.2) / / 3
add(1) (2) / / 3
Copy the code
- Compose: The function is passed as an argument and executed in the reverse order. Each function consumes the return value of the previous function.
const addAndIncrement = compose(
add(1), // previous add result + 1
add // arg1 + arg2
)
addAndIncrement(2.2) / / 5
Copy the code
Several libraries have implemented these functions in them, such as Ramda, but in this project I decided to implement them to try to better understand how they work. This article (medium.com/dailyjs/fun…) Is an important source of information on how they work and how to achieve these functions recursively.
To simplify the composition of the native JS functions I use, I use Curry to create a helper where entries are passed as arguments.
Ex. :
const filter = curry((fn, array) = > array.filter(fn))
const getAliveSpaceships =
compose(
filter(isAlive),
getSpaceships
Copy the code
How do we declare the model?
For the implementation of the model, we use functional-shared style, where the model instance is an object with its properties and functions. To manage the state of the model, we create the following helper, where getState returns the state of the instance. AssignState returns a new instance, the old state is concatenated with the new instance, and getProp returns the value of the transfer property encapsulated in Monad. Monad is a kind of popular in the functional structure, and it is difficult to summarize the definition of an introduction, this article made a very good explanation to its: jrsinclair.com/articles/20…
const modelFunctions = (model, state) = > ({
getState: (a)= > state,
assignState: newProps= >model({ ... state, ... newProps }),getProp: name= > getProp(state, name),
})
Copy the code
Using this helper, we can declare the model, create instances, and use its functions, as follows:
const Engine = state= > ({ ...modelFunctions(Engine, state) })
Engine({ a: 'a' }).assignState({ b: 'b' }).getState() // { a: 'a', b: 'b' }
Copy the code
Implement the rest
After defining the basic functions and templates, there is still a lot of work to do. Here are some other functions of the project, which are quite readable.
- Remove the player’s destroyed ship
const removeDestroyedSpaceships = player= > compose(
setSpaceships(player),
getAliveSpaceships
)(player)
Copy the code
- Reduce the ship’s shroud
export const reduceShield = curry((spaceship, damage) = >
compose(
checkDestroyed,
shield => assignState({ shield }, spaceship),
shield => sub(shield, damage),
getShield
)(spaceship)
)
Copy the code
Code implemented through composition is generally easier to understand than imperative programming. For example, I used SonarQube to analyze the cognitive complexity of this function and got the highest score.
- Get the ship’s bullets
export const getBullets = compose(
either([]),
getProp('bullets'))Copy the code
You can omit the function argument here because it is used only by composite functions. You can also guarantee that the returned value will be valid, because getProp returns a MONAD, while Either returns a wrapped value of MonAD if it is valid or an empty array.
- Sets a new position for the bullet
const setPosition = curry((coordinate, bullet) = >
compose(
callListenerIfExist('onMove'),
assignState({ coordinate })
)(bullet)
)
Copy the code
The composition of functional programming requires that functions always have a return value. If callListenerIfExist does not return any value, you will not be able to link other functions with other functions or setPosition after execution.
Is it worth it?
This is the github repository for the project and hosted here. Because I haven’t used… Size of PixiJS module.