Understanding Scala default argument message - scala

I am playing around with some Scala code and have met with an error message I don't quite follow. Below is my code
val ignoredIds = Array("one", "two", "three")
def csNotify(x : Any): String = {
case org: String if !ignoredIds.contains(x) =>
println( s" $x should not be here")
"one"
case org : String if ignoredIds.contains(x) =>
println(s"$x should be here")
"two"
}
csNotify("four")
The console output is that I am the arguments for a default function must be known. The error point appears to be pointing at the " String = ". Why would this be the case ? The function should check the two cases and return a string ?

Your case is not finding the match against which it can check your block , and you have missed the match block:
val ignoredIds = Array("one", "two", "three")
def csNotify(x : Any): String = x match {
case org: String if !ignoredIds.contains(x) =>
println( s" $x should not be here")
"one"
case org : String if ignoredIds.contains(x) =>
println(s"$x should be here")
"two"
}
csNotify("four")
So basically when you pass x in method , you have to give it for match as well.

Amit Prasad's answer already shows how to fix it, but to explain the error message:
{
case org: String if !ignoredIds.contains(x) =>
println( s" $x should not be here")
"one"
case org : String if ignoredIds.contains(x) =>
println(s"$x should be here")
"two"
}
on its own (without ... match before it) is a pattern-matching anonymous function, which can only be used where the compiler knows the argument type from the context, i.e. the expected type must be either PartialFunction[Something, SomethingElse] or a single-abstract-method type (including Something => SomethingElse).
Here the expected type is String, which isn't either of those, so the compiler complains about not knowing what the argument type is.

You need to use match keyword here to use cases. There might be some value for which you will be using pattern matching. So use the following code in your function:
x match {
case org: String if !ignoredIds.contains(x) => ???
case org : String if ignoredIds.contains(x) => ???
}
Also, you should consider adding one more case which is default. As you know the parameter x of your function def csNotify(x: Any): String is of type any. So anything other than String can also be passed here like Int or Boolean or any custom type. In that case, the code will break with match error.
There will also be a compiler warning saying match is not exhaustive as the current code does not handle all possible values for type Any of parameter x.
But if you add one default case in your pattern matching, all the cases which are not handled by the first two cases (unexpected type or values) will go to the default case. In this way the code will be more robust:
def csNotify(x : Any): String = x match {
case org: String if !ignoredIds.contains(org) => ???
case org : String if ignoredIds.contains(org) => ???
case org => s"unwanted value: $org" // or any default value
}
Note: Kindly replace ??? with your intended code. :)

Related

Typecasting in Scala

I have an alphanumeric field in an RDD of type AnyRef.
Case1: If it's 99898, I want to cast it as Long
Case2: If it's 0099898, I want to cast it as String
Case3: If it's AB998, I want to cast it as String.
I am trying this:
try {
account_number.asInstanceOf[ Long ])
} catch {
case _: Throwable => account_number.asInstanceOf[ String ])
}
But in this, I miss the case2, because 0099898 is converted to 99898. Any ideas?
If this field is AnyRef I wouldn't expect AnyVals there at all (like Long) - Scala's numbers are not equal to Java's numbers. At best you can have there some instance of java.lang.Numeric (e.g. java.lang.Long which is NOT scala.Long).
But to turn it into Long you would have to use pattern matching (with type matching or regexp pattern matching) and conversion (NOT casting!) to
val isStringID = raw"(0[0-9]+)".r
val isLongID = raw"([0-9]+)".r
account_number match {
case isStringID(id) => id // numeric string starting with 0
case isLongID(id) => id.toLong // numeric string convertible to Long
case l: java.lang.Long => l.toLong // Java's long
case _ => throw new IllegalArgumentException("Expected long or numeric string")
}
However, I would find that completely useless - right now you have Any instead of AnyVal. You could expect it to have Long or String but it's not represented by the returned value so compiler would NOT have any information about the safe usages. Personally, I would recommend doing something imediatelly after matching e.g. wrapping it with Either or creating ADT or passing it to function which needs String or Long.
// can be exhaustively pattern matched, or .folded or passed, etc
val stringOrLong: Either[String, Long] = account_number match {
case isStringID(id) => Left(id)
case isLongID(id) => Right(id.toLong)
case l: java.lang.Long => Right(l.toLong)
case _ => throw new IllegalArgumentException("Expected long or numeric string")
}
You cannot use .asInstanceOf to turn AnyRef to Long because neither is subtype or supertype of another, and this operation would always fail.
Any
/ \
AnyVal AnyRef
| |
Long |
\ /
Nothing
.asInstanceOf would only make sense if you were moving vertically in this hierarchy, not horizontally.
Another option you have is:
def tryConvert(s: String): Either[Long, String] = {
Try(s.toLong).filter(_.toString == s) match {
case Success(value) =>
Left(value)
case Failure(_) =>
Right(s)
}
}
Code run at Scastie.

Scala methods with generic parameter type

I have been working with Scala for close to a year, but every now and then I come across a piece of code that I don't really understand. This time it is this one. I tried looking into documents on "scala methods with generic parameter type", but I am still confused.
def defaultCall[T](featureName : String) (block : => Option[T])(implicit name: String, list:Seq[String]) : Option[T] =
{
val value = block match {
case Some(n) => n match {
case i : Integer => /*-------Call another method----*/
case s : String => /*--------Call another method----*/
}
case _ => None
}
The method is called using the code shown below :
var exValue = Some(10)
val intialization = defaultCall[Integer]("StringName"){exValue}
What I don't understand in the above described code is the "case" statement in the defaultCall method.
I see that when the exValue has a value and is not empty, the code works as expected. But in case I change the exValue to None, then my code goes into the "case _ = None" condition. I don't understand why this happens since the match done here is against the "variable" which would be either an Integer or a String.
What happens here is that when you pass a None it will match on the second case, which "catches" everything that is not an instance of a Some[T]:
block match {
case Some(n) => // Will match when you pass an instance of Some[T]
case _ => // Will match on any other case
}
Note that None and Some are two different classes that inherit from Option.
Also, the variable match is only done if the first match succeeds, otherwise not. To achieve the type checking in the first match you could do:
block match {
case Some(n: Int) => // do stuff
case Some(n: String) => // do stuff
case _ => // Will match on any other case
}
Hope that helps

Why scala cannot infer common return type for a function with multiple parameter lists?

Scala can infer the common base of the two parameters to a function, T = Option[Int] is found from Some[Int] and None:
def handleUndefined1[T](op: => T, alt: => T) : T = {
try { op }
catch { case _: Exception => alt }
}
val x = handleUndefined1(Some(1),None) // Option[Int]
But as a partial function:
def handleUndefined2[T](op: => T)(alt: => T) : T = {
try { op }
catch { case _: Exception => alt }
}
// None is not Some[Int]
val x = handleUndefined2(Some(1))(None)
First, it's not a "partial function" what you're doing is called a "curried function."
Second, try the following:
val x = handleUndefined1(Some(1), _)
and see what happens. You'll get a warning saying that you're missing a type parameter on the 2nd parameter. Why?
The type inferencer goes from left to right. Generally, types in the first argument group will determine those in the 2nd, 3rd, etc. If you leave out a type parameter, the inferencer will not be able to determine the type it should become as you've left out information.
Use Option rather than Some:
val x = handleUndefined2(Option(1))(None)
Alternatively you can coerce the Some to Option:
val x = handleUndefined2(Some(1): Option[Int])(None)
Generally it's a good idea to always use Option rather than Some for this reason.

Is it possible to pattern match on type in a Scala for expression?

I'm trying to use a for expression to map over an Option, but I only want to match if the contents of the Option are of a specific type. What I thought would work is this:
for {
vcs: Mercurial <- maybeVcs
} yield vcs
But that yields the following compile error:
<console>:76: error: type mismatch;
found : sbtrelease.Mercurial => sbtrelease.Mercurial
required: sbtrelease.Vcs => ?
vcs: Mercurial <- get (releaseVcs in Compile)
^
Is it possible to pattern match on type in a for expression?
It's really straightforward if you use collect instead of for:
trait A
case class B(x: Int) extends A
case class C(y: Int) extends A
val someB: Option[A] = Some(B(2))
val someC: Option[A] = Some(C(2))
val noneA: Option[A] = None
someB.collect { case n: B => n } // Some(B(2))
someC.collect { case n: B => n } // None
noneA.collect { case n: B => n } // None
The fact that this pattern match does not work is actually a bug (at least its not in accordance with the spec). See https://issues.scala-lang.org/browse/SI-900.
However, there is a simple workaround.
Define somewhere the following object:
object Id { def unapply[T](x:T) = Some(x) }
Now you can use Id(x) as a pattern match that matches everything, and just binds x to whatever it matched. So basically a pointless construct, since Id(pattern) is the same as pattern.
However, it has one effect: A type annotation inside Id(...) will not be interpreted as a type annotation, but as a type pattern. Thus
for {
Id(vcs: Mercurial) <- maybeVcs
} yield vcs
will have the effect you desire. (And differently from Bob's answer, the overall expression will have type Seq[Mercurial] and not Seq[Vcs].)
You can use an ugly test:
for {
vcs <- maybeVcs
if vcs.instanceof[Mercurial]
} yield vcs

Extract same value type from pattern matching "or" case

This must be possible, yet I'm unable to find any examples or guidance online...
I am trying to extract a variable from an Either return where Left can have an Exception case class with the value I want OR a Right with the value I want.
Definitions:
def findInnerObj(innerObjId: String): Either[InnerObjNotFoundException, (OuterObj, InnerObj)] = ???
case class InnerObjNotFoundException(outer: OuterObj) extends Exception
Usage:
findInnerObj(innerObjId) match {
case Left(InnerObjNotFoundException(x)) | Right((x, _)) =>
// do something with x <-- ATM, compiler: "Cannot resolve symbol x"
}
Pattern alternatives with name binding are not supported, you can do it like this.
val innerObj = findInnerObj(innerObjId) match {
case Left(InnerObjNotFoundException(x)) => x
case Right((x, _)) => x
}
// do something with innerObj