Akka & Case Statements - scala

I'm learning Akka for Scala, and have also been reading up on Scala's pattern matching/case statements.
In Akka, I can write an actor as follows:
class MyActor extends Actor {
def receive: Receive = {
case msg: MyMsgClass => sender ! "message received!"
}
}
Questions:
Is this a case of Scala's pattern matching? If so, why is there no corresponding match keyword?
In the case line, is msg an identifier and is it required? What happens if I omit it and just use the class name (and presumably if I provide no identifier, I can't use the variable itself)?

It is a case of pattern matching, in combination with a Partial Function. In short, the partial function
{
case msg: MyMsgClass
}
only matches if there is an argument of type MyMsgClass. It handles a 'partial subset' of possible values. This syntax generates a PartialFunction object that handles the case where the input is a MyMsgClass.
You can also write:
{
case MyMsgClass(value) => sender ! value
}
but in this case you only get the value. You can even do complex things like:
{
case m # MyMsgClass(AnotherClass(_), "this must be this string", a) => sender ! doSomething(m, a)
}
and it will nicely match only MyMsgClass objects that have a first parameter of type AnotherClass (regardless of its parameters, hence the _), the exact string "this must be this string", and a value a. The m # syntax denotes that the object itself is also used, not just as a type.
Some more info: http://blog.bruchez.name/2011/10/scala-partial-functions-without-phd.html

You can see from the Scaladocs for Actor that Receive is defined as having type: type
Receive = PartialFunction[Any, Unit], which effectively means that it's an abstract member defined as being a partial function.

Related

Scala pattern matching not working with Option[Seq[String]] [duplicate]

This question already has answers here:
How do I get around type erasure on Scala? Or, why can't I get the type parameter of my collections?
(11 answers)
Closed 3 months ago.
I am new to Scala(2.13.8) and working on code to use pattern matching to handle a value in different ways, code is very simply like below
def getOption(o: Option[Any]): Unit = {
o match {
case l: Some[List[String]] => handleListData(l)
case _ => handleData(_)
}
}
getOption(Some(3))
getOption(Some(Seq("5555")))
The result is handleListData() been invoked for both input. Can someone help on what's wrong in my code?
As sarveshseri mentioned in the comments, the problem here is caused by type erasure. When you compile this code, scalac issues a warning:
[warn] /Users/tmoore/IdeaProjects/scala-scratch/src/main/scala/PatternMatch.scala:6:15: non-variable type argument List[String] in type pattern Some[List[String]] is unchecked since it is eliminated by erasure
[warn] case l: Some[List[String]] => handleListData(l)
[warn] ^
This is because the values of type parameters are not available at runtime due to erasure, so this case is equivalent to:
case l: Some[_] => handleListData(l.asInstanceOf[Some[List[String]]])
This may fail at runtime due to an automatically-inserted cast in handleListData, depending on how it actually uses its argument.
One thing you can do is take advantage of destructuring in the case pattern in order to do a runtime type check on the content of the Option:
case Some(l: List[_]) => handleListData(l)
This will work with a handleListData with a signature like this:
def handleListData(l: List[_]): Unit
Note that it unwraps the Option, which is most likely more useful than passing it along.
However, it does not check that the List contains strings. To do so would require inspecting each item in the list. The alternative is an unsafe cast, made with the assumption that the list contains strings. This opens up the possibility of runtime exceptions later if the list elements are cast to strings, and are in fact some other type.
This change also reveals a problem with the second case:
case _ => handleData(_)
This does not do what you probably think it does, and issues its own compiler warning:
warn] /Users/tmoore/IdeaProjects/scala-scratch/src/main/scala/PatternMatch.scala:7:28: a pure expression does nothing in statement position
[warn] case _ => handleData(_)
[warn] ^
What does this mean? It's telling us that this operation has no effect. It does not invoke the handleData method with o as you might think. This is because the _ character has special meaning in Scala, and that meaning depends on the context where it's used.
In the pattern match case _, it is a wildcard that means "match anything without binding the match to a variable". In the expression handleData(_) it is essentially shorthand for x => handleData(x). In other words, when this case is reached, it evaluates to a Function value that would invoke handleData when applied, and then discards that value without invoking it. The result is that any value of o that doesn't match the first case will have no effect, and handleData is never called.
This can be solved by using o in the call:
case _ => handleData(o)
or by assigning a name to the match:
case x => handleData(x)
Returning to the original problem: how can you call handleListData only when the argument contains a List[String]? Since the type parameter is erased at runtime, this requires some other kind of runtime type information to differentiate it. A common approach is to define a custom algebraic data type instead of using Option:
object PatternMatch {
sealed trait Data
case class StringListData(l: List[String]) extends Data
case class OtherData(o: Any) extends Data
def handle(o: Data): Unit = {
o match {
case StringListData(l) => handleListData(l)
case x => handleData(x)
}
}
def handleListData(l: List[String]): Unit = println(s"Handling string list data: $l")
def handleData(value: Any): Unit = println(s"Handling data: $value")
def main(args: Array[String]): Unit = {
PatternMatch.handle(OtherData(3))
PatternMatch.handle(StringListData(List("5555", "6666")))
PatternMatch.handle(OtherData(List(7777, 8888)))
PatternMatch.handle(OtherData(List("uh oh!")))
/*
* Output:
* Handling data: OtherData(3)
* Handling string list data: List(5555, 6666)
* Handling data: OtherData(List(7777, 8888))
* Handling data: OtherData(List(uh oh!))
*/
}
}
Note that it's still possible here to create an instance of OtherData that actually contains a List[String], in which case handleData is called instead of handleListData. You would need to be careful not to do this when creating the Data passed to handle. This is the best you can do if you really need to handle Any in the default case. You can also extend this pattern with other special cases by creating new subtypes of Data, including a case object to handle the "empty" case, if needed (similar to None for Option):
case object NoData extends Data
// ...
PatternMatch.handle(NoData) // prints: 'Handling data: NoData'

Matching against Value Classes in Akka

I've created the Value Class
final class Feature(val value: Vector[Double]) extends AnyVal
To match against that class in scala:
val d = new Feature(Vector(1.1))
s match {
case a:Feature => println(s"a:feature, $a")
case _ => println("_")
}
This works correctly, but in Akka, with the same class above, in the receive method this is not working:
def receive = LoggingReceive {
case a:Feature =>
log.info("Got Feature: {}", a)
case _ => println("_")
}
When I execute the code, although I am sending a Feature, the case statement that is being executed is case _ => println("_"), but, If I change the code to this:
def receive = LoggingReceive {
case a:Feature =>
log.info("Got Feature: {}", a)
case b:Vector[_] =>
log.info("GOT FEATURE")
case _ => println("_")
}
case b:Vector[_] is executed.
Akka documentation mentions:
The recommended way to instantiate actor props uses reflection at runtime to determine the correct actor construc-
tor to be invoked and due to technical limitations is not supported when said constructor takes arguments that are
value classes. In these cases you should either unpack the arguments or create the props by calling the constructor
manually:
But do not mention nothing about matching against Value classes
Update
Thanks to YuvalItzchakov for the help. The Actor's code is as below:
Actor that receive the message:
def receive = LoggingReceive {
case Feature(a) =>
log.info("Got feature {}", a)
// ....
}
Actor sending the message:
def receive = LoggingReceive {
// ..
case json: JValue =>
log.info("Getting json response, computing features...")
val features = Feature(FeatureExtractor.getFeatures(json))
log.debug(s"Features: $features")
featureListener.get ! features
// ..
}
Due to the nature of how value classes work, both your examples will cause the allocation of Feature. Once due to run-time checking in your pattern matching example, and the other due to the signature of receive which requires Any as the input type.
As the docs for Value Classes specify (emphasis mine):
Allocation Summary
A value class is actually instantiated when:
a value class is treated as another type.
a value class is assigned to an array.
doing runtime type tests, such as pattern matching.
This means that if you're seeing a Vector[_] type, that means your actually passing a concrete vector from some place in the code.

How can I use the pattern matching result in the List of matching type?

I've found some weird behaviour in Akka. When I matching the pattern, I cannot add a message to the list:
var msgs: List[Message] = Message() :: Nil
...
override def receive: Receive = {
case msg # Message => {
msgs = msgs.::(Message()) // ok
//msgs = msgs.::(msg) // doesn't work
sender ! "Thanks!"
}
case Request => { sender ! msgs.head }
case _ =>
}
Is this a bug in Scala or Akka?
To fix it I need to case the type
msgs = msgs.::(msg.asInstanceOf[Message])
which is not a convenient solution.
The problem is that you are making a subtle mistake in the matching happening in your case statement. You are matching there against the Message companion object, not the Message case class.
Another way of looking at it is that the case x # Y syntax can be thought of as saying "match with any instance of type Y, and then run the equivalent of val x: Y = <incoming value>.asInstanceOf[Y]", but here the inferred type you are providing is a parameter-less type called Message, which the compiler takes to be object Message, not case class Message().
So, to fix the line, write in the parameter list. For example, if the Message class is defined as:
case class Message()
this will be:
case msg # Message() => ...
If instead we had, say:
case class Message(text: String, id: Int)
then the case statement becomes something like:
case msg # Message(txt, is) => ...
or, if we don't care (or need to use) the text and id parameters:
case msg # Message(_, _) => ...
For a slightly more technically correct description of what is happening here, the case statement is actually attempting a match with any "unapply" methods available in the companion object (also called "extractors"). By default, there will be both an apply and an unapply method provided for free in the companion object for any case class that match exactly the parameter list provided in the case class constructor. In the second version of the Message class above, the relevant unapply method will have a signature like:
def unapply(text: String, id: Int): Option[Message]
You can read more about extractors here and here.

Scala: pattern matching Option with case classes and code blocks

I'm starting to learn the great Scala language ang have a question about "deep" pattern matching
I have a simple Request class:
case class Request(method: String, path: String, version: String) {}
And a function, that tries to match an request instance and build a corresponding response:
def guessResponse(requestOrNone: Option[Request]): Response = {
requestOrNone match {
case Some(Request("GET", path, _)) => Response.streamFromPath(path)
case Some(Request(_, _, _)) => new Response(405, "Method Not Allowed", requestOrNone.get)
case None => new Response(400, "Bad Request")
}
}
See, I use requestOrNone.get inside case statement to get the action Request object. Is it type safe, since case statement matched? I find it a bit of ugly. Is it a way to "unwrap" the Request object from Some, but still be able to match Request class fields?
What if I want a complex calculation inside a case with local variables, etc... Can I use {} blocks after case statements? I use IntelliJ Idea with official Scala plugin and it highlights my brackets, suggesting to remove them.
If that is possible, is it good practice to enclose matches in matches?
... match {
case Some(Request("GET", path, _)) => {
var stream = this.getStream(path)
stream match {
case Some(InputStream) => Response.stream(stream.get)
case None => new Response(404, "Not Found)
}
}
}
For the first part of your question, you can name the value you match against with # :
scala> case class A(i: Int)
defined class A
scala> Option(A(1)) match {
| case None => A(0)
| case Some(a # A(_)) => a
| }
res0: A = A(1)
From the Scala Specifications (8.1.3 : Pattern Binders) :
A pattern binder x#p consists of a pattern variable x and a pattern p.
The type of the variable x is the static type T of the pattern p. This
pattern matches any value v matched by the pattern p, provided the
run-time type of v is also an instance of T , and it binds the
variable name to that value.
However, you do not need to in your example: since you're not matching against anything about the Request but just its presence, you could do :
case Some(req) => new Response(405, "Method Not Allowed", req)
For the second part, you can nest matches. The reason Intellij suggests removing the braces is that they are unnecessary : the keyword case is enough to know that the previous case is done.
As to whether it is a good practice, that obviously depends on the situation, but I would probably try to refactor the code into smaller blocks.
You can rewrite the pattern as following (with alias).
case Some(req # Request(_, _, _)) => new Response(405, "Method Not Allowed", req)
You cannot use code block in pattern, only guard (if ...).
There are pattern matching compiler plugin like rich pattern matching.

Need clarification on Scala literal identifiers (backticks)

Reading the Programming in Scala 2nd Ed and I came across this:
literal identifier
"The idea is that you can put any string that's accepted by the runtime as an identifier between backtick"
I'm not entirely sure why I would use this? The book gave a use case of accessing the static yield method in Java's Thread class.
So since in Scala, yield is a reserve word, if I use yield with backticks,
Thread.`yield`()
it would ignore the Scala's yield and let me access the Java's Thread class's method yield instead?
Thank you in advance.
Exactly. Using backticks, you can more or less give any name to a field identifier. In fact, you can even say
val ` ` = 0
which defines a variable with name (one character of whitespace).
The literal definition of identifiers is useful in two cases. The first case is, when there is already a reserved word of the same name in Scala and you need to use a Java library which does not care about that (and of course, why should it).
The other use case comes with case statements. The convention is that lower case names refer to match variables, whereas upper case names refer to identifiers from the outer scope. So,
val A = "a"
val b = "b"
"a" match {
case b => println("b")
case A => println("A")
}
prints "b" (if the compiler were dumb enough not to fail with saying case A were unreachable). If you want to refer to the originally defined val b, you need to use backticks as a marker.
"a" match {
case `b` => println("b")
case A => println("A")
}
Which prints "A".
Add There is a more advanced use case in this recent question method with angle brackets (<>) where the backticks were needed to get the compiler to digesting the code for a setter method (which in itself uses some ‘magic’ syntax).
Thank you #Debilski, it helps me to understand this code below from AKKA doc :
class WatchActor extends Actor {
val child = context.actorOf(Props.empty, "child")
...
def receive = {
...
case Terminated(`child`) ⇒ ...
}
}
The case :
case Terminated(`child`)
matches a message of type Terminated with ActorRef field equals to child which is defined earlier.
With this statement :
case Terminated(c)
We match every Terminated messages with any reference of ActorRef mapped in c.