Skip to content

Instantly share code, notes, and snippets.

@JoolsF
Last active June 2, 2022 21:20
Show Gist options
  • Save JoolsF/089952d28ae42bbd42b89370831dcd40 to your computer and use it in GitHub Desktop.
Save JoolsF/089952d28ae42bbd42b89370831dcd40 to your computer and use it in GitHub Desktop.
Monoids practical real-world example
/*
* Modelling business rules using Monoids
* https://medium.com/@shashankbaravani/understanding-monoids-using-real-life-examples-6ec3cb349f2f
*
* Creating business rules, in this case filters, in a wholly composable way such that they can be combined
* in arbitrary orders given they fit the definition of Monoids and are associative.
*
* The crux of the pattern is the List[Cab] => List[Cab] 'Filter' type which allows the composition.
*/
object CoolTaxi {
class User(val firstName: String, val lastName: String, val custType: String)
class Location(val latitue: Double = 0.00, val longitude: Double = 0.00)
class Cab(val typ: String, val totalSeats: Int, val seatsAvlbl: Int,
val currentLocation: Location, val finalDestination: Location) {
def distanceFromHere(here: Location): Double = 0.00
def timeToDrop(here: Location): Long = 0
def isOnPromotion: Boolean = false
}
class Source() extends Location
class Destination() extends Location
//encapsulated a ride request
class RideRequest(val source: Source, val destination: Destination, val service: String, val seats: Int)
// represents a filter function that take a collection of cabs, filters and returns the result
type Filter = List[Cab] => List[Cab]
/* a simple filter to filter cabs based on source and destination, */
def matchRoutes(source: Source, destination: Destination): Filter = {
cabs: List[Cab] => cabs.filter(c => c.finalDestination == destination && isWithinRange(c.currentLocation, source))
}
def isWithinRange(cabLoc: Location, myLoc: Location) = true
/* a simple filter to filter cabs based on service type - pool, premier, etc, */
def matchClass(typName: String): Filter = {
cabs: List[Cab] => cabs.filter(_.typ.equalsIgnoreCase(typName))
}
/* a simple filter to filter cabs based on some kind of promotion */
def matchPromotionCabs: Filter = {
cabs: List[Cab] => cabs.filter(_.isOnPromotion)
}
/* a long laborious process of matching the best cabs based on multitude of factors
* such as driver ratings, user ratings, co-passenger ratings, traffic conditions, etc
* */
def matchSeats(numOfSeats: Int): Filter = {
cabs: List[Cab] => cabs.filter(_.seatsAvlbl == numOfSeats)
}
def matchDistanceToPickUp(here: Location, dis: Double): Filter = {
cabs: List[Cab] => cabs.filter(_.distanceFromHere(here) == dis)
}
def matchTimeToDrop(here: Location, time: Int): Filter = {
cabs: List[Cab] => cabs.filter(_.timeToDrop(here) <= time)
}
}
object CabService{
import CoolTaxi._
val MAX_DIST = 4.00
val MAX_TIME_WAIT = 60
/* predfined recipe for implementing abstract notion of convinience; */
def matchConvinience(location: Location): Filter = matchDistanceToPickUp(location, MAX_DIST) andThen matchTimeToDrop(location, MAX_TIME_WAIT)
/* more coarse grained filters have been defined based on underlying ones; */
def matchBasic(start:Source, end: Destination): Filter = matchRoutes(start, end) andThen matchClass("basic")
def matchPool(start:Source, end: Destination)(seats: Int): Filter = matchRoutes(start, end) andThen matchClass("pool") andThen matchSeats(seats)
def matchPremium(start:Source, end: Destination): Filter = matchRoutes(start, end) andThen matchClass("premium")
/* more abtstract constructs defined by business needs however the client is not aware of
how these constructs are composed to generate final outcomes */
def matchMyDailyCommute(start:Source, end: Destination): Filter = matchPool(start, end)(1) andThen matchConvinience(start)
def matchOffersOnWheels(start:Source, end: Destination): Filter = matchPool(start, end)(1) andThen matchPromotionCabs
}
object SmartOccupancyManager {
import CoolTaxi._
import CabService._
import CabService.{matchMyDailyCommute, matchOffersOnWheels}
import CoolTaxi.RideRequest
/* a non trivial process of fetching cabs from, say, in memory DB based on source and destination */
val cabsFromDB = List[Cab]()
def getCabs(start:Source, end: Destination, user: User, request: RideRequest): List[Cab] = {
(request.service, request.seats) match {
case ("ShareMyCab", seats) => matchPool(start, end)(seats)(cabsFromDB)
case ("OffersOnWheels", _) => matchOffersOnWheels(start, end)(cabsFromDB)
case ("MyDailyCommute", _) => matchMyDailyCommute(start, end)(cabsFromDB)
case _ => cabsFromDB
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment