Modeling with Scala case class - scala

I'm attempting to model responses from REST APIs as case classes which I can use pattern matching on.
I thought it would be a good fit assuming inheritance, but I see that this is deprecated.
I know there are already questions related to case classes and inheritance, but my question is more about how you would model the following the "right way" here without inheritance.
I started with the following two case classes, which work fine:
case class Body(contentType: String, content: String)
case class Response(statusCode: Int, body: Body)
i.e. a REST call would return with something like:
Response(200, Body("application/json", """{ "foo": "bar" }"""))
which I could pattern match like:
response match {
case Response(200, Body("application/json", json)) => println(json)
case Response(200, Body("text/xml", xml)) => println(xml)
case Response(_,_) => println("Something unexpected")
}
etc. which works fine.
Where I ran into trouble is: I'd like helper extensions for these case classes, such as:
case class OK(body: Body) extends Response(200, body)
case class NotFound() extends Response(404, Body("text/plain", "Not Found"))
case class JSON(json: String) extends Body("application/json", json)
case class XML(xml: String) extends Body("text/xml", xml)
so that I can do simplified pattern matches like this:
response match {
case OK(JSON(json)) => println(json)
case OK(XML(xml)) => println(xml)
case NotFound() => println("Something is not there")
// And still drop down to this if necessary:
case Response(302, _) => println("It moved")
}
and also which would also allow my REST code to directly use and return:
Response(code, Body(contentType, content))
which is easier to build a response dynamically.
so...
I can get it to compile (with deprecation warnings) via:
case class OK(override val body: Body) extends Response(200, body)
However, this doesn't seem to work with pattern matching.
Response(200, Body("application/json", "")) match {
case OK(_) => ":-)"
case _ => ":-("
}
res0: java.lang.String = :-(
Any ideas on how this could work? I'm open to different approaches, but this was my attempt to find a practical use for case classes

There are several reasons why case classes shouldn't be subclassed. In your case, the problem becomes that OK is another type than (a subtype of) Response, therefore the match fails (even if the arguments match, the type doesn't match).
You will want custom extractors, instead. For example:
case class Response(code: Int, body: String)
object OK {
def apply(body: String) = Response(200, body)
def unapply(m: Response): Option[String] = m match {
case Response(200, body) => Some(body)
case _ => None
}
}
def test(m: Response): String = m match {
case OK(_) => ":-)"
case _ => ":-("
}
test(Response(300, "Hallo")) // :-(
test(Response(200, "Welt")) // :-)
test(OK("Welt")) // :-)
There are few more examples of custom extractors in this thread.

Have you looked at the scala library unfiltered?
http://unfiltered.lessis.me/
It may help you with approaching you problem .
HTH

While custom extractors mentioned by 0__ can certainly be used, you'll lose exhaustiveness guarantees of sealed type hierarchies. While in the example you gave in the question there's nothing sealed, the problem is well suited to them.
In that case, my suggestion is to simply make sure the case class is always at the bottom of the type hierarchy, and make the upper classes normal. For example:
sealed class Response(val statusCode: Int, val body: Body) sealed
case class Ok(override val body: Body) extends Response(200, body)
sealed class NotOk(statusCode: Int, body: Body) extends Response(statusCode, body)
case object NotFound extends NotOk(404, "Not found")
// and so on...

Related

Is it possible to serialize as known default format in custom serializer?

I have to deserialize JSON response that can have one of the fields set to different objects (with just one common field). Real-life model is rather complex, but for example we can represent it by two case classes extending sealed trait:
sealed trait Item {
val itemType: String
}
case class FirstItem(
itemType: String = "FirstItem",
firstProperties: SomeComplexType
) extends Item
case class SecondItem(
itemType: String = "SecondItem",
secondProperties: SomeOtherComplexType,
secondName: String,
secondSize: Int
) extends Item
Since Json4s does not know how to handle that object I wrote custom serializer:
object ItemSerializer extends CustomSerializer[Item](_ => ({
case i: JObject =>
implicit val formats: Formats = DefaultFormats
(i \ "itemType").extract[String] match {
case "FirstType" => i.extract[FirstItem]
case "SecondItem" => i.extract[SecondItem]
}
}, {
case x: Item => x match {
case f: FirstItem => JObject() //TODO
case s: SecondItem => JObject() //TODO
}
}))
First part - deserialization is not perfect as it depends strongly on type-field, but its fine for my needs. The problem is the second part - serialization. In examples I've found people usually write down every field step by step, but usually, they serialize some simple objects. In my case this object has multiple levels and over 60-80 fields in total so it would result in rather messy and hard to read code. So I'm wondering if there is a better way to do it, as both FirstItem and SecondItem can be deserialized using only DefaultFormats. Is there any way to tell Json4s that if object matches the given type it should be serialized with default format?
It took me some digging in various examples and turns out that it is is possible and super simple. There is an org.json4s.Extraction.decompose() method that handles everything e.g.:
object ItemSerializer extends CustomSerializer[Item](_ => ({
case i: JObject =>
implicit val formats: Formats = DefaultFormats
(i \ "itemType").extract[String] match {
case "FirstType" => i.extract[FirstItem]
case "SecondItem" => i.extract[SecondItem]
}
}, {
case x: Item =>
implicit val formats: Formats = DefaultFormats
x match {
case f: FirstItem => Extraction.decompose(f)
case s: SecondItem => Extraction.decompose(s)
}
}))
However, my solution to the described problem was wrong. I don't need to specify extra serializers. What I needed was just a companion object for trait, that contains constructors for each format of data and Json4s handles everything perfectly, e.g.:
object Item{
def apply(
itemType: String,
firstProperties: SomeComplexType
): Item = FirstItem(itemType, firstProperties)
def apply(
itemType: String,
secondProperties: SomeOtherComplexType,
secondName: String, secondSize: Int
): Item = SecondItem(itemType, secondProperties, secondName, secondSize)
}

idiomatic scala/FP way of expressing similar value objects

I am wondering what is the idiomatic way to express similar messages (value objects)
Let's say I have some messages that I want to send : UserEnter, UserLeft, UserSendGreeting etc'. I hope that the following can express my dilemma
there are two options that I think one can express it :
trait UserAction
case class UserEnter (username:String,hour:Long,day:WeekDay,room:Int)
case class UserLeft (username:String,hour:Long,day:WeekDay,room:Int)
case class UserSendGreeting (username:String,,hour:Long,day:WeekDay,greeting:String)
def foo(userActions:List[UserAction]) = {
userActions match {
case UserEnter(userName)::tail =>
sendWelcomeMessage(userName)
handleOtherActions(tail)
case UserLeft::tail => "must enter first"
case _ => "do something else"
}
another way is :
trait Action
case class Enter(room:Int) extends Action
case object Left(room:Int) extends Action
case object Greet(msg:String) extends Action
case class UserAction(username:String,action:Action,,hour:Long,day:WeekDay)
def foo(userActions:List[UserAction]) = {
userActions match {
case head::tail if head.action == Enter =>
sendWelcomeMessage(head.userName)
handleOtherActions(tail)
case head::tail if head.action == Left => "must enter first"
case _ => "do something else"
}
The 1st option is more explicit and easier to pattern match but more repetitive code.
The other one is more explicit on the action itself but pattern match is more verbose
which way is better ?
Don't overcomplicate things. If you need to have a pair of User (in your case string name) and Action, use Tuple. Also it makes sense to gather all "...other attributes" inside corresponding Action.
type User = String
sealed trait Action
case class Enter(room:Int) extends Action
case class Left(room:Int) extends Action
case class Greet(msg:String) extends Action
def foo(userActions:List[(User, Action)]) = userActions match {
case (username, _:Enter)::tail =>
sendWelcomeMessage(username)
handleOtherActions(tail)
case (username, _:Left)::tail => "must enter first"
case _ => "do something else"
}
Also don't forget, that pattern matching allows you to unapply hierarchies of arbitrary depth:
case (username, Enter(room))::tail =>
I think the 1 way is better but you can make a constraint on UserAction trait to have username field:
trait UserAction {
def username: String
}
case class UserEnter(username:String, room:Int)
case class UserLeft(username:String, room:Int)
case class UserSendGreeting(username:String, greeting:String)
This way it is easy to
1) pattern match
2) use generic UserAction to get username field
How about: case class UserAction(user: User, action: Action)
def foo(actions: Seq[UserActions]) = actions match {
case UserAction(user, _: EnterAction)::tail =>
processEnter(user)
handleOtherActions(tail)
case UserExit(user, _: ExitAction)::tail => processExit(user)
...
}
Or even better
def foo(actions: Seq[UserAction]) = actions.find { ua =>
!ua.action.process(ua.user)
}
Or, maybe, just
def foo(action: Seq[UserAction]) = actions.find { !_.process }

How to improve the code of "nested Try.. match "?

In my scala code, I have some nested Try() match {}, which look ugly:
import scala.util._
Try(convertJsonToObject[User]) match {
case Success(userJsonObj) =>
Try(saveToDb(userJsonObj.id)) match {
case Success(user) => Created("User saved")
case _ => InternalServerError("database error")
}
case _ => BadRequest("bad input")
}
Is there any better way of writing such code?
There's a bunch of ways to solve this problem. I'll give you one possibility. Consider this cleaned up version of your code:
trait Result
case class BadRequest(message:String) extends Result
case class InternalServerError(message:String) extends Result
case class Created(message:String) extends Result
def processRequest(json:String):Result = {
val result =
for{
user <- Try(parseJson(json))
savedUser <- Try(saveToDb(user))
} yield Created("saved")
result.recover{
case jp:JsonParsingException => BadRequest(jp.getMessage)
case other => InternalServerError(other.getMessage)
}.get
}
def parseJson(json:String):User = ...
def saveToDb(user:User):User = ...
The caveat to this code is that it assumes that you can differentiate the json parsing failure from the db failure by the exception each might yield. Not a bad assumption to make though. This code is very similar to a java try/catch block that catches different exception types and returns different results based on catching those different types.
One other nice thing about this approach is that you could just define a standard recovery Partial Function for all kinds of possible exceptions and use it throughout your controllers (which I'm assuming this code is) to eliminate duplicate code. Something like this:
object ExceptionHandling{
val StandardRecovery:PartialFunction[Throwable,Result] = {
case jp:JsonParsingException => BadRequest(jp.getMessage)
case sql:SQLException => InternalServerError(sql.getMessage)
case other => InternalServerError(other.getMessage)
}
}
And then in your controller:
import ExceptionHandling._
result.recover(StandardRecovery).get
Another approach is to define implicit reads for User (if using Play Framework) and then doing something like
someData.validate[User].map { user =>
saveToDb(user.id) match { // you can return Try from saveToDb
case Success(savedUser) => Created("User saved")
case Failure(exception) => InternalServerError("Database Error")
}
}.recoverTotal {
e => BadRequest(JsError.toFlatJson(e))
}
Try(convertJsonToObject[User]).map([your code]).toOption.getOrElse(fallback)

How to implement receive () in Akka Actor

I am in the process of converting Akka UntypedActors in Java code to their Scala equivalent.
However, I am having trouble understanding how to correctly implement the receive() abstract method. The ScalaDoc is a little confusing and most of the examples I see just involve String messages!
My Actor can support multiple message types and this is my solution so far:
override def receive = {
case message if message.isInstanceOf[ClassOne] => {
// do something after message.asInstanceOf[ClassOne]
}
case message if message.isInstanceOf[ClassTwo] => {
// do something after message.asInstanceOf[ClassTwo]
}
case message => unhandled(message)
}
Is there a better way to achieve the above?
override def receive = {
case c: ClassOne =>
// do something after message.asInstanceOf[ClassOne]
case c: ClassTwo =>
// do something after message.asInstanceOf[ClassTwo]
case message => unhandled(message)
}
If you're using case classes, you can get more sophisticated.
case class ClassOne(x: Int, y: String)
case class ClassTwo(a: Int, b: Option[ClassOne])
override def receive = {
case ClassOne(x, y) =>
println(s"Received $x and $y")
case ClassTwo(a, Some(ClassOne(x, y)) if a == 42 =>
// do something
case ClassTwo(a, None) =>
case c # ClassOne(_, "foo") => // only match if y == "foo", now c is your instance of ClassOne
}
All sorts of fun stuff.
receive's type is really just a PartialFunction[Any,Unit], which means you can use Scala's pattern match expressions - in fact, you're already doing it, just not entirely succinctly. A terser equivalent that would also let you handle the type of the match for each case:
def receive = {
case classOneMessage : ClassOne => {
// do something
}
case classTwoMessage : ClassTwo => {
// do something
}
case _ => someCustomLogicHereOtherWiseThereWillBeASilentFailure
//you can, but you don't really need to define this case - in Akka
//the standard way to go if you want to process unknown messages
//within *this* actor, is to override the Actor#unhandled(Any)
//method instead
}
Read the tour article, and the already-linked tutorial for more info on pattern matching, especially in the context of using the feature together with case classes - this pattern is applied regularly when working with Akka, for example here in the Akka manual when handling the ActorIdentity case class.
receive is a regular partial function in Scala. You can write something like this in your case:
case class Example(number: Int, text: String)
override def receive = {
case message: ClassOne =>
// do something with ClassOne instance
case message: ClassTwo =>
// do something with ClassTwo instance
case Example(n, t) =>
println(t * n)
case Example(n, t) if n > 10 =>
println("special case")
}
You don't have to include a special case for unhandled messages unless your application logic requires you to handle all possible messages.
First two cases just match by type of a message and subtypes will be matched as well. Last one not only matches the type Example but also "deconstructs" it using pattern matching.

scala style - how to avoid having lots of nested map

Very often i end up with lots of nested .map and .getOrElse when validating several consecutives conditions
for example:
def save() = CORSAction { request =>
request.body.asJson.map { json =>
json.asOpt[Feature].map { feature =>
MaxEntitiyValidator.checkMaxEntitiesFeature(feature).map { rs =>
feature.save.map { feature =>
Ok(toJson(feature.update).toString)
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Error creating feature entity")
))
}
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "You have already reached the limit of feature.")
))
}
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Invalid feature entity")
))
}
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Expecting JSON data")
))
}
}
You get the idea
I just wanted to know if there's some idiomatic way to keep it more clear
If you hadn't had to return a different message for the None case this would be an ideal use-case for for comprehension. In your case , you probably want to use the Validation monad, as the one you can find in Scalaz. Example ( http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html ).
In functional programming, you should not throw exceptions but let functions which can fail return an Either[A,B], where by convention A is the type of result in case of failure and B is the type of result in case of success. You can then match against Left(a) or Right(b) to handle, reespectively, the two cases.
You can think of the Validation monad as an extended Either[A,B] where applying subsequent functions to a Validation will either yield a result, or the first failure in the execution chain.
sealed trait Validation[+E, +A] {
import Scalaz._
def map[B](f: A => B): Validation[E, B] = this match {
case Success(a) => Success(f(a))
case Failure(e) => Failure(e)
}
def foreach[U](f: A => U): Unit = this match {
case Success(a) => f(a)
case Failure(e) =>
}
def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = this match {
case Success(a) => f(a)
case Failure(e) => Failure(e)
}
def either : Either[E, A] = this match {
case Success(a) => Right(a)
case Failure(e) => Left(e)
}
def isSuccess : Boolean = this match {
case Success(_) => true
case Failure(_) => false
}
def isFailure : Boolean = !isSuccess
def toOption : Option[A] = this match {
case Success(a) => Some(a)
case Failure(_) => None
}
}
final case class Success[E, A](a: A) extends Validation[E, A]
final case class Failure[E, A](e: E) extends Validation[E, A]
Your code now can be refactored by using the Validation monad into three validation layers. You should basically replace your map with a validation like the following:
def jsonValidation(request:Request):Validation[BadRequest,String] = request.asJson match {
case None => Failure(BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Expecting JSON data")
)
case Some(data) => Success(data)
}
def featureValidation(validatedJson:Validation[BadRequest,String]): Validation[BadRequest,Feature] = {
validatedJson.flatMap {
json=> json.asOpt[Feature] match {
case Some(feature)=> Success(feature)
case None => Failure( BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Invalid feature entity")
)))
}
}
}
And then you chain them like the following featureValidation(jsonValidation(request))
This is a classic example of where using a monad can clean up your code. For example you could use Lift's Box, which is not tied to Lift in any way. Then your code would look something like this:
requestBox.flatMap(asJSON).flatMap(asFeature).flatMap(doSomethingWithFeature)
where asJson is a Function from a request to a Box[JSON] and asFeature is a function from a Feature to some other Box. The box can contain either a value, in which case flatMap calls the function with that value, or it can be an instance of Failure and in that case flatMap does not call the function passed to it.
If you had posted some example code that compiles, I could have posted an answer that compiles.
I tried this to see if pattern matching offered someway to adapt the submitted code sample (in style, if not literally) to something more coherent.
object MyClass {
case class Result(val datum: String)
case class Ok(val _datum: String) extends Result(_datum)
case class BadRequest(_datum: String) extends Result(_datum)
case class A {}
case class B(val a: Option[A])
case class C(val b: Option[B])
case class D(val c: Option[C])
def matcher(op: Option[D]) = {
(op,
op.getOrElse(D(None)).c,
op.getOrElse(D(None)).c.getOrElse(C(None)).b,
op.getOrElse(D(None)).c.getOrElse(C(None)).b.getOrElse(B(None)).a
) match {
case (Some(d), Some(c), Some(b), Some(a)) => Ok("Woo Hoo!")
case (Some(d), Some(c), Some(b), None) => BadRequest("Missing A")
case (Some(d), Some(c), None, None) => BadRequest("Missing B")
case (Some(d), None, None, None) => BadRequest("Missing C")
case (None, None, None, None) => BadRequest("Missing D")
case _ => BadRequest("Egads")
}
}
}
Clearly there are ways to write this more optimally; this is left as an exercise for the reader.
I agree with Edmondo suggestion of using for comprehension but not with the part about using a validation library (At least not anymore given the new features added to scala standard lib since 2012). From my experience with scala, dev that struggle to come up with nice statement with the standard lib will also end up doing the same of even worst when using libs like cats or scalaz. Maybe not at the same place, but ideally we would solve the issue rather than just moving it.
Here is your code rewritten with for comprehension and either that is part of scala standard lib :
def save() = CORSAction { request =>
// Helper to generate the error
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
//Actual validation
val updateEither = for {
json <- request.body.asJson.toRight(badRequest("Expecting JSON data"))
feature <- json.asOpt[Feature].toRight(badRequest("Invalid feature entity"))
rs <- MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.toRight(badRequest("You have already reached the limit"))
} yield toJson(feature.update).toString
// Turn the either into an OK/BadRequest
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
Explanations
Error handling
I'm not sure how much you know about either but they are pretty similar in behaviour as Validation presented by Edmondo or Try object from the scala library. Main difference between those object regard their capability and behaviour with errors, but beside that they all can be mapped and flat mapped the same way.
You can also see that I use toRight to immediately convert the option into Either instead of doing it at the end. I see that java dev have the reflex to throw exception as far as they physically can, but they mostly do so because the try catch mechanism is unwieldy: in case of success, to get data out of a try block you either need to return them or put them in a variable initialized to null out of the block. But this is not the case is scala: you can map a try or an either, so in general, you get a more legible code if you turn results into error representation as soon as have identified it as they are identified as incorrect.
For comprehension
I also know that dev discovering scala are often quite puzzled by for comprehension. This is quite understandable as in most other language, for is only used for iteration over collections while is scala, it seem to use usable on a lot of unrelated types. In scala for is actually more nicer way to call the function flatMap. The compiler may decide to optimize it with map or foreach but it remain correct assume that you will get a flatMap behavior when you use for.
Calling flatMap on a collection will behave like the for each would in other language, so scala for may be used like a standard for when dealing with collection. But you can also use it on any other type of object that provide an implementation for flatMap with the correct signature. If your OK/BadRequest also implement the flatMap, you may be able to use in directly in the for comprehension instead of usong an intermediate Either representation.
For the people are not at ease with using for on anything that do not look like a collection, here is is how the function would look like if explicitly using flatMap instead of for :
def save() = CORSAction { request =>
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
val updateEither = request.body.asJson.toRight(badRequest("Expecting JSON data"))
.flatMap { json =>
json
.asOpt[Feature]
.toRight(badRequest("Invalid feature entity"))
}
.flatMap { feature =>
MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.map(_ => feature)
.toRight(badRequest("You have already reached the limit"))
}
.map { rs =>
toJson(feature.update).toString
}
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
Note that in term of parameter scope, for behave live if the function where nested, not chained.
Conclusion
I think that more than not using the right framework or the right language feature, the main issue with the code your provided is how errors are dealt with. In general, you should not write error paths as after thought that you pile up at the end of the method. If you can deal with the error immediately as they occur, that allow you to move to something else. On the contrary, the more you push them back, the more you will have code with inextricable nesting. They are actually a materialization of all the pending error cases that scala expect you to deal with at some point.