Suppose I have a class in JS with Typescript like this:
type Command = 'F' | 'B' // Forwards, Backwards class Car { private x: number private y: number constructor(x: number, y: number) { this.x = x this.y = y } private reportPosition() { return `(${this.x}, ${this.y})` } private move(command: Command) { if (command === 'F') this.y++ if (command === 'B') this.y-- } execute(command: Command) { this.move(command) return this.reportPosition() } }
When I create a Car instance and execute the execute
method, two things happen:
- The internal state of the instance (x, y) is updated.
- The
execute
function returns a string.
Now I want to write the same thing in a more FP way, making the functions pure, but I stumble at the execute
function.
My approach is this:
type Command = 'F' | 'B' type Car = { x: number y: number } function createCar(x: number, y: number): Car { return { x, y } } function reportPosition(car: Car) { return `$({car.x}, ${car.y})` } function move(car: Car, command: Command) { if (command === 'F') return createCar(car.x + 1, car.y) if (command === 'B') return createCar(car.x - 1, car.y) return car } function execute(car: Car, command: Command) { const newCar = move(car, command) const msg = reportPosition(newCar) return [newCar, msg] }
My questions are the following:
Since
execute
does two things at once, I feel I am forced to return two values from it in the function. But this feels wrong. Is this “valid” functional programming? Or would I never create such a function in the FP world and just call each of the functions inside (move
,reportPosition
) separately.What if the
move
function also had to return the information on whether the car has crashed after its move? Would I also have to return two values from that modified function: the new car instance and a boolean (indicating a crash)?Also, I used the
createCar
function within themove
function, which is technically not allowed for pure functions, correct? What would be the best way to fix that? Pass thecreateCar
function as an argument tomove
?
Thanks!
Advertisement
Answer
- Doing two things at once doesn’t necessarily make a function invalid functional programming (I think by “valid” you’re referring to pure functional programming). What makes a function “pure” in functional programming is that its return value is only determined by its input values and nothing else. It also does not modify any external state or variables (referred to as “free variables” or global variables, meaning a variable that is not bound in the input parameters). What you’re doing in
execute
can be expressed in a functional programming language trivially, for example Haskell:
execute :: Car -> Command -> (Car, String) execute car cmd = let newCar = move car cmd in (newCar, reportPosition newCar)
If
move
had to report additional data, you could include it in the return type and it would remain pure. However, assuming “if the car crashed” is an error state, then typically this would be modeled by returning a sum type (Maybe
orEither
in Haskell). TakeMaybe
for example:data Maybe a = Just a | Nothing
, if the car crashed you could returnNothing
and if it didn’t then returnJust position
, then anything using themove
function can verify that it didn’t returnNothing
.Why would you not be allowed to call
createCar
insidemove
? Neithermove
norcreateCar
are modifying any external state/variables, both are only using the inputs provided in their returns.
Re-iterating my comment in the main post, a lot of these things from Haskell that I mentioned above (e.g. Maybe
) are available in libraries for JavaScript/TypeScript. For TypeScript in particular, there’s https://github.com/gcanti/fp-ts. It can be a little bit confusing sometimes, as usually there are many names that refer to the same concept. For instance, some libraries refer to Maybe
as Option
.