After studying Monad, I also curious about Reader Monad and Writer Monad. So, I write a post about it. In fact, first, I suppose to write a Reader Monad and Writer Monad at the same post, but later I decided to write separately.
Below example is from mastering advanced Scala
What is Reader Monad
At first, I thought Reader Monad is used to read something. It’s true. But another definition is effective.
Reader Monad is used to Dependency Injection.
Yes. I think it’s a better definition. Reader Monad object is like this.
object Reader { def apply[A, B](f: A => B): Reader[A, B] = ReaderT[Id, A, B](f) }
Anonymous function f is the function we want to run. And ‘A’ will be injected later when we call like this.
readerMoandValue.run(ATypeInstance)
ATypeInstance is an instance which type is A. And as you can see, is passed when you call run function.
Example
First, Let’s suppose some Service like this.
trait AuthService { def isLogged(name: String): Boolean } class AuthServiceChar3 extends AuthService{ override def isLogged(name: String): Boolean = name.length == 3 } class AuthServiceChar5 extends AuthService{ override def isLogged(name: String): Boolean = name.length == 5 } trait UserService { def greet(name: String, isLogged: Boolean): String } class UserServiceDefaultUser extends UserService{ override def greet(name: String, isLogged: Boolean): String = { val actualName = if(isLogged) name else "User" s"Hello $actualName" } } class UserServiceNoDefault extends UserService{ override def greet(name: String, isLogged: Boolean): String = { if(isLogged) s"Hello $name" else "No authorization" } } case class Environment(userName: String, userService: UserService, authService: AuthService)
AuthService has a simple auth logic and actualized class is AuthServiceChar3 and AuthServiceChar5. And UserSertivce has a simple greet logic and actualized class is UserServiceDefaultUser and UserServiceNoDefault.
And Environment is case class which needs a user name, UserService, and AuthService. And here are some Reader Monad use environment and return something.
import cats.data.Reader def isLoggedUser: Reader[Environment, Boolean] = Reader[Environment, Boolean] { env => env.authService.isLogged(env.userName) } def greetUser(logged: Boolean): Reader[Environment, String] = Reader[Environment, String] { env => env.userService.greet(env.userName, logged) } // In Intellij if you use auto complete, Kleisli[Id, Environment, String] will be written. I will write post later val resultR: Reader[Environment, String] = for { logged <- isLoggedUser greeting <- greetUser(logged) } yield greeting
Value resultR is a Reader Monad get logged value from isLoggedUser and return greet String.
In here yet, we didn’t pass Environment instance. Now, let’s pass Environment.
val environment1 = Environment("Joe", new UserServiceDefaultUser, new AuthServiceChar3) println(resultR.run(environment1)) // print Hello Joe val environment2 = Environment("Joe", new UserServiceNoDefault, new AuthServiceChar5) println(resultR.run(environment2)) // print No authorization
As you can see, we inject environment when we call run function. And when we inject different instance, print different output.
trait AuthService { | |
def isLogged(name: String): Boolean | |
} | |
class AuthServiceChar3 extends AuthService{ | |
override def isLogged(name: String): Boolean = name.length == 3 | |
} | |
class AuthServiceChar5 extends AuthService{ | |
override def isLogged(name: String): Boolean = name.length == 5 | |
} | |
trait UserService { | |
def greet(name: String, isLogged: Boolean): String | |
} | |
class UserServiceDefaultUser extends UserService{ | |
override def greet(name: String, isLogged: Boolean): String = { | |
val actualName = if(isLogged) name else "User" | |
s"Hello $actualName" | |
} | |
} | |
class UserServiceNoDefault extends UserService{ | |
override def greet(name: String, isLogged: Boolean): String = { | |
if(isLogged) s"Hello $name" else "No authorization" | |
} | |
} | |
case class Environment(userName: String, userService: UserService, authService: AuthService) | |
import cats.data.Reader | |
def isLoggedUser: Reader[Environment, Boolean] = Reader[Environment, Boolean] { env => | |
env.authService.isLogged(env.userName) | |
} | |
def greetUser(logged: Boolean): Reader[Environment, String] = Reader[Environment, String] { env => | |
env.userService.greet(env.userName, logged) | |
} | |
// In Intellij if you use auto complete, Kleisli[Id, Environment, String] will be written. I will write post later | |
val resultR: Reader[Environment, String] = for { | |
logged <- isLoggedUser | |
greeting <- greetUser(logged) | |
} yield greeting | |
val environment1 = Environment("Joe", new UserServiceDefaultUser, new AuthServiceChar3) | |
println(resultR.run(environment1)) | |
val environment2 = Environment("Joe", new UserServiceNoDefault, new AuthServiceChar5) | |
println(resultR.run(environment2)) |