Dynamic Stable identifiers in Scala Pattern Match - scala

Using Scala, is there any way to dynamically construct a list patterns to be pattern matched against?
For example, suppose I'm using stable identifiers to parse a list of Strings, like this:
def matchingAndDispatch(xs: List[String])= {
case `namespace` :: value :: `elementTerminator` :: rest => {
// Do Something...
}
case `openBracket` :: rest => {
// Do Something Else...
}
case `closeBracket` :: `elementTerminator` :: rest => {
// Or perhaps something else...
}
}
Now, suppose there are going to be a lot of case clauses and I wanted the ability to store them in a collection of some sort that could be changed at runtime - not necessarily the patterns themselves, but the collection of patterns could be changed. I've made up the imaginary class MatchClause in the code below to explain more or less what I have in mind - basically traverse a collection of pattern(i.e. Match Clauses) and match one at a time:
def matchingAndDispatch(xs: List[String], matchingClauses:List[MatchClause])= {
if(!matchingClauses.empty){
case matchingClauses.head => {
// Do Something...
}
case _ => matchingAndDispatch(xs, matchingClause.tail)
}
}else throw new Error("no match")
Is there anything in the Scala API that would serve this purpose? I haven't found anything. Or perhaps I'm going about this the wrong way?

val `namespace` = "namespace"
val `elementTerminator` = "elementTerminator"
val `openBracket` = "openBracket"
val `closeBracket` = "closeBracket"
// list of partial functions from list of strings to string:
val patterns = List[PartialFunction[List[String], String]](
{ case `namespace` :: value :: `elementTerminator` :: rest => "case1" },
{ case `openBracket` :: rest => "case2" },
{ case `closeBracket` :: `elementTerminator` :: rest => "case3" })
def matchingAndDispatch(xs: List[String], patterns: List[PartialFunction[List[String], String]]): String = {
patterns.find(_.isDefinedAt(xs)).map(_(xs)).getOrElse("unknown")
}
Test:
matchingAndDispatch(List("namespace", "somevalue", "elementTerminator"), patterns)
> case1
matchingAndDispatch(List("namespace", "somevalue", "elementTerminator", "more"), patterns)
> case1
matchingAndDispatch(List("namespace", "somevalue", "not_terminator", "more"), patterns)
> unknown

You can declare a stable identifier within a local scope. That is, you can write
val hd = matchingClauses.head
xs match {
case `hd` => ???
}

Related

Scala: How to add match vals to a list val

I have a few vals that match for matching values
Here is an example:
val job_ = Try(jobId.toInt) match {
case Success(value) => jobs.findById(value).map(_.id)
.getOrElse( Left(WrongValue("jobId", s"$value is not a valid job id")))
case Failure(_) => jobs.findByName(jobId.toString).map(_.id)
.getOrElse( Left(WrongValue("jobId", s"'$jobId' is not a known job title.")))
}
// Here the value arrives as a string e.i "yes || no || true || or false" then converted to a boolean
val bool_ = bool.toLowerCase() match {
case "yes" => true
case "no" => false
case "true" => true
case "false" => false
case other => Left(Invalid("bool", s"wrong value received"))
}
Note: invalid case is case class Invalid(x: String, xx: String)
above i'm looking for a given job value and checking whether it exist in the db or not,
No I have a few of these and want to add to a list, here is my list val and flatten it:
val errors = List(..all my vals errors...).flatten // <--- my_list_val (how do I include val bool_ and val job_)
if (errors.isEmpty) { do stuff }
My result should contain errors from val bool_ and val job_
THANK!
You need to fix the types first. The type of bool_ is Any. Which does not give you something you can work with.
If you want to use Either, you need to use it everwhere.
Then, the easiest approach would be to use a for comprehension (I am assuming you're dealing with Either[F, T] here, where WrongValue and Invalid are both sub-classes of F and you're not really interested in the errors).
for {
foundJob <- job_
_ <- bool_
} yield {
// do stuff
}
Note, that in Scala >= 2.13 you can use toIntOption when converting the String to Int:
vaj job_: Either[F, T] = jobId.toIntOption match {
case Some(value) => ...
case _ => ...
}
Also, in case expressions, you can use alternatives when you have the same statement for several cases:
val bool_: Either[F, Boolean] = bool.toLowerCase() match {
case "yes" | "true" => Right(true)
case "no" | "false" => Right(false)
case other => Left(Invalid("bool", "wrong value received"))
}
So, according to your question, and your comments, these are the types you're dealing with.
type ID = Long //whatever id is
def WrongValue(x: String, xx: String) :String = "?-?-?"
case class Invalid(x: String, xx: String)
Now let's create a couple of error values.
val job_ :Either[String,ID] = Left(WrongValue("x","xx"))
val bool_ :Either[Invalid,Boolean] = Left(Invalid("x","xx"))
To combine and report them you might do something like this.
val errors :List[String] =
List(job_, bool_).flatMap(_.swap.toOption.map(_.toString))
println(errors.mkString(" & "))
//?-?-? & Invalid(x,xx)
After checking types as #cbley explained. You can just do a filter operation with pattern matching on your list:
val error = List(// your variables ).filter(_ match{
case Left(_) => true
case _ => false
})

Pattern match with variable parameters

Please suggest best was to implement the below code:
Requirement: pass single string or no parameters
object Twofer {
def twofer(name: String*): String = name match {
case Seq(nm) => s"One for $nm, one for me."
case List() => "One for you, one for me."
}
}
Maybe something similar to the following:
def twofer(names: String*): String = List(names: _*) match {
case Nil => "No names!"
case n :: Nil => s"Single name $n"
case ls => s"Multiple names $ls"
}

Scala list foreach, update list while in foreach loop

I just started working with scala and am trying to get used to the language. I was wondering if the following is possible:
I have a list of Instruction objects that I am looping over with the foreach method. Am I able to add elements to my Instruction list while I am looping over it? Here is a code example to explain what I want:
instructions.zipWithIndex.foreach { case (value, index) =>
value match {
case WhileStmt() => {
---> Here I want to add elements to the instructions list.
}
case IfStmt() => {
...
}
_ => {
...
}
Idiomatic way would be something like this for rather complex iteration and replacement logic:
#tailrec
def idiomaticWay(list: List[Instruction],
acc: List[Instruction] = List.empty): List[Instruction] =
list match {
case WhileStmt() :: tail =>
// add element to head of acc
idiomaticWay(tail, CherryOnTop :: acc)
case IfStmt() :: tail =>
// add nothing here
idiomaticWay(tail, list.head :: acc)
case Nil => acc
}
val updatedList = idiomaticWay(List(WhileStmt(), IfStmt()))
println(updatedList) // List(IfStmt(), CherryOnTop)
This solution works with immutable list, returns immutable list which has different values in it according to your logic.
If you want to ultimately hack around (add, remove, etc) you could use Java ListIterator class that would allow you to do all operations mentioned above:
def hackWay(list: util.List[Instruction]): Unit = {
val iterator = list.listIterator()
while(iterator.hasNext) {
iterator.next() match {
case WhileStmt() =>
iterator.set(CherryOnTop)
case IfStmt() => // do nothing here
}
}
}
import collection.JavaConverters._
val instructions = new util.ArrayList[Instruction](List(WhileStmt(), IfStmt()).asJava)
hackWay(instructions)
println(instructions.asScala) // Buffer(CherryOnTop, IfStmt())
However in the second case you do not need scala :( So my advise would be to stick to immutable data structures in scala.

Cleanest way in Scala to avoid nested ifs when transforming collections and checking for error conditions in each step

I have some code for validating ip addresses that looks like the following:
sealed abstract class Result
case object Valid extends Result
case class Malformatted(val invalid: Iterable[IpConfig]) extends Result
case class Duplicates(val dups: Iterable[Inet4Address]) extends Result
case class Unavailable(val taken: Iterable[Inet4Address]) extends Result
def result(ipConfigs: Iterable[IpConfig]): Result = {
val invalidIpConfigs: Iterable[IpConfig] =
ipConfigs.filterNot(ipConfig => {
(isValidIpv4(ipConfig.address)
&& isValidIpv4(ipConfig.gateway))
})
if (!invalidIpConfigs.isEmpty) {
Malformatted(invalidIpConfigs)
} else {
val ipv4it: Iterable[Inet4Address] = ipConfigs.map { ipConfig =>
InetAddress.getByName(ipConfig.address).asInstanceOf[Inet4Address]
}
val dups = ipv4it.groupBy(identity).filter(_._2.size != 1).keys
if (!dups.isEmpty) {
Duplicates(dups)
} else {
val ipAvailability: Map[Inet4Address, Boolean] =
ipv4it.map(ip => (ip, isIpAvailable(ip)))
val taken: Iterable[Inet4Address] = ipAvailability.filter(!_._2).keys
if (!taken.isEmpty) {
Unavailable(taken)
} else {
Valid
}
}
}
}
I don't like the nested ifs because it makes the code less readable. Is there a nice way to linearize this code? In java, I might use return statements, but this is discouraged in scala.
I personally advocate using a match everywhere you can, as it in my opinion usually makes code very readable
def result(ipConfigs: Iterable[IpConfig]): Result =
ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
case Nil =>
val ipv4it = ipConfigs.map { ipc =>
InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address]
}
ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
case Nil =>
val taken = ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys
if (taken.nonEmpty) Unavailable(taken) else Valid
case dups => Duplicates(dups)
}
case invalid => Malformatted(invalid)
}
Note that I've chosen to match on the else part first, since you generally go from specific to generic in matches, since Nil is a subclass of Iterable I put that as the first case, eliminating the need for an i if i.nonEmpty in the other case, since it would be a given if it didn't match Nil
Also a thing to note here, all your vals don't need the type explicitly defined, it significantly declutters the code if you write something like
val ipAvailability: Map[Inet4Address, Boolean] =
ipv4it.map(ip => (ip, isIpAvailable(ip)))
as simply
val ipAvailability = ipv4it.map(ip => (ip, isIpAvailable(ip)))
I've also taken the liberty of removing many one-off variables I didn't find remotely necessary, as all they did was add more lines to the code
A thing to note here about using match over nested ifs, is that is that it's easier to add a new case than it is to add a new else if 99% of the time, thereby making it more modular, and modularity is always a good thing.
Alternatively, as suggested by Nathaniel Ford, you can break it up into several smaller methods, in which case the above code would look like so:
def result(ipConfigs: Iterable[IpConfig]): Result =
ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
case Nil => wellFormatted(ipConfigs)
case i => Malformatted(i)
}
def wellFormatted(ipConfigs: Iterable[IpConfig]): Result = {
val ipv4it = ipConfigs.map(ipc => InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address])
ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
case Nil => noDuplicates(ipv4it)
case dups => Duplicates(dups)
}
}
def noDuplicates(ipv4it: Iterable[IpConfig]): Result =
ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys match {
case Nil => Valid
case taken => Unavailable(taken)
}
This has the benefit of splitting it up into smaller more manageable chunks, while keeping to the FP ideal of having functions that only do one thing, but do that one thing well, rather than having god-methods that do everything.
Which style you prefer, of course is up to you.
This has some time now but I will add my 2 cents. The proper way to handle this is with Either. You can create a method like:
def checkErrors[T](errorList: Iterable[T], onError: Result) : Either[Result, Unit] = if(errorList.isEmpty) Right() else Left(onError)
so you can use for comprehension syntax
val invalidIpConfigs = getFormatErrors(ipConfigs)
val result = for {
_ <- checkErrors(invalidIpConfigs, Malformatted(invalidIpConfigs))
dups = getDuplicates(ipConfigs)
_ <- checkErrors(dups, Duplicates(dups))
taken = getAvailability(ipConfigs)
_ <- checkErrors(taken, Unavailable(taken))
} yield Valid
If you don't want to return an Either use
result.fold(l => l, r => r)
In case of the check methods uses Futures (could be the case for getAvailability, for example), you can use cats library to be able of use it in a clean way: https://typelevel.org/cats/datatypes/eithert.html
I think it's pretty readable and I wouldn't try to improve it from there, except that !isEmpty equals to nonEmpty.

How to match a string on a prefix and get the rest?

I can write the code like this:
str match {
case s if s.startsWith("!!!") => s.stripPrefix("!!!")
case _ =>
}
But I want to know is there any better solutions. For example:
str match {
case "!!!" + rest => rest
case _ =>
}
val r = """^!!!(.*)""".r
val r(suffix) = "!!!rest of string"
So suffix will be populated with rest of string, or a scala.MatchError gets thrown.
A different variant would be:
val r = """^(!!!){0,1}(.*)""".r
val r(prefix,suffix) = ...
And prefix will either match the !!! or be null. e.g.
(prefix, suffix) match {
case(null, s) => "No prefix"
case _ => "Prefix"
}
The above is a little more complex than you might need, but it's worth looking at the power of Scala's regexp integration.
Starting Scala 2.13, it's now possible to pattern match a String by unapplying a string interpolator:
"!!!hello" match {
case s"!!!$rest" => rest
case _ => "oups"
}
// "hello"
If it's the sort of thing you do often, it's probably worth creating an extractor
object BangBangBangString{
def unapply(str:String):Option[String]= {
str match {
case s if s.startsWith("!!!") => Some(s.stripPrefix("!!!"))
case _ => None
}
}
}
Then you can use the extractor as follows
str match{
case BangBangBangString(rest) => println(rest)
case _ => println("Doesn't start with !!!")
}
or even
for(BangBangBangString(rest)<-myStringList){
println("rest")
}
Good question !
Even i was trying a lot to find out the answer.
Here is a good link where I found the answer
object _04MatchExpression_PatternGuards {
def main(args: Array[String]): Unit = {
val url: String = "Jan";
val monthType = url match {
case url if url.endsWith(".org") => "Educational Websites";
case url if url.endsWith(".com") => "Commercial Websites";
case url if url.endsWith(".co.in") => "Indian Websites"
case _ => "Unknow Input";
}
}
}