Skip to content

Instantly share code, notes, and snippets.

@afsalthaj
Last active July 29, 2018 05:32
Show Gist options
  • Save afsalthaj/c6ce45b4bd717758698520a8cf22804c to your computer and use it in GitHub Desktop.
Save afsalthaj/c6ce45b4bd717758698520a8cf22804c to your computer and use it in GitHub Desktop.

Code Hint (please don't look at this until you solve it yourself)

So, here goes my code snippet thinking. Looking at the problem, it feels like a program should return another program. In fact, in our case we have lots of instances of "program" and we need to return/pass these programs.

So lets go ahead and create a program that should be returned. In our case, the main one is Notification.

{
 sealed trait Notification[A]

 case class SendEmail(s: String) extends Notification[Unit]
 case class SendMessage(s: String) extends Notification[Unit]
 case class NothingJustLog(s: String) extends Notification[Unit]
}

I am yet to worry about whether its a Free Monad or any of those boiler plates. Needn't lift to Free just because it is looking like Free. (Although in real life, yeah lifting becomes useful most of the times)

I have a Table. For the sake of simplicity (or complexity), let's assume table has a function that returns a program under an effect.

private def someInternalPureUnknownService(s: String): Option[Int] = Some(100)

// Consider your favorite F to be F =:= Option
def write(s: String): Option[Notification[Unit]] =
  someOtherService(s).map { t => {
   if (t == 100)
     SendEmail(s)
   else if (t < 100)
     SendMessage(s)
   else
     NothingJustLog(s)
    }
}

// Great. Let me test write Operation. I am so glad, no mocking of notification

write("afsal") === Some(SendEmail("afsal")) // Thats it

I have a real-world interpreter for notification algebra

def notify: Notification ~> scalaz.Id.Id = new (Notification ~> scalaz.Id.Id) {
  def apply[A](fa: Notification[A]): scalaz.Id.Id[A] =
    fa match {
      case SendEmail(k) => println("Sent email to "+ k)
      case SendMessage(k) => println("Sent message to " + k)
      case NothingJustLog(k) => println("Just logging" + k)
    }
}

// Let's test this interpreter

notify(SendEmail("afsal")) prints (go check the inbox!.. 

Well mostly you don't need to test an external library that does the mail/msg/logging .. Anyways)

Ok, now let's actually send a notification based on write operation. It is as easy as !

write("afsal").map(notify)

Please see the Free monad bits of the above solution in here

Advantages

  1. Pure separation of concerns between Table and Notification algebras.
  2. Differed the notification operation during a table operation. Testing Table algebra becomes easy because I can pattern match on the final result and see what it "will" do in future.
  3. Avoided dependency injection of Notification Algebra in the interpreter (implementation) of Table. (considering Table is just an interface). If you passed notification algebra as dependency injection to interpreter of Table, Finally Tagless is better. Finally tagless is better for inheritance and dependency injection.
  4. Separately testing notification.
  5. Depending on the context where SendEmail exists (SendEmail during WriteOperation can be different from SendEmail during ReadOperation)

Code snippet thinking with tagless finally

Now please consider doing exactly the same(without changing the usecase) with Finally tagless. It is possible, but remove the cap of Free monad thinker and solve it using finallytagless!

trait Notification[F[_], A] {
  def sendEmail(s: A): F[Unit]
  def sendMessage(s: A): F[Unit]
  def log(s: A): F[Unit]
}

// Solution 1: Hmmm looks complex
trait SuperFunctionalTable[F[_]] {
  // Write operation writes the user and based on certain logic, decides how to send a notification under an effect F.
  def writeOperation(s: String): F[String => F[Unit]]
}

// Solution 2: Hmm looks a little more simpler
trait FunctionalTable[F[_]] {
  // Write operation writes the user and based on certain logic, decides how to send a notification.
  def writeOpetation(s: String): F[String => Unit]
}

// Solution 3: Way simpler
trait Table[F[_]] {
  // Write operation writes the user and based on certain logic, send the notification. Hurray...
  // Go and check the mail inbox if `writeOperation` worked.
  def writeOperation(s: String):  F[Unit]
}


// Solution 4: Super simpler
trait TableSolo[F[_]] {
  // No i am not sending an email. i will write this one out. I am not concerned at all about notification. But return me the string that is to be passed to the next algebra.
  def writeOperation(s: String):  F[String]
}


trait ValidateAndSendNotification[F[_]] {
  def sendNotification(s: String): Notification[F, String] => String


  // So we got rid of dependency injection through Kleisli like operation (function).
  // This seems to be the best of all solutions in finally tagless.
  // However we still need to test each sink of mock notification to see if was mail, msg, or logging.
  // Oh by the way, We could have a validation layer in Free algebra as well. At this
  // stage I am convinced how easy is my implementation with Free !
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment