Post

Harnessing power of multi-paradigm

Modern languages such as fsharp, swift, rust and Kotlin supports multiple programming paradigms, allowing developers to leverage the strengths of different approaches.

Rust: combines imperative, functional, and object-oriented paradigms.
Swift: imperative, object-oriented programming paradigms and incorporates functional programming features.
Kotlin: object-oriented and functional programming paradigms

Please note each languages varies at degree at which they support given paradigm and might have their own nuances.

Programming paradigms

  1. Declarative
    1. Functional
    2. Logic
    3. Reactive
  2. Imperative
    1. Procedural
    2. Object Oriented

Declarative paradigm focuses on what rather than “how”. This one thing along bring ton of benefits on table. This become even more powerful when fused with

Imperative coding style focuses on “how” rather than “what”.

  • Offers greater controls over each steps
  • Less indirections

Let’s take a simple example in Kotlin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class BirthdayNotifier(
  private val contactService: ContactService,
  private val notificationService: NotificationService,
  private val metricService: MetricService
) {

  fun notify(user: UUID) {
    val birthdayBuddies = contactService.findFriendsOf(user)
      .filter { contact -> contact.birthDate?.isToday() ?: false }
      .also { logger.info { "${it.count()} people from your contact have Birthday today" } }
      .toList()

    var failedToNotify = 0

    birthdayBuddies.forEach { contact ->
      try {
        val isAlreadyNotified = notificationService
          .findNotification(contact)
          .filterNot { notification ->
            notification.time.toLocalDate().isToday()
              && (notification.notification is BirthdayNotification)
          }
          .any()

        if (isAlreadyNotified) logger.info { "Already notified" }
        else {
          val notification = BirthdayNotification.from(contact)
          notificationService.notify(notification)
          logger.info { "Notified about birthday" }
        }

      } catch (ex: Throwable) {
        logger.error(ex) { "Failed to notify contact $contact" }
        failedToNotify += 1
      }
    }
    if (failedToNotify > 0) metricService.measureCount("birthday.nofitication.failed.count", failedToNotify)
  }
}

The author of code naturally gravitates towards “how to do things” rather than “what are we doing here” during writing phase, where as reader always seek for “what are we doing here” and want to form mental model of the system. Once we understand objectives of Authors and Readers, we can address needs accordingly. We can use declarative style when what is more important than how. When it’s about doing one thing and main question is about how rather than what, then go for imperative style with and keep focus on efficiency.

Now lets look at above example by zooming out a little bit, we’ll see we’re

  • Finding buddies who have birthday and which aren’t yet notified yet
  • Once we found such buddies we’re notifying them
  • Lastly reporting failed to notified count

But above code does not look or speak this clear. Let’s write code at this level of abstraction and see how it looks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// other code

fun notify(user: UUID) {
    birthdayBuddiesOf(user)
      .filterNot(::alreadyNotified)
      .map(::BirthdayNotification)
      .map(::sendNotification)
      .let{ notificationResults -> notificationResults.count { it.isFailure } }
      .also(::recordMetric)
  }

  private fun birthdayBuddiesOf(userId: UUID): List<Contact> =
    contactService.findFriendsOf(userId)
      .filter { contact -> contact.birthDate?.isToday() ?: false }
      .also { logger.info { "${it.count()} people from your contact have Birthday today" } }
      .toList()

  private fun alreadyNotified(contact: Contact): Boolean =
    notificationService
      .findNotification(contact)
      .filter { notification ->
        notification.time.toLocalDate().isToday()
        && (notification.notification is BirthdayNotification)
      }
      .any()
      .also { alreadyNotified -> if (alreadyNotified) logger.info { "Already notified" } }

  private fun recordMetric(failedCount: Int) {
    if (failedCount > 0) metricService.measureCount("birthday.nofitication.failed.count", failedCount)
  }

  private fun sendNotification(notification: BirthdayNotification): Result<Unit> =
    runCatching {
      notificationService.notify(notification)
      logger.info { "Notified about birthday" }
    }

  companion object {
    private fun LocalDate.isToday(): Boolean = this == LocalDate.now()
  }
// other code down here.

To summarise, mixing different paradigms can lead to better code. Here we’re mixing Object oriented and functional paradigm together to make code more readable.

This post is licensed under CC BY 4.0 by the author.