Connecting...

Pexels Photo 240163

Combine Free Monad Algebra with Scalaz by Afsal Thaj

Pexels Photo 240163

What has been your experience of using Scalaz? Have you ever tried to combine Free Monad algebra using it? 

Software Engineer Afsal Thaj looks at his experience of trying this, check out his coding below.

 

'Here is my gist that combines the various algebra defined in various parts of the application, using Free Monad with in Scalaz! For those who are impatient, jump straight to line number 58, that handles what Scalaz is missing!

1 package com.thaj.functionalprogramming.exercises.part4
2
3 import scalaz.{ Scalaz, _ }
4 import Scalaz._
5
6 // Thanks to https://underscore.io/blog/posts/2017/03/29/free-inject.html
7 object CombineFreeInScalaz {
8   sealed trait Logging[A]
9   case class Info(s: String) extends Logging[Unit]
10
11   sealed trait BridgeExec[A]
12   case class PrintBridge(s: String) extends BridgeExec[Unit]
13
14   // This is just for describing how things work, and doesn't need to defined in scalaz.
15   trait Inject[F[_], G[_]] {
16     def inj[A](fa: F[A]): G[A]
17   }
18
19   // Inject implicits exists in scalaz, pointed out here for better understandability.
20   implicit def injectCoproductLeft[F[_], X[_]]: Inject[F, Coproduct[F, X, ?]] =
21     new Inject[F, Coproduct[F, X, ?]] {
22       def inj[A](fa: F[A]): Coproduct[F, X, A] = Coproduct.leftc(fa)
23     }
24
25   // Inject implicits exists in scalaz, pointed out here for better understandability.
26   // It is similar to the above implicit definition for left, however, we assume that the right can be `R`
27   // specifiying it could be another coproduct or in fact it could be anything, and we use recursive implicit
28   // resolution.
29   implicit def injectCoproductRight[F[_], R[_], X[_]](implicit I: Inject[F, R]): Inject[F, Coproduct[X, R, ?]] =
30     new Inject[F, Coproduct[X, R, ?]] {
31       def inj[A](fa: F[A]): Coproduct[X, R, A] = Coproduct.rightc(I.inj(fa))
32     }
33
34   // Sadly, the above definition doesn't take care of the fact that what if R =:= F
35   implicit def injectReflexive[F[_]]: Inject[F, F] =
36     new Inject[F, F] {
37       def inj[A](fa: F[A]): F[A] = fa
38     }
39
40   // A helper lift function that can lift any of your algebra to a Coproduct that defines your entire app.
41   def inject[F[_], C[_], A](fa: F[A])(implicit m: Inject[F, C]) = Free.liftF[C, A](m.inj(fa))
42   
43   // However, we have  individual interpreters for each component in the app.
44   // But what we need is an interpreter that interprets the coproduct to some unified effect.
45   // Copying cats or in fact turned out to be cats implementation
46   def mixInterpreters[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = {
47     new (Coproduct[F, G, ?] ~> H) {
48       def apply[A](fa: Coproduct[F, G, A]): H[A] = {
49         fa.run match {
50           case -\/(ff) => f(ff)
51           case \/-(gg) => g(gg)
52         }
53       }
54     }
55   }
56
57   // A helper to access mixInterpreters
58   implicit class MixInterpreterOps[F[_], H[_]](val f: F ~> H) {
59     def or[G[_]](g: G ~> H): Coproduct[F, G, ?] ~> H = mixInterpreters[F, G, H](f, g)
60   }
61
62   // Lifted first algebra to a Corproduct
63   private val logAsCoproduct: Free[Coproduct[Logging, BridgeExec, ?], Unit] =
64     inject[Logging, Coproduct[Logging, BridgeExec, ?], Unit](Info("printLogging"))
65
66   // Lifted second algebra to a Corproduct
67   private val bridgeCoproduct: Free[Coproduct[Logging, BridgeExec, ?], Unit] =
68     inject[BridgeExec, Coproduct[Logging, BridgeExec, ?], Unit](PrintBridge("pringBridge"))
69
70   // Interpreter 1
71   private def runLogging: Logging ~> Id = new (Logging ~> Id) {
72     def apply[A](fa: Logging[A]): Id[A] = fa match {
73       case Info(x) => println(x)
74     }
75   }
76
77   // Interpreter 2
78   private def runBridge: BridgeExec ~> Id = new (BridgeExec ~> Id) {
79     def apply[A](fa: BridgeExec[A]): Id[A] = fa match {
80       case PrintBridge(x) => println(x)
81     }
82   }
83
84   // App interpreter
85   val interpreter: Coproduct[Logging, BridgeExec, ?] ~> Scalaz.Id = runLogging.or(runBridge)
86
87   // Your free program that is easily testable.
88   val program: Free[Coproduct[Logging, BridgeExec, ?], Unit] =
89     for {
90      _ <- logAsCoproduct
91       _ <- bridgeCoproduct
92     } yield ()
93
94   // Call this !
95   def runThis = program.foldMap(interpreter)
96 }

Well, that was useless toy example.

I think , with my experience with Free (learning scalaz, learning the red book, reading Free things, Runar’s paper on Trampoling to Free and in fact implementing free things in production with Scala), I can say operations as data with Free is attractive, but that can bring significant set of boiler plates that is strong enough to damage the entire design of the app if you are doing something non-trivial. So you need to really consider the complexity of your app before you try to make it simple with Free and find ways to hide away boilers from business logic. A blog can’t be your source of truth as most of them including this one talks Free using it’s constructs.

Back to the tangent, what happens if we have three algebras? Well, that was kind of a terrible fight with Scala type system but that’s fine for me personally ! I will also consider the library iotaz to achieve the same with less boiler plates.

1
2
3 import scalaz.{Scalaz, _}
4 import Scalaz._
5
6 // Thanks to https://underscore.io/blog/posts/2017/03/29/free-inject.html
7 object CombineFreeInScalaz {
8   sealed trait Logging[A]
9   case class Info(s: String) extends Logging[Unit]
10
11   sealed trait BridgeExec[A]
12   case class PrintBridge(s: String) extends BridgeExec[Unit]
13
14   sealed trait BridgeExe1c[A]
15   case class PrintBridge1(s: String) extends BridgeExe1c[Unit]
16
17   // This is just for describing how things work, and doesn't need to defined in scalaz.
18   trait Inject[F[_], G[_]] {
19     def inj[A](fa: F[A]): G[A]
20   }
21
22   // Inject implicits exists in scalaz, pointed out here for better understandability.
23   implicit def injectCoproductLeft[F[_], X[_]]: Inject[F, Coproduct[F, X, ?]] =
24     new Inject[F, Coproduct[F, X, ?]] {
25       def inj[A](fa: F[A]): Coproduct[F, X, A] = Coproduct.leftc(fa)
26     }
27
28   // Inject implicits exists in scalaz, pointed out here for better understandability.
29   // It is similar to the above implicit definition for left, however, we assume that the right can be `R`
30   // specifiying it could be another coproduct or in fact it could be anything, and we use recursive implicit
31   // resolution.
32   implicit def injectCoproductRight[F[_], R[_], X[_]](implicit I: Inject[F, R]): Inject[F, Coproduct[X, R, ?]] =
33     new Inject[F, Coproduct[X, R, ?]] {
34       def inj[A](fa: F[A]): Coproduct[X, R, A] = Coproduct.rightc(I.inj(fa))
35     }
36
37   // Sadly, the above definition doesn't take care of the fact that what if R =:= F
38   implicit def injectReflexive[F[_]]: Inject[F, F] =
39     new Inject[F, F] {
40       def inj[A](fa: F[A]): F[A] = fa
41    }
42
43   // A helper lift function that can lift any of your algebra to a Coproduct that defines your entire app.
44   def inject[F[_], C[_], A](fa: F[A])(implicit m: Inject[F, C]) = Free.liftF[C, A](m.inj(fa))
45
46   // However, we have  individual interpreters for each component in the app.
47   // But what we need is an interpreter that interprets the coproduct to some unified effect.
48   // Copying cats or in fact turned out to be cats implementation
49   def mixInterpreters[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = {
50     new (Coproduct[F, G, ?] ~> H) {
51       def apply[A](fa: Coproduct[F, G, A]): H[A] = {
52         fa.run match {
53           case -\/(ff) => f(ff)
54           case \/-(gg) => g(gg)
55         }
56       }
57     }
58   }
59
60   type SubApp[A] =  Coproduct[BridgeExec, BridgeExe1c, A]
61   type App[A] = Coproduct[Logging, SubApp, A]
62
63   // A helper to access mixInterpreters
64   implicit class MixInterpreterOps[F[_], H[_]](val f: F ~> H) {
65     def or[G[_]](g: G ~> H): Coproduct[F, G, ?] ~> H = mixInterpreters[F, G, H](f, g)
66   }
67 
68   // Lifted first algebra to a Corproduct
69   private val logAsCoproduct: Free[ App, Unit] =
70     inject[Logging, App, Unit](Info("printLogging"))
71 
72   // Lifted second algebra to a Corproduct
73   private val bridgeCoproduct: Free[App, Unit] =
74     inject[BridgeExec, App, Unit](PrintBridge("pringBridge"))
75
76   // Interpreter 1
77   private def runLogging: Logging ~> Id = new (Logging ~> Id) {
78     def apply[A](fa: Logging[A]): Id[A] = fa match {
79       case Info(x) => println(x)
80     }
81   }
82
83   // Interpreter 2
84   private def runBridge: BridgeExec ~> Id = new (BridgeExec ~> Id) {
85     def apply[A](fa: BridgeExec[A]): Id[A] = fa match {
86       case PrintBridge(x) => println(x)
87     }
88   }
89
90   private def runBridges: BridgeExe1c ~> Id = new (BridgeExe1c ~> Id) {
91     def apply[A](fa: BridgeExe1c[A]): Id[A] = fa match {
92       case PrintBridge1(x) => println(x)
93     }
94   }
95
96   val sub: ~>[Coproduct[BridgeExec, BridgeExe1c, ?], Scalaz.Id] = runBridge.or(runBridges)
97   // App interpreter
98   val interpreter: App ~> Scalaz.Id = runLogging.or[Coproduct[BridgeExec, BridgeExe1c, ?]](sub)
99 
100   // Your free program that is easily testable.
101   val program: Free[App, Unit] =
102     for {
103       _ <- logAsCoproduct
104       _ <- bridgeCoproduct
105     } yield ()
106 
107   // Call this !
108   def runThis = program.foldMap(interpreter)
109 }

Thanks developers ! Have fun developing only robust apps!'

 

This article was written by Afsal Thaj and posted originally on Medium.