Metadata
Metadata

Reputation: 2083

How to return a yielded value from a for-loop in scala?

In my project, we are migrating from Java to Scala. I have to a function in Java that uses continue in for loop and returns a value based on an if-condition inside the for loop as below.

private TSourceToken getBeforeToken(TSourceToken token) {
  TSourceTokenList tokens = token.container;
  int index = token.posinlist;     
  for ( int i = index - 1; i >= 0; i-- ) {
    TSourceToken currentToken = tokens.get( i );
    if ( currentToken.toString( ).trim( ).length( ) == 0 ) {
      continue;
    }
    else {
      return currentToken;
    }
  }
  return token;
}

To convert this to Scala, I used Yield option in order to filter out the values that satisfy the else condition of the if expression as below.

def getBeforeToken(token: TSourceToken): TSourceToken = {
  val tokens = token.container
  val index = token.posinlist

  for { i <- index -1 to 0 by -1
        currentToken = tokens.get(i)
        if(currentToken.toString.trim.length != 0)
        } yield currentToken

}

I am not able to figure out how to return the value yielded from the for-loop. The error message is

type mismatch;
Found   : IndexedSeq[TSourceToken]
Required: TSourceToken

I understand that yield collecting all the values from the for-loop and its condition inside and is resulting in a collection: IndexedSeq & hence the error message. I am not able to think of a logic to form as it was written in the Java code. Could anyone let me know how can I frame the code in Scala without using yield and break the for-loop once the else condition is satisfied ?

Upvotes: 0

Views: 815

Answers (3)

Boris Azanov
Boris Azanov

Reputation: 4501

There is no usual for-loops like in java in scala. for-yield is not the same as for-loop. It's just syntactic sugar for flatMap, map and withFilter functions combinations. Also in scala is better not to use return keyword. The language based on expressions which has return value and return word can break the logic and another developers can't expect that.

In your case it will be better to use find for looking some specific element in some collection by predicate:

def getBeforeToken(token: TSourceToken): TSourceToken = {
  val tokens = token.container
  val index = token.posinlist

  (index - 1 to 0 by -1)
    .find(i => tokens.get(i).toString.trim.length != 0)
    .fold(token)(i => tokens.get(i))
}

Upvotes: 7

Martijn
Martijn

Reputation: 12112

a for comprehension is not the right tool for this problem: it's for mapping, not for finding.

For a straight-forward re-write that just re-writes your java code to scala, use a while loop:

def getBeforeToken(token: TSourceToken): TSourceToken = {
  val tokens = token.container
  var i = index - 1
  while (i >= 0) {
    val currentToken = tokens.get(i)
    if (currentToken.toString.trim.length != 0) return currentToken
    i -= 1
  }
  token
}

if token.container is some collection though, you'd probably write it without explicit iteration:

def getBeforeToken(token: TSoruceToken): TSourceToken =
  token.container.reverseIterator.collectFirst {
    case t if t.toString.trim.length != 0 => t
  }.getOrElse(token)

While in the process of migrating, it may be smart to stick with the first though, to get the almost-mechnical translation done as quick as possible, before trying to up the scala code quality.

Upvotes: 3

Tomer Shetah
Tomer Shetah

Reputation: 8539

You can read a lot about Scala yield. Here are some nice articles:

But I think that is the wrong way to approach this in Scala. Scala is a functional programming language, and you can use its capabalities.

Assuming TSourceToken is case class having two members:

case class TSourceToken(container: Seq[String], posinlist: Int)

Where you want to choose the last token that is not empty from the last posinlist ones. There are many ways to achieve that. One way I was thinking about is:

val tokens = TSourceToken(Seq("", "   ", "  \n   ", "real token", "\t\t", "   ", "  \n\n  ", "token to ignore"), 5)
val token = tokens.container.take(tokens.posinlist).filter(_.trim.nonEmpty).last

token will have real token, as you expect. Code run can be found at Scastie.

Upvotes: 1

Related Questions