Here's the scaste for the code below: https://scastie.scala-lang.org/bQMGrAKgRoOFaK1lwCy04g
I've a two JSON API endpoints. First, items.cgi, returns list of item objects in the following format
$ curl http://example.com/items.cgi
[
...
{ sn: "KXB1333", ownerId: 3, borrowerId: 0 },
{ sn: "KCB1200", ownerId: 1, borrowerId: 2 },
...
]
borrowerId == 0 means item has no borrower.
Second, users.cgi, returns user specified by id query parameter
$ curl http://example.com/user.cgi?id=1
{ id: 1, name: "frank" }
The API may be bad but I have to deal with it. Now in Scala I'd like to work with this nice data model
case class User(id: Int, name: String)
case class Item(sn: String, owner: User, borrower: Option[User])
I also have the following for doing HTTP requests
case class ApiFail(reason: String)
def get[T](url: String): Either[ApiFail, T] = ??? /* omitted for brevity */
The get() function uses some magic to fetch a JSON from an URL and construct a T out of it (it uses some libraries). On IO failure or bad HTTP status it returns Left.
I'd like to write the following function
def getItems: Either[ApiFail, Seq[Item]]
It should fetch the list of items, for each item fetch the linked users and return a new list of Items or fail on any HTTP request failure. (There may be redundant requests for users with a same ID but I don't care about memoization/caching yet.)
So far I only managed to write this function
def getItems: Either[ApiFail, Seq[Either[ApiFail, Item]]]
where a failure to retrieve some user is fatal only for the corresponding item and not the whole result. Here's the implementation
def getItems: Either[ApiFail, Seq[Either[ApiFail, Item]]] = {
case class ItemRaw(sn: String, ownerId: Int, borrowerId: Int)
get[List[ItemRaw]]("items.cgi").flatMap(itemRawList => Right(
itemRawList.map(itemRaw => {
for {
owner <- get[User](s"users.cgi?id=${itemRaw.ownerId}")
borrower <-
if (itemRaw.borrowerId > 0)
get[User](s"users.cgi?id=${itemRaw.borrowerId}").map(Some(_))
else
Right(None)
} yield
Item(itemRaw.sn, owner, borrower)
})
))
}
This seems like a request for a homework but it occurs to me frequently that I want to switch from one wrapper thing (m-monad?) to another and I'm a bit puzzled as to how to do it with wrapper functions (c-combinators?) only. I could of course switch to an imperative implementation. I'm just curious.
There is a word for doing exactly this in the FP world - "Traverse" (link to cats implementation). It's used when you have an F[A] and a function A => G[B] and you want a G[F[B]]. Here, F is List, A is ItemRaw, G is Either[ApiFail, _], and B is Item. Of course there are some constraints on what F and G can be.
Using cats, you can change your method very slightly:
import cats._, cats.implicits._
def getItems: Either[ApiFail, Seq[Item]] = {
case class ItemRaw(sn: String, ownerId: Int, borrowerId: Int)
get[List[ItemRaw]]("items.cgi").flatMap(itemRawList =>
itemRawList.traverse[({type T[A]=Either[ApiFail, A]})#T, Item](itemRaw => {
for {
owner <- get[User](s"users.cgi?id=${itemRaw.ownerId}")
borrower <-
if (itemRaw.borrowerId > 0)
get[User](s"users.cgi?id=${itemRaw.borrowerId}").map(Some(_))
else
Right(None)
} yield
Item(itemRaw.sn, owner, borrower)
})
)
}
With that said, I can certainly understand being hesitant to go fully down that route. cats (and scalaz) are a lot to take in - though I recommend you do at some point!
Without them, you can always write your own utility methods for manipulating your commonly-used containers:
def seqEither2EitherSeq[A, B](s: Seq[Either[A, B]]): Either[A, Seq[B]] = {
val xs: Seq[Either[A, Seq[B]]] = s.map(_.map(b => Seq(b)))
xs.reduce{ (e1, e2) => for (x1 <- e1; x2 <- e2) yield x1 ++ x2 }
}
def flattenEither[A, B](e: Either[A, Either[A, B]]): Either[A, B] = e.flatMap(identity)
Then the result you want would be:
val result: Either[ApiFail, Seq[Item]] = flattenEither(getItems.map(seqEither2EitherSeq))
Related
I have an actor, that should receive two messages and after that, become new initialised state. I wrote some code, but it seems very ugly:
def waitInitialisation(#Nullable one: Integer, #Nullable two: String): Receive = {
case _one: Int =>
if (two == null)
context.become(waitInitialisation(_one, two))
else {
doSomething()
context.become(initialised(_one, two))
}
case _two: String =>
if (one == null)
context.become(waitInitialisation(one, _two))
else {
doSomething()
context.become(initialised(one, _two))
}
}
def initialised(one: Int, two: String): Receive = ???
override def receive: Receive = waitInitialisation(null, null)
So problems, what I see: null checking and duplicate code. How I can simplify my implementation and make it properly?
#chunjef already gave a great direction in using Options which is the way to go in Scala. I'm letting below two other options in which I use pattern matching to make the code a bit more beautiful.
Before you take a look at the two solutions please bear in mind that usually calling .get on an Option is not recommended and will possibly get you some compiler warnings. Anyways, we're always sure to make the right call in our examples because we're checking beforehand if the option isDefined.
Oh, and when working with values that might come null - like operating with Java APIs - always use Option's apply, not Some's apply.
The first one defines essentially the same method but structured a bit differently:
def waitInit(one: Option[Int], two: Option[String]): Receive = {
case value: Int if two.isDefined =>
context.become(initialised(value, two.get))
case value: Int =>
context.become(waitInit(Option(value), two))
case value: String if one.isDefined =>
context.become(initialised(one.get, value))
case value: String =>
context.become(waitInit(one, Option(value)))
}
override val receive = waitInit(None, None)
The second one splits this logic in two pieces so you can follow on it easier:
def waitOne(two: Option[String]): Receive = {
case one: Int if two.isDefined =>
context.become(initialised(one, two.get))
case one: Int =>
context.become(waitOne(two) orElse waitTwo(Option(one)))
}
def waitTwo(one: Option[Int]): Receive = {
case two: String if one.isDefined =>
context.become(initialised(one.get, two))
case two: String =>
context.become(waitOne(Option(two)) orElse waitTwo(one))
}
override val receive: Receive =
waitOne(None) orElse waitTwo(None)
That's it, I didn't put some code here (like the definition of initialised) essentially because it's the same.
Enjoy :)
Using Option is the idiomatic way in Scala to handle nulls:
def waitInitialisation(one: Option[Int], two: Option[String]): Receive = {
case _one: Int =>
two match {
case Some(s) =>
doSomething()
context.become(initialised(_one, s))
case None =>
context.become(waitInitialisation(Option(_one), None))
}
case _two: String =>
one match {
case Some(i) =>
doSomething()
context.become(initialised(i, _two))
case None =>
context.become(waitInitialisation(None, Option(_two)))
}
}
def initialised(one: Int, two: String): Receive = ???
def receive = waitInitialisation(None, None)
As for "code duplication," I wouldn't get hung up on the number of become calls. Your actor can be in one of the four following states:
waitInitialisation(None, None)
waitInitialisation(Some, None)
waitInitialisation(None, Some)
initialised
You probably could implement the state changes with the FSM trait, but that would be overkill for your case. The way you've structured your actor is simple and clear.
You can also do that with an additional message.
With that solution, adding or changing messages for the init will be easy :
case class InitState(
one : Option[Int],
two : Option[String],
three : Option[Boolean]
)
{
def fire() : Unit = {
context.become(waitInit(this))
self ! this
}
}
def waitInit(st : InitState = InitState(None, None, None)) : Receive = {
case i : Int =>
st.copy(one = Some(i)).fire()
case s : String =>
st.copy( two = Some(s)).fire()
case b : Boolean =>
st.copy(three = Some(b)).fire()
case InitState(Some(i : Int), Some(s : String), Some(b : Boolean)) =>
context.become(afterInit(i, s, b))
case _ : InitState =>
}
def afterInit(one : Int, two : String, three : Boolean) : Receive = ???
def receive = waitInit()
As chunjef wrote, for not so simple cases, the good choice will be using FSM.
akka-contrib provided this Aggregator pattern that is similar to what you are looking for: https://github.com/akka/akka/blob/master/akka-contrib/src/main/scala/akka/contrib/pattern/Aggregator.scala
This code is now deprecated but you can copy it into your project.
Here you can find how it works (this documentation is pretty old): http://doc.akka.io/docs/akka/2.3.0/contrib/aggregator.html
The main idea is to use expect or expectOnce to receive certain messages. Once that has happened, you can do whatever else.
In order to be able to handle large amounts of different request types I created a .proto file like this:
message Message
{
string typeId = 1;
bytes message = 2;
}
I added the typeId so that one knows what actual protobuf bytes represents. (Self-describing)
Now my problem is handling that different "concrete types" in an elegant way. (Note: All works fine if I simple use a switch-case-like approach!)
I thought about a solution like this:
1) Have a trait the different handlers have to implement, e.g.:
trait Handler[T]
{
def handle(req: T): Any
}
object TestHandler extends Handler[Test]
{
override def handle(req: Test): String =
{
s"A success, $req has been handled by TestHandler
}
}
object OtherHandler extends Handler[Other]
{
override def handle(req: Other): String =
{
s"A success, $req has been handled by OtherHandler
}
}
2) provide some kind of registry to query the right handler for a given message:
val handlers = Map(
Test -> TestHandler,
Other -> OtherHandler
)
3) If a request comes in it identifies itself, so we need another Mapper:
val reqMapper = Map(
"Test" -> Test
"Other" -> Other
)
4) If a request comes in, handle it:
val request ...
// Determine the requestType
val requestType = reqMapper(request.type)
// Find the correct handler for the requestType
val handler = handlers(requestType)
// Parse the actual request
val actualRequest = requestType.parse(...) // type of actualRequest can only be Test or Other in our little example
Now, until here everything looks fine and dandy, but then this line breaks my whole world:
handler.handle(actualRequest)
It leads to:
type mismatch; found : com.trueaccord.scalapb.GeneratedMessage with Product with com.trueaccord.scalapb.Message[_ >: tld.test.proto.Message.Test with tld.test.proto.Message.Other <: com.trueaccord.scalapb.GeneratedMessage with Product] with com.trueaccord.lenses.Updatable[_ >: tld.test.proto.Message.Other with tld.test.proto.Message.Test <: com.trueaccord.scalapb.GeneratedMessage with Product]{def companion: Serializable} required: _1
As far as I understand - PLEASE CORRECT ME HERE IF AM WRONG - the compiler cannot be sure here, that actualRequest is "handable" by a handler. That means it lacks the knowledge that the actualRequest is definitely somewhere in that mapper AND ALSO that there is a handler for it.
It's basically implicit knowledge a human would get, but the compiler cannot infer.
So, that being said, how can I overcome that situation elegantly?
your types are lost when you use a normal Map. for eg
object Test{}
object Other{}
val reqMapper = Map("Test" -> Test,"Other" -> Other)
reqMapper("Test")
res0: Object = Test$#5bf0fe62 // the type is lost here and is set to java.lang.Object
the most idomatic way to approach this is to use pattern matching
request match {
case x: Test => TestHandler(x)
case x: Other => OtherHandler(x)
case _ => throw new IllegalArgumentException("not supported")
}
if you still want to use Maps to store your type to handler relation consider HMap provided by Shapeless here
Heterogenous maps
Shapeless provides a heterogenous map which supports an arbitrary
relation between the key type and the corresponding value type,
I settled for this solution for now (basically thesamet's, a bit adapted for my particular use-case)
trait Handler[T <: GeneratedMessage with Message[T], R]
{
implicit val cmp: GeneratedMessageCompanion[T]
def handle(bytes: ByteString): R = {
val msg: T = cmp.parseFrom(bytes.newInput())
handler(msg)
}
def apply(t: T): R
}
object Test extends Handler[Test, String]
{
override def apply(t: Test): String = s"$t received and handled"
override implicit val cmp: GeneratedMessageCompanion[Test] = Test.messageCompanion
}
One trick you can use is to capture the companion object as an implicit, and combine the parsing and handling in a single function where the type is available to the compiler:
case class Handler[T <: GeneratedMessage with Message[T]](handler: T => Unit)(implicit cmp: GeneratedMessageCompanion[T]) {
def handle(bytes: ByteString): Unit = {
val msg: T = cmp.parseFrom(bytes.newInputStream)
handler(t)
}
}
val handlers: Map[String, Handler[_]] = Map(
"X" -> Handler((x: X) => Unit),
"Y" -> Handler((x: Y) => Unit)
)
// To handle the request:
handlers(request.typeId).handle(request.message)
Also, take a look at any.proto which defines a structure very similar to your Message. It wouldn't solve your problem, but you can take advantage of it's pack and unpack methods.
I have an app that manages Items. When the client queries an item by some info, the app first tries to find an existing item in the db with the info. If there isn't one, the app would
Check if info is valid. This is an expensive operation (much more so than a db lookup), so the app only performs this when there isn't an existing item in the db.
If info is valid, insert a new Item into the db with info.
There are two more classes, ItemDao and ItemService:
object ItemDao {
def findByInfo(info: Info): Future[Option[Item]] = ...
// This DOES NOT validate info; it assumes info is valid
def insertIfNotExists(info: Info): Future[Item] = ...
}
object ItemService {
// Very expensive
def isValidInfo(info: Info): Future[Boolean] = ...
// Ugly
def findByInfo(info: Info): Future[Option[Item]] = {
ItemDao.findByInfo(info) flatMap { maybeItem =>
if (maybeItem.isDefined)
Future.successful(maybeItem)
else
isValidInfo(info) flatMap {
if (_) ItemDao.insertIfNotExists(info) map (Some(_))
else Future.successful(None)
}
}
}
}
The ItemService.findByInfo(info: Info) method is pretty ugly. I've been trying to clean it up for a while, but it's difficult since there are three types involved (Future[Boolean], Future[Item], and Future[Option[Item]]). I've tried to use scalaz's OptionT to clean it up but the non-optional Futures make it not very easy either.
Any ideas on a more elegant implementation?
To expand on my comment.
Since you've already indicated a willingness to go down the route of monad transformers, this should do what you want. There is unfortunately quite a bit of line noise due to Scala's less than stellar typechecking here, but hopefully you find it elegant enough.
import scalaz._
import Scalaz._
object ItemDao {
def findByInfo(info: Info): Future[Option[Item]] = ???
// This DOES NOT validate info; it assumes info is valid
def insertIfNotExists(info: Info): Future[Item] = ???
}
object ItemService {
// Very expensive
def isValidInfo(info: Info): Future[Boolean] = ???
def findByInfo(info: Info): Future[Option[Item]] = {
lazy val nullFuture = OptionT(Future.successful(none[Item]))
lazy val insert = ItemDao.insertIfNotExists(info).liftM[OptionT]
lazy val validation =
isValidInfo(info)
.liftM[OptionT]
.ifM(insert, nullFuture)
val maybeItem = OptionT(ItemDao.findByInfo(info))
val result = maybeItem <+> validation
result.run
}
}
Two comments about the code:
We are using the OptionT monad transformer here to capture the Future[Option[_]] stuff and anything that just lives inside Future[_] we're liftMing up to our OptionT[Future, _] monad.
<+> is an operation provided by MonadPlus. In a nutshell, as the name suggests, MonadPlus captures the intuition that often times monads have an intuitive way of being combined (e.g. List(1, 2, 3) <+> List(4, 5, 6) = List(1, 2, 3, 4, 5, 6)). Here we're using it to short-circuit when findByInfo returns Some(item) rather than the usual behavior to short-circuit on None (this is roughly analogous to List(item) <+> List() = List(item)).
Other small note, if you actually wanted to go down the monad transformers route, often times you end up building everything in your monad transformer (e.g. ItemDao.findByInfo would return an OptionT[Future, Item]) so that you don't have extraneous OptionT.apply calls and then .run everything at the end.
You don't need scalaz for this. Just break your flatMap into two steps:
first, find and validate, then insert if necessary. Something like this:
ItemDao.findByInfo(info).flatMap {
case None => isValidInfo(info).map(None -> _)
case x => Future.successful(x -> true)
}.flatMap {
case (_, true) => ItemDao.insertIfNotExists(info).map(Some(_))
case (x, _) => Future.successful(x)
}
Doesn't look too bad, does it? If you don't mind running validation in parallel with retrieval (marginally more expensive resource-vise, but likely faster on average), you could further simplify it like this:
ItemDao
.findByInfo(info)
.zip(isValidInfo(info))
.flatMap {
case (None, true) => ItemDao.insertIfNotExists(info).map(Some(_))
case (x, _) => x
}
Also, what does insertIfNotExists return if the item does exist? If it returned the existing item, things could be even simpler:
isValidInfo(info)
.filter(identity)
.flatMap { _ => ItemDao.insertIfNotExists(info) }
.map { item => Some(item) }
.recover { case _: NoSuchElementException => None }
If you are comfortable with path-dependent type and higher-kinded type, something like the following can be an elegant solution:
type Const[A] = A
sealed trait Request {
type F[_]
type A
type FA = F[A]
def query(client: Client): Future[FA]
}
case class FindByInfo(info: Info) extends Request {
type F[x] = Option[x]
type A = Item
def query(client: Client): Future[Option[Item]] = ???
}
case class CheckIfValidInfo(info: Info) extends Request {
type F[x] = Const[x]
type A = Boolean
def query(client: Client): Future[Boolean] = ???
}
class DB {
private val dbClient: Client = ???
def exec(request: Request): request.FA = request.query(dbClient)
}
What this does is basically to abstract over both the wrapper type (eg. Option[_]) as well as inner type. For types without a wrapper type, we use Const[_] type which is basically an identity type.
In scala, many problems alike this can be solved elegantly using Algebraic Data Type and its advanced type system (i.e path-dependent type & higher-kinded type). Note that now we have single point of entry exec(request: Request) for executing db requests instead of something like DAO.
I'm trying to use Reader monad for dependency injection, but have problems when the methods requires different dependencies:
class PageFetcher {
def fetch(url: String) = Reader((dep1: Dep1) => Try {
...
})
}
class ImageExtractor {
def extractImages(html: String) = Reader((deps: (Dep2, Dep3)) => {
...
})
}
object MyImageFinder {
def find(url: String) = Reader((deps: (PageFetcher, ImageExtractor)) => {
val (pageFetcher, imageExtractor) = deps
for {
htmlTry <- pageFetcher.fetch(url)
html <- htmlTry
images <- imageExtractor.extractImages(html)
} yield images
})
}
// I add these 3 useless dependencies here just for demo
class Dep1
class Dep2
class Dep3
You can see PageFetcher.fetch and ImageExtractor.extractImages and MyImageFinder.find all have different dependencies.
I'm not sure if the way I use the Reader correctly, and soon when I combine them together and want to pass the dependencies, I don't know how to do it:
val pageFetcher = new PageFetcher
val imageExtractor = new ImageExtractor
val dep1 = new Dep1
val dep2 = new Dep2
val dep3 = new Dep3
def main(args: Array[String]) {
args.headOption match {
case Some(url) =>
MyImageFinder.find(url)(???) match {
case Success(images) => images.foreach(println)
case Failure(err) => println(err.toString)
}
case _ => println("Please input an url")
}
}
Notice the code MyImageFinder.find(url)(???), I want to pass the dependencies like pageFetcher/imageExtractor/dep1/dep2/dep3, and no matter how I tried, it just can't be compiled.
Is my way to use Reader correct? How can I pass the dependencies easily?
If you want to use multiple readers in a for-comprehension, the argument types will need to be the same, one way or another. One easy way is just to bundle everything up in an environment type (it could just be a tuple), and then use that as the dependency for all your readers.
That throws away a lot of information about fine-grained dependencies in the types, though, and you can also use local as a kind of map over the input in the for-comprehension:
case class Foo(i: Int)
case class Bar(s: String)
case class Config(foo: Foo, bar: Bar)
val doSomethingWithFoo: Reader[Foo, String] = Reader(foo => "hello " * foo.i)
val doSomethingWithBar: Reader[Bar, String] = Reader(bar => s"bar is $bar")
val doSomethingWithConfig: Reader[Config, String] = for {
resFoo <- doSomethingWithFoo.local(_.foo)
resBar <- doSomethingWithBar.local(_.bar)
} yield (resFoo, resBar)
Just as map with a function A => B can change a Reader[E, A] to a Reader[E, B], local with E => F changes Reader[F, A] to Reader[E, A], in this case taking the specific chunk of the environment the reader needs and feeding it in by itself.
Note that there are lots of other combinators on Kleisli (a more general type—Reader is just an alias for Kleisli[Id, _, _]) that are worth reading up on.
Update: removed custom flatMap in favor of scalaz's Reader
As Travis already pointed out, to use the Reader pattern, you need single argument functions. So in order to use it for multiple dependencies, you somehow need to get all of your dependencies into a single argument. And here it becomes interesting. The way Travis showed is the simplest way to do it, but you also have to manually switch environments using the .local calls and if you need multiple dependencies for subtrees of your computation, you need to manually build local environments.
Another way to to it is to let Scala's subtyping figure it out auto-magically. As long as your dependencies can be mixed in, composing things with different or multiple dependencies just works (if you actually use scalaz's Reader, not if you use flatMap on Function1 as some of the Reader examples do).
Option 1: Cup cake pattern
One way to allow your dependencies to be able to mixed in is a stripped down cake pattern. I'd call it cup-cake pattern, if I had to give it a name, Dick Wall calls it Parfait (see https://parleys.com/play/53a7d2cde4b0543940d9e55f/chapter28/about ). The idea is instead of putting everything into the cake, only put the dependencies into the cake and pass it through as a context object, which you can abstract over using the reader. Let's apply it to your example:
// business logic
class PageFetcher {
def fetch(url: String) = Reader((deps: Dep1Component) => Try {
...
})
}
class ImageExtractor {
def extractImages(html: String) = Reader((deps: (Dep2Component with Dep3Component)) => {
...
})
}
object MyImageFinder {
def find(url: String) =
for {
pageFetcher <- Reader((deps: PageFetcherComponent) => dep.pageFetcher)
imageExtractor <- Reader((deps: ImageExtractorComponent) => dep.imageExtractor)
htmlTry <- pageFetcher.fetch(url)
html <- htmlTry
images <- imageExtractor.extractImages(html)
} yield images
}
// I add these 3 useless dependencies here just for demo
class Dep1
class Dep2
class Dep3
// cupcake modules
trait PageFetcherComponent{
def pageFetcher: PageFetcher
}
trait ImageExtractorComponent{
def imageExtractor: ImageExtractor
}
trait Dep1Component{
def dep1: Dep1
}
trait Dep2Component {
def dep2: Dep2
}
trait Dep3Component{
def dep3: Dep3
}
object Dependencies extends PageFetcherComponent with ImageExtractorComponent with Dep1Component with Dep2Component with Dep3Component{
val pageFetcher = new PageFetcher
val imageExtractor = new ImageExtractor
val dep1 = new Dep1
val dep2 = new Dep2
val dep3 = new Dep3
}
def main(args: Array[String]) {
args.headOption match {
case Some(url) =>
MyImageFinder.find(url)(Dependencies) match {
case Success(images) => images.foreach(println)
case Failure(err) => println(err.toString)
}
case _ => println("Please input an url")
}
}
The cup-cake pattern becomes tricky if you have multiple instances of the same dependencies (multiple loggers, multiple dbs, etc.) and have some code which you want to be able to selectively use on the one or the other.
Option 2: Type-indexed Map
I recently came up with another way to do it using a special data structure I call type-indexed map. It saves all the cup-cake boiler plate and it makes it much easier to use multiple instances of the same type of dependency (i.e. just wrap them in single member classes to distinguish them).
/** gets stuff out of a TMap */
def Implicit[V:TTKey] = Reader((c: TMap[V]) => c[V])
// business logic
class PageFetcher {
def fetch(url: String) = Implicit[Dep1].map{ dep1 => Try {
...
}}
}
class ImageExtractor {
def extractImages(html: String) = for{
dep2 <- Implicit[Dep1]
dep3 <- Implicit[Dep3]
} yield {
...
}
}
object MyImageFinder {
def find(url: String) =
for {
pageFetcher <- Implicit[PageFetcherComponent]
imageExtractor <- Implicit[ImageExtractorComponent]
htmlTry <- pageFetcher.fetch(url)
html <- htmlTry
images <- imageExtractor.extractImages(html)
} yield images
}
// I add these 3 useless dependencies here just for demo
class Dep1
class Dep2
class Dep3
val Dependencies =
TMap(new PageFetcher) ++
TMap(new ImageExtractor) ++
TMap(new Dep1) ++
TMap(new Dep2) ++
TMap(new Dep3)
def main(args: Array[String]) {
args.headOption match {
case Some(url) =>
MyImageFinder.find(url)(Dependencies) match {
case Success(images) => images.foreach(println)
case Failure(err) => println(err.toString)
}
case _ => println("Please input an url")
}
}
I published it here https://github.com/cvogt/slick-action/ . The corresponding test cases are here: https://github.com/cvogt/slick-action/blob/master/src/test/scala/org/cvogt/di/TMapTest.scala#L213 It's on maven, but be careful when using it, because the code is in flux and the current implementation is not thread-safe in 2.10, only in 2.11, because it relies on TypeTags. I'll probably publish a version that works for 2.10 and 2.11 at some point.
Addendum
While this solves multi-dependency injection with the reader monad, you will still get type errors for htmlTry because you are mixing Reader/Function1-composition with Try-composition. The solution is to create a wrapping Monad that internally wraps Function1[TMap[...],Try[...]] and allow composing those. This does require you to stuff everything into this type of monad, even if something wouldn't need a Try.
I am trying to create a neat construction with for-comprehension for business logic built on futures. Here is a sample which contains a working example based on Exception handling:
(for {
// find the user by id, findUser(id) returns Future[Option[User]]
userOpt <- userDao.findUser(userId)
_ = if (!userOpt.isDefined) throw new EntityNotFoundException(classOf[User], userId)
user = userOpt.get
// authenticate it, authenticate(user) returns Future[AuthResult]
authResult <- userDao.authenticate(user)
_ = if (!authResult.ok) throw new AuthFailedException(userId)
// find the good owned by the user, findGood(id) returns Future[Option[Good]]
goodOpt <- goodDao.findGood(goodId)
_ = if (!good.isDefined) throw new EntityNotFoundException(classOf[Good], goodId)
good = goodOpt.get
// check ownership for the user, checkOwnership(user, good) returns Future[Boolean]
ownership <- goodDao.checkOwnership(user, good)
if (!ownership) throw new OwnershipException(user, good)
_ <- goodDao.remove(good)
} yield {
renderJson(Map(
"success" -> true
))
})
.recover {
case ex: EntityNotFoundException =>
/// ... handle error cases ...
renderJson(Map(
"success" -> false,
"error" -> "Your blahblahblah was not found in our database"
))
case ex: AuthFailedException =>
/// ... handle error cases ...
case ex: OwnershipException =>
/// ... handle error cases ...
}
However this might be seen as a non-functional or non-Scala way to handle the things. Is there a better way to do this?
Note that these errors come from different sources - some are at the business level ('checking ownership') and some are at controller level ('authorization') and some are at db level ('entity not found'). So approaches when you derive them from a single common error type might not work.
Don't use exceptions for expected behaviour.
It's not nice in Java, and it's really not nice in Scala. Please see this question for more information about why you should avoid using exceptions for regular control flow. Scala is very well equipped to avoid using exceptions: you can use Eithers.
The trick is to define some failures you might encounter, and convert your Options into Eithers that wrap these failures.
// Failures.scala
object Failures {
sealed trait Failure
// Four types of possible failures here
case object UserNotFound extends Failure
case object NotAuthenticated extends Failure
case object GoodNotFound extends Failure
case object NoOwnership extends Failure
// Put other errors here...
// Converts options into Eithers for you
implicit class opt2either[A](opt: Option[A]) {
def withFailure(f: Failure) = opt.fold(Left(f))(a => Right(a))
}
}
Using these helpers, you can make your for comprehension readable and exception free:
import Failures._
// Helper function to make ownership checking more readable in the for comprehension
def checkGood(user: User, good: Good) = {
if(checkOwnership(user, good))
Right(good)
else
Left(NoOwnership)
}
// First create the JSON
val resultFuture: Future[Either[Failure, JsResult]] = for {
userRes <- userDao.findUser(userId)
user <- userRes.withFailure(UserNotFound).right
authRes <- userDao.authenticate(user)
auth <- authRes.withFailure(NotAuthenticated).right
goodRes <- goodDao.findGood(goodId)
good <- goodRes.withFailure(GoodNotFound).right
checkedGood <- checkGood(user, good).right
} yield renderJson(Map("success" -> true)))
// Check result and handle any failures
resultFuture.map { result =>
result match {
case Right(json) => json // serve json
case Left(failure) => failure match {
case UserNotFound => // Handle errors
case NotAuthenticated =>
case GoodNotFound =>
case NoOwnership =>
case _ =>
}
}
}
You could clean up the for comprehension a little to look like this:
for {
user <- findUser(userId)
authResult <- authUser(user)
good <- findGood(goodId)
_ <- checkOwnership(user, good)
_ <- goodDao.remove(good)
} yield {
renderJson(Map(
"success" -> true
))
}
Assuming these methods:
def findUser(id:Long) = find(id, userDao.findUser)
def findGood(id:Long) = find(id, goodDao.findGood)
def find[T:ClassTag](id:Long, f:Long => Future[Option[T]]) = {
f(id).flatMap{
case None => Future.failed(new EntityNotFoundException(implicitly[ClassTag[T]].runtimeClass, id))
case Some(entity) => Future.successful(entity)
}
}
def authUser(user:User) = {
userDao.authenticate(user).flatMap{
case result if result.ok => Future.failed(new AuthFailedException(userId))
case result => Future.successful(result)
}
}
def checkOwnership(user:User, good:Good):Future[Boolean] = {
val someCondition = true //real logic for ownership check goes here
if (someCondition) Future.successful(true)
else Future.failed(new OwnershipException(user, good))
}
The idea here is to use flatMap to turn things like Options that are returned wrapped in Futures into failed Futures when they are None. There are going to be a lot of ways to do clean up that for comp and this is one possible way to do it.
The central challenge is that for-comprehensions can only work on one monad at a time, in this case it being the Future monad and the only way to short-circuit a sequence of future calls is for the future to fail. This works because the subsequent calls in the for-comprehension are just map and flatmap calls, and the behavior of a map/flatmap on a failed Future is to return that future and not execute the provided body (i.e. the function being called).
What you are trying to achieve is the short-cicuiting of a workflow based on some conditions and not do it by failing the future. This can be done by wrapping the result in another container, let's call it Result[A], which gives the comprehension a type of Future[Result[A]]. Result would either contain a result value, or be a terminating result. The challenge is how to:
provide subsequent function calls the value contained by a prior non-terminating Result
prevent the subsequent function call from being evaluated if the Result is terminating
map/flatmap seem like the candidates for doing these types of compositions, except we will have to call them manually, since the only map/flatmap that the for-comprehension can evaluate is one that results in a Future[Result[A]].
Result could be defined as:
trait Result[+A] {
// the intermediate Result
def value: A
// convert this result into a final result based on another result
def given[B](other: Result[B]): Result[A] = other match {
case x: Terminator => x
case v => this
}
// replace the value of this result with the provided one
def apply[B](v: B): Result[B]
// replace the current result with one based on function call
def flatMap[A2 >: A, B](f: A2 => Future[Result[B]]): Future[Result[B]]
// create a new result using the value of both
def combine[B](other: Result[B]): Result[(A, B)] = other match {
case x: Terminator => x
case b => Successful((value, b.value))
}
}
For each call, the action is really a potential action, as calling it on or with a terminating result, will simply maintain the terminating result. Note that Terminator is a Result[Nothing] since it will never contain a value and any Result[+A] can be a Result[Nothing].
The terminating result is defined as:
sealed trait Terminator extends Result[Nothing] {
val value = throw new IllegalStateException()
// The terminator will always short-circuit and return itself as
// the success rather than execute the provided block, thus
// propagating the terminating result
def flatMap[A2 >: Nothing, B](f: A2 => Future[Result[B]]): Future[Result[B]] =
Future.successful(this)
// if we apply just a value to a Terminator the result is always the Terminator
def apply[B](v: B): Result[B] = this
// this apply is a convenience function for returning this terminator
// or a successful value if the input has some value
def apply[A](opt: Option[A]) = opt match {
case None => this
case Some(v) => Successful[A](v)
}
// this apply is a convenience function for returning this terminator or
// a UnitResult
def apply(bool: Boolean): Result[Unit] = if (bool) UnitResult else this
}
The terminating result makes it possible to to short-circuit calls to functions that require a value [A] when we've already met our terminating condition.
The non-terminating result is defined as:
trait SuccessfulResult[+A] extends Result[A] {
def apply[B](v: B): Result[B] = Successful(v)
def flatMap[A2 >: A, B](f: A2 => Future[Result[B]]): Future[Result[B]] = f(value)
}
case class Successful[+A](value: A) extends SuccessfulResult[A]
case object UnitResult extends SuccessfulResult[Unit] {
val value = {}
}
The non-teminating result makes it possible to provide the contained value [A] to functions. For good measure, I've also predefined a UnitResult for functions that are purely side-effecting, like goodDao.removeGood.
Now let's define your good, but terminating conditions:
case object UserNotFound extends Terminator
case object NotAuthenticated extends Terminator
case object GoodNotFound extends Terminator
case object NoOwnership extends Terminator
Now we have the tools to create the the workflow you were looking for. Each for comprehention wants a function that returns a Future[Result[A]] on the right-hand side, producing a Result[A] on the left-hand side. The flatMap on Result[A] makes it possible to call (or short-circuit) a function that requires an [A] as input and we can then map its result to a new Result:
def renderJson(data: Map[Any, Any]): JsResult = ???
def renderError(message: String): JsResult = ???
val resultFuture = for {
// apply UserNotFound to the Option to conver it into Result[User] or UserNotFound
userResult <- userDao.findUser(userId).map(UserNotFound(_))
// apply NotAuthenticated to AuthResult.ok to create a UnitResult or NotAuthenticated
authResult <- userResult.flatMap(user => userDao.authenticate(user).map(x => NotAuthenticated(x.ok)))
goodResult <- authResult.flatMap(_ => goodDao.findGood(goodId).map(GoodNotFound(_)))
// combine user and good, so we can feed it into checkOwnership
comboResult = userResult.combine(goodResult)
ownershipResult <- goodResult.flatMap { case (user, good) => goodDao.checkOwnership(user, good).map(NoOwnership(_))}
// in order to call removeGood with a good value, we take the original
// good result and potentially convert it to a Terminator based on
// ownershipResult via .given
_ <- goodResult.given(ownershipResult).flatMap(good => goodDao.removeGood(good).map(x => UnitResult))
} yield {
// ownership was the last result we cared about, so we apply the output
// to it to create a Future[Result[JsResult]] or some Terminator
ownershipResult(renderJson(Map(
"success" -> true
)))
}
// now we can map Result into its value or some other value based on the Terminator
val jsFuture = resultFuture.map {
case UserNotFound => renderError("User not found")
case NotAuthenticated => renderError("User not authenticated")
case GoodNotFound => renderError("Good not found")
case NoOwnership => renderError("No ownership")
case x => x.value
}
I know that's a whole lot of setup, but at least the Result type can be used for any Future for-comprehension that has terminating conditions.