Written by Aleksandr Novikov
Generic code is very useful for implementing an algorithm on a set of types. A sorting algorithm may be applied to a list of integers or a list of strings. I hope you know how it is done. I'd like to draw your attention to something different. I'd like to present to you a way of writing business logic with generics. There is an example that is proven to be good for explaining modelling with objects. It's to describe how a car may look like in a program. Here is a generic car.
package my-project.car
case class Car[ENGINE, WHEEL](
engine: ENGINE,
wheels: (WHEEL, WHEEL, WHEEL, WHEEL)
)
Business objects we invent for modeling of the real world are very often unique. So there is no gain of massive code reuse, as the case is with more general types like integers or strings. The objects I have in mind are in a way named stages of a process that some real thing goes through before our eyes . These objects are wrappers of some state. Another state for our car might be when it gets in motion.
package imy-project.movingCar
case class MovingCar[ENGINE_ON, TURNING_WHEEL](
engineOn: ENGINE_ONE,
turningWheels: (TURNING_WHEEL, TURNING_WHEEL, TURNING_WHEEL, TURNING_WHEEL)
)
Now it's possible to alternate between the states of a car. Let's take the steering wheel in our hands and turn on the engine.
package my-project.car
trait Start[CAR, MOVING_CAR]:
def f(car: CAR): MOVING_CAR
extension(car: CAR)
def start: MOVING_CAR = f(car)
object Start:
import my-project.engine.On
import my-project.wheel.Turn
import my-project.factory.MakeMovingCar
given [ENGINE, WHEEL](using
On[ENGINE, ENGINE_ON],
Turn[WHEEL, TURNING_WHEEL],
MakeMovingCar[FACTORY, ENGINE_ON, MOVING_CAR, TURNING_WHEEL]
)(using
factory: FACTORY
): Start[Car[ENGINE, WHEEL], MOVING_CAR] with
override def f(car: Car[Car[ENGINE, WHEEL], MOVING_CAR]): MOVING_CAR =
val engineOn = engine.on
val turningWheels = (
car.wheels(0).turn,
car.wheels(1).turn,
car.wheels(2).turn,
car.wheels(3).turn
)
factory.makeMovingCar(engineOn, turningWheels)
Have you ever seen so much boilerplait code? This is an approach to coding with generic. What benefits justify such a waste of keystrokes, you might ask. The main benefit is straightforward unit-testing with stubs. To create stubs for the four wheels, one would write:
class WheelStub
val wheelStub_1 = new WeelStub
To conjure a stub for the factory, one would add:
class FactoryStub
given FactoryStub = new FactoryStub
Implementing methods for the test is easy. Let's take the engine this time.
given On[EngineStub, EngineOnStub] with
def f(engine: EngineStub): EngineOnStub =
assert(engine.equals(engineStub)
engineOnStub
No fancy mocking needed as you see.
So now you got a taste of using generic programming in business logic. If you got interested, you might want to examine a full-fledged project written in this way.
Here is an attempt to graph manipulation library for you:
Comments