Skip to content

Instantly share code, notes, and snippets.

@dadepo
Last active June 10, 2018 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dadepo/4138175b64dca46ca64158727ca3278e to your computer and use it in GitHub Desktop.
Save dadepo/4138175b64dca46ca64158727ca3278e to your computer and use it in GitHub Desktop.
Implementation of a program that requires configuration using Reader Monad (via Cats), Implicit Parameters, and Implicit Function.
/**
* Chapter 4 of the Scala with Cats book (https://underscore.io/books/scala-with-cats/) has got
* an exercise regarding the Reader monad which is supposed to showcase the classic use case of Reader Monad:
* which is to build programs that accepts a configuration as parameter.
*
* The exercise can be summarised as follows.
*
* Given a DB configuration with the following shape:
* case class Db(usernames: Map[Int, String], passwords: Map[String, String])
* where usernames is Map of userid to username
* and passwords is a Map of username to password
*
* Create a method that generate DbReaders to look up the username for an Int user ID. The method will look this this:
* def findUsername(userId: Int): DbReader[Option[String]] = ???
*
* Create a method that looks up the password for a String username. The method will look like this:
* def checkPassword(username: String, password: String): DbReader[Boolean] = ???
*
* Then making use of the findUsername and checkPassowrd method, create a checkLogin method to check the password for a given user
* ID. The method should be as follows:
* def checkLogin(userId: Int, password: String): DbReader[Boolean] = ???
*
*
* After reading about the Reader monad and implementing the exercise I had a feeling Reader monad is unnecessary in
* Scala since the language has Implicits which can easily be used to achieve what is said to be the primary use
* case of Reader Monads.
*
* In this gist I note down the solution to the exercise implemented using:
*
* 1. Reader Monad
* 2. Implicit parameters
* 3. Implicit functions (requires Scala 3)
*
* And yes, I think, for this particular use case, it is better to eschew the Reader monad and just got with implicits
*/
// 1. Using Reader Monad from Cats.
package example.chapter4
import cats.data.Reader
import cats.implicits._
object readerImplementation extends App {
type DbReader[A] = Reader[Db, A]
case class Db(usernames: Map[Int, String], passwords: Map[String, String])
def findUsername(userId: Int): DbReader[Option[String]] = Reader {db:Db => db.usernames.get(userId)}
def checkPassword(username: String, password: String): DbReader[Boolean] = Reader {
db:Db => db.passwords.get(username).contains(password)
}
def checkLogin(userId: Int, password: String): DbReader[Boolean] = for {
username <- findUsername(userId)
allowed <- username.map{uname => checkPassword(uname, password)}.getOrElse(false.pure[DbReader])
} yield allowed
val users = Map(
1 -> "dade",
2 -> "kate",
3 -> "margo"
)
val passwords = Map(
"dade" -> "zerocool",
"kate" -> "acidburn",
"margo" -> "secret"
)
val db = Db(users, passwords)
println(checkLogin(1, "zerocool").run(db)) // prints true
println(checkLogin(4, "davinci").run(db)) // prints false
}
//2. Using Plain Scala with Implicit Parameters
package example.chapter4
object implicitParameterImplementation extends App {
case class Db(usernames: Map[Int, String], passwords: Map[String, String])
def findUsername(userId: Int)(implicit db: Db): Option[String] =
db.usernames.get(userId)
def checkPassword(username: String, password: String)(implicit db: Db): Boolean =
db.passwords.get(username).contains(password)
val users = Map(
1 -> "dade",
2 -> "kate",
3 -> "margo"
)
val passwords = Map(
"dade" -> "zerocool",
"kate" -> "acidburn",
"margo" -> "secret"
)
implicit val db = Db(users, passwords)
def checkLogin(userId: Int, password: String)(implicit db: Db): Boolean = {
for {
username <- findUsername(userId)
allowed <- Some(checkPassword(username, password))
} yield allowed
}.getOrElse(false)
println(checkLogin(1, "zerocool")) // prints true
println(checkLogin(4, "davinci")) // prints false
}
// 3. Implementation using Implicit Functions.
// Implicit function is a feature only available in the next Scala release. ie Scala 3 (aka Dotty)
// So implementation below would only compile if you use dotty compiler.
// Find information on getting dotty here http://dotty.epfl.ch/index.html#getting-started
package example.chapter4
object implicitFunctionImplementation extends App {
type ConfiguredWithDb[T] = implicit Db => T
// usernames Map of userid to username
// passwords Map of username to password
case class Db(usernames: Map[Int, String], passwords: Map[String, String])
def findUsername(userId: Int): ConfiguredWithDb[Option[String]] =
db.usernames.get(userId)
def checkPassword(username: String, password: String): ConfiguredWithDb[Boolean] =
db.passwords.get(username).contains(password)
val users = Map(
1 -> "dade",
2 -> "kate",
3 -> "margo"
)
val passwords = Map(
"dade" -> "zerocool",
"kate" -> "acidburn",
"margo" -> "secret"
)
implicit val db:Db = Db(users, passwords)
def checkLogin(userId: Int, password: String): ConfiguredWithDb[Boolean] = {
for {
username <- findUsername(userId)
allowed <- Some(checkPassword(username, password))
} yield allowed
}.getOrElse(false)
println(checkLogin(1, "zerocool")) // prints true
println(checkLogin(4, "davinci")) // prints false
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment