There is a method in my code, which need to check if the passing string contain some specified chars, and then do something.
The code is looking like:
def check(str: String) = {
if(str.contains("A")) {
doSomething()
} else if(str.contains("B")) {
doSomething()
} else if(str.contains("C")) {
doSomething()
} else {
doSomething()
}
}
I want to try pattern match on it, but not very satisfied with:
def check(str: String) = str match {
case s if s.contains("A") => doSomething()
case s if s.contains("B") => doSomething()
case s if s.contains("C") => doSomething()
case _ => doSomething()
}
I hope I can define a StrContains.unapply to use it like this:
def check(str: String) = str match {
case StrContains("A") => doSomething()
case StrContains("B") => doSomething()
case StrContains("C") => doSomething()
case _ => doSomething()
}
But now sure how to do it. Any thoughts?
The problem is that when you do case StrContains("A"), the compiler will first call StrContains.unapply/unapplySeq (whichever way it is defined) and only then try to match the returned result against "A". The "A" literal itself will never be passed to StrContains.unapply/unapplySeq, so it has no way to perform the call s.contains("A") inside unapply/unapplySeq.
Simply put, this means you'd need to define distinct objects such as StrContainsA/StrContainsB/StrContainsC which is clearly a worse situation than simply doing case s if s.contains("A").
However, there is an alternative (and somewhat contrived) solution that allows to define a single extractor while still being able to specify inline the substring to match for, and that is to exploit the fact that scala supports defining extractors based on string interpolation:
implicit class ContainsContext (val sc : StringContext) {
object StrContains {
def unapply(str: String): Boolean = {
val substr: String = sc.parts.mkString
str.contains(substr)
}
}
}
Usage:
def check(str: String) = str match {
case StrContains"A" => doSomething()
case StrContains"B" => doSomething()
case StrContains"C" => doSomething()
case _ => doSomething()
}
Related
I have a method which returns Either[Exception, String]
class A {
def validate(a: Any) = {
case a: String => Left(...some.. exception)
case a: Any => Right(a)
}
}
class B(a: A) {
def callValidate(any: Any) = {
a.validate(any)
}
}
Now I write tests for class B and I stub method validate
class BTest {
val param: Any = "22"
val a = mock[A]
(a.validate _).expects(param).returning(....someValue...) // . this value should be Right(....) of either function.
}
Is it possible to stub it that way to return Right(.....) of Either function ?
As B is taking the object of a you can make a new object of A in BTest class and override the method validate to return whatever you want once return Right(a) and to cover the Left part return Left(a).
class BTest {
val param: Any = "22"
val a = new A{
override def validate(a:Any) = case _ => Right(a)
}
(a.validate _).expects(param).returning(Right("22"))
}
or you can do like this. As DarthBinks911 suggested.
(a.validate _).expects(param).returning(Right("a"))
this will work fine in the given scenario but if you do something like mockObject.something then it will give you NullPointerException. I would suggest you to override the validate method and return what ever you want.
I'm extending an abstract class from a library, and I want to override a function definition and use the superclass's definition as a fallback.
If the parent class's method were defined as a PartialFunction, I could just use orElse. Since it isn't, I'm doing the thing below (unlifting the parent function into a partial function so I can use orElse). It works, but it is one of those times when I suspect there is a better / more elegant way. Is there?
abstract class ThingICantChange {
def test:Int=>String = {
case 1 => "one"
case 2 => "two"
case _ => "unknown"
}
}
class MyClass extends ThingICantChange {
def myChanges:PartialFunction[Int,String] = {
case 2 => "mytwo"
case 3 => "three"
}
override def test = myChanges orElse Function.unlift(x => Some(super.test(x)))
}
I would do this:
class MyClass extends ThingICantChange {
override def test: Int => String = {
case 2 => "mytwo"
case 3 => "three"
case x => super.test(x)
}
}
If you're looking to construct a partial function from super.test then here's a clean way:
override def test = myChanges orElse PartialFunction(super.test)
Why can't I bind the variable in #-style when the extractor return Option[<Type>]? I.e. this one does not work:
object IsUpperCase {
def unapply(s: String): Option[String] = {
if (s.toUpperCase() == s) {
Some(s)
} else {
None
}
}
}
val s = "DuDu#qwadasd.ru"
s match {
case u # IsUpperCase() => println("gotcha!") // what? "wrong number of arguments for object IsUpperCase"?
case _ =>
}
But this one works!
val s = "DuDu#qwadasd.ru"
s match {
case IsUpperCase(u) => println("gotcha!")
case _ =>
}
From the other hand, if IsUpperCase looks like this:
object IsUpperCase {
def unapply(s: String): Boolean = {
return s.toUpperCase() == s
}
}
Then the first example works, and the second does not! Why is it this way?
what? "wrong number of arguments for object IsUpperCase"?
case u # IsUpperCase() => println("gotcha!")
Well, yes. The return type of unapply is Option[String], which means the pattern match of IsUpperCase must accept a parameter, like this:
case u # IsUpperCase(_) => println("gotcha!") // I don't care about the parameter
The unapply definition that fits the first pattern is this:
object IsUpperCase {
def unapply(s: String): Boolean = s.toUpperCase() == s
}
That can be used to pattern match against IsUpperCase().
Because for the first example you need to write something like case u # IsUpperCase(v) => or case u # IsUpperCase(_) =>, which means "match IsUpperCase(v) and if it succeeds bind the original string to u".
Say I have something like this:
obj match {
case objTypeOne : TypeOne => Some(objTypeOne)
case objTypeTwo : TypeTwo => Some(objTypeTwo)
case _ => None
}
Now I want to generalise, to pass in one of the types to match:
obj match {
case objTypeOne : clazz => Some(objTypeOne)
case objTypeTwo : TypeTwo => Some(objTypeTwo)
case _ => None
}
But this isn't allowed, I think for syntactic rather than semantic reasons (although I guess also that even though the clazz is a Class[C] the type is erased and so the type of the Option will be lost).
I ended up with:
if(clazzOne.isAssignableFrom(obj.getClass)) Some(clazz.cast(obj))
if(obj.isInstanceOf[TypeTwo]) Some(obj.asInstanceOf[TypeTwo])
None
I just wondered if there was a nicer way.
You could define an extractor to match your object:
class IsClass[T: Manifest] {
def unapply(any: Any): Option[T] = {
if (implicitly[Manifest[T]].erasure.isInstance(any)) {
Some(any.asInstanceOf[T])
} else {
None
}
}
}
So let's test it:
class Base { def baseMethod = () }
class Derived extends Base
val IsBase = new IsClass[Base]
def test(a:Any) = a match {
case IsBase(b) =>
println("base")
b.baseMethod
case _ => println("?")
}
test(new Base)
test(1)
You will have to define a val for your extractor, you can't inline IsBase, for example. Otherwise it would be interpreted as an extractor.
You could use pattern guards to achieve that. Try something like this:
obj match {
case objTypeTwo : TypeTwo => Some(objTypeTwo)
case objTypeOne if clazz.isAssignableFrom(objTypeOne.getClass) => Some(clazz.cast(objTypeOne))
case _ => None
}
You can use a local type alias for that:
def matcher[T](obj: Any)(implicit man: Manifest[T]) = {
val instance = man.erasure.newInstance.asInstanceOf[AnyRef]
type T = instance.type // type alias
obj match {
case objTypeOne : T => "a"
case objTypeTwo : TypeTwo => "b"
case _ => "c"
}
}
scala> matcher[TypeOne](TypeOne())
res108: java.lang.String = a
scala> matcher[TypeTwo](TypeOne())
res109: java.lang.String = c
UPDATE: Aaron Novstrup has pointed out that singleton type will only work if man.erasure.newInstance==obj (see ยง3.2.1 of the spec)
I tried to create an unapply method to use in pattern matching, and I tried to make it return something different than Option, however, Eclipse shows that as an error. Is it a rule that unapply must return an Option[T] ?
EDIT: here's the code I'm trying to use. I switched the code from the previous section so that unapply returns a Boolean
import java.util.regex._
object NumberMatcher {
def apply(x:String):Boolean = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
return matcher.find
}
def unapply(x:String):Boolean = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
return matcher.find
}
}
object x {
def main(args : Array[String]) : Unit = {
val strings = List("geo12","neo493","leo")
for(val str <- strings) {
str match {
case NumberMatcher(group) => println(group)
case _ => println ("no")
}
}
}
}
Eclipse says wrong number of arguments for object NumberMatcher. Why is that?
If you want to return something with unapply, return it inside Some. Returning Boolean just tests if the match can be made or not.
Here is how a pattern matching is translated:
str match {
case NumberMatcher(group) => println(group)
case _ => println("no")
}
Assuming NumberMatcher returns Option[...], it will do:
val r1 = NumberMatcher.unapply(str)
if (r1 != None) {
val group = r1.get
println(group)
} else {
println("no")
}
If NumberMatcher returns Boolean, then you can have it receive something. In that case, this is what happens:
str match {
case NumberMatcher() => println("yes")
case _ => println("no")
}
becomes
val r1 = NumberMatcher.unapply(str)
if (r1) {
println("yes")
} else {
println("no")
}
Note that this is a very superficial explanation. Case matches can test for constants, have additional guard conditions, alternatives, use unapply recursively, use unapplySeq, etc. Here I'm only showing very basic usage to address a specific question. I strongly advise searching for a fuller explanation of pattern matching.
Take a look at this example again.
I quote
The return type of an unapply should be chosen as follows:
* If it is just a test, return a Boolean. For instance case even()
* If it returns a single sub-value of type T, return a Option[T]
* If you want to return several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)].
When you defined unapply to return a Boolean, you were indicating that the pattern doesn't have any wildcards to match (or bind). So the case statement for this unextractor should be case NumberMatcher => println(str), and giving it a variable to fill is wrong.
Alternatively, to make case NumberMatcher(group) => println(group) you need to define unapply() to return Option[String]
package com.tutorial.extracters
object ExtracterwithBooleanReturnType extends App {
import java.util.regex._
object NumberMatcher {
def apply(x: String*) = {
x
}
def unapply(x: String): Option[Boolean] = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
return Some(matcher.find)
}
}
val strings = NumberMatcher("geo12", "neo493", "leo")
for (str <- strings) {
str match {
case NumberMatcher(group) => println(group)
case _ => println("no")
}
}
}
we can achieve this with above code as well