如何在Scala中使用`continue`和`yield`从for循环中返回多个值?

huangapple go评论72阅读模式
英文:

How to apply continue and yield multiple values from a for-loop in scala?

问题

以下是用Scala编写相同功能的代码:

def checkColumn(server: String, database: String, schema: String, table: String, column: String): Boolean = {
  var bRet = false
  for (i <- 0 until columns.length) {
    val bServer = server == null || server.isEmpty || columns(i)(0).equalsIgnoreCase(server)
    val bDatabase = database == null || database.isEmpty || columns(i)(1).equalsIgnoreCase(database)
    val bSchema = schema == null || schema.isEmpty || columns(i)(2).equalsIgnoreCase(schema)
    val bTable = columns(i)(3).equalsIgnoreCase(table)
    val bColumn = columns(i)(4).equalsIgnoreCase(column)

    if (bServer && bDatabase && bSchema && bTable && bColumn) {
      bRet = true
      // Exit the loop once the condition is satisfied
      return bRet
    }
  }
  bRet
}

注意,Scala中没有continue语句,但我们可以通过适当的布尔条件来实现相同的逻辑。在上面的代码中,我们使用了布尔变量bRet来记录是否满足条件,一旦满足条件,就可以直接返回结果并退出循环。此外,我们使用了Scala的字符串方法isEmpty来检查字符串是否为空。

英文:

I am trying to convert a module of a java program into Scala. So far, I have been able to apply Scala's functional programming paradigm and its syntax in every module I converted. But I have come across a method that does some validations, use continue and finally yield the output.
Below is the code in Java:

public boolean checkColumn(String server, String database, String schema, String table, String column) {
        boolean bServer, bDatabase, bSchema, bTable, bColumn, bRet = false;
        for (int i = 0; i &lt; columns.length; i++) {
            if ((server == null) || (server.length() == 0)) {
                bServer = true;
            } else {
                bServer = columns[i][0].equalsIgnoreCase(server);
            }
            if (!bServer) continue;

            if ((database == null) || (database.length() == 0)) {
                bDatabase = true;
            } else {
                bDatabase = columns[i][1].equalsIgnoreCase(database);
            }
            if (!bDatabase) continue;

            if ((schema == null) || (schema.length() == 0)) {
                bSchema = true;
            } else {
                bSchema = columns[i][2].equalsIgnoreCase(schema);
            }

            if (!bSchema) continue;

            bTable = columns[i][3].equalsIgnoreCase(table);
            if (!bTable) continue;

            bColumn = columns[i][4].equalsIgnoreCase(column);
            if (!bColumn) continue;

            bRet = true;
            break;
        }
        return bRet;
    }

While I understand there is no continue in recent versions of Scala I am able to understand how to write the same code in Scala. I tried to construct a for loop as below:

val finalReturn = for {i &lt;- 0 until columns.length
    
} yield bRet

But couldn't think of a way to form the logic of all the if-conditions & continue inside the for-loop. Could anyone let me know how can I write the same code in Scala ?

答案1

得分: 5

你正在检查(至少)一个“列”的内容是否满足所有测试。这是“存在”操作:

def checkColumn(server: String, database: String, schema: String, table: String, column: String) = {
  columns.exists { col =>
    (server.isEmpty || col(0).equalsIgnoreCase(server)) &&
    (database.isEmpty || col(1).equalsIgnoreCase(database)) &&
    (schema.isEmpty || col(2).equalsIgnoreCase(schema)) &&
    col(3).equalsIgnoreCase(table) &&
    col(4).equalsIgnoreCase(column)
  }
}

这将逐个检查每个元素,直到有一列满足所有测试(返回true),或列表耗尽且没有列满足条件(返回false)。

英文:

You are checking that (at least) one of the columns matches all the tests. This is the exists operation:

def checkColumn(server: String, database: String, schema: String, table: String, column: String) = {
  columns.exists { col =&gt;
    (server.isEmpty || col(0).equalsIgnoreCase(server)) &amp;&amp;
    (database.isEmpty || col(1).equalsIgnoreCase(database)) &amp;&amp;
    (schema.isEmpty || col(2).equalsIgnoreCase(schema)) &amp;&amp;
    col(3).equalsIgnoreCase(table) &amp;&amp;
    col(4).equalsIgnoreCase(column)
  }

This will check each element in turn until one of the columns passes all the tests (which will return true) or the list is exhausted, which will return false.

答案2

得分: 1

这是翻译好的内容:

没有办法使用函数式方法完全相同的算法,因为在 Java 代码中,这是一种非常命令式的风格,涉及可变性和对评估流程的手动控制。

所以你应该思考这段代码在做什么,代码中的逻辑是什么。然后使用函数式的原语和模式来实现这个逻辑。

让我们逐步来看:

if ((server == null) || (server.length() == 0)) {
    bServer = true;
} else {
    bServer = columns[i][0].equalsIgnoreCase(server);
}
if (!bServer) continue;

这段代码检查是否存在定义的服务器字符串 - 你应该检查列的第一个字段是否等于定义的服务器字符串(我不清楚 columns 具体是什么,但我猜测 columns[i]columncolumn[j] 是列的一个字段)。
否则就会调用 continue 跳过迭代。对于其他字段也是类似,不同的是 tableschema 不会被检查是否为空。

如果我们看最后,跳过迭代会导致 "不将 bRet 设置为 true"。因此,如果某个列的所有检查都通过,bRet 将为真,然后循环会在此之后中断。

因此我们可以说 "如果存在至少一个通过检查的列 - 该方法的结果应该为真"。幸运的是,在 Scala 中,你有一个特殊的集合方法 exists,恰好具有这种逻辑。

另外,最好有一个专门的辅助函数来处理 null 和空值。

private def isEmpty(string: String) = string == null || string.isEmpty

def checkColumn(server: String, database: String, schema: String, table: String, column: String): Boolean = {
  columns.exists { column =>
      (isEmpty(server) || column(0).equalsIgnoreCase(server)) &&
      (isEmpty(database) || column(1).equalsIgnoreCase(database)) &&
      (isEmpty(schema) || column(2).equalsIgnoreCase(schema)) &&
                          column(3).equalsIgnoreCase(table) &&
                          column(4).equalsIgnoreCase(column)
  }
}

此外,我必须提到在 Scala 中处理业务代码中的空值是非常不好的实践,你应该将可为 null 的参数更改为 Option[String],并稍微更改表达式以处理该类型。我将展示三种方法:

def checkColumn(serverOpt: Option[String], databaseOpt: Option[String], schemaOpt: Option[String], table: String, column: String): Boolean =
  columns.exists { column =>
      serverOpt.filterNot(_.isEmpty).map(server =>
          column(0).equalsIgnoreCase(server)).getOrElse(true) &&
      databaseOpt.filterNot(_.isEmpty).map(column(1).equalsIgnoreCase).getOrElse(true) &&
      schemaOpt.filterNot(_.isEmpty).fold(true)(column(2).equalsIgnoreCase) &&
      column(3).equalsIgnoreCase(table) &&
      column(4).equalsIgnoreCase(column)
  }

当调用它时,你应该使用 Option 构造函数包装你的可为 null 的字符串,像这样:

checkColumn(Option(nullableServer), Option(nullableDatabase), Option(nullableSchema), table, column)

进一步的改进将是使用细化类型技术,并将可为 null,甚至可能为空的字符串类型从仅仅的 Option[String] 改变为 Option[NonEmptyString]

英文:

There is no way to write exactly the same algorithm with a functional approach because in java code this is a very imperative style with mutability and manual control over the flow of evaluation.
So you should think what this code does, what is the logic in the code. And then implement this logic with functional primitives and patterns.

Let's go step by step

        if ((server == null) || (server.length() == 0)) {
            bServer = true;
        } else {
            bServer = columns[i][0].equalsIgnoreCase(server);
        }
        if (!bServer) continue;

it checks that if there is the defined server string - you should check that 1st field of the column
should be equal to that defined server string (i don't know what columns exactly are, but I try to guess that columns[i] is column and column[j] is a field of the column).
Otherway it calls continue and skips the iteration.
Same for other fields with the exception that table and schema doesn't being checked for emptiness.

If we looked at the end, the skipping results in "not setting bRet to true". So if all checks passed for some column, bRet would be true and the loop breaks after that.

So we can say that "if there exists at least one column that passes the checks - the result of that method should be true". Good for you that in scala you have a special method of the collection exists with exactly that logic.

Also, it is better to have a dedicated helper function to deal with nullability and emptiness.

private def isEmpty(string: String) = string == null || string.isEmpty

def checkColumn(server: String, database: String, schema: String, table: String, column: String): Boolean = {
  columns.exists { column =&gt;
      (isEmpty(server)   || column(0).equalsIgnoreCase(server)) &amp;&amp;
      (isEmpty(database) || column(1).equalsIgnoreCase(database)) &amp;&amp;
      (isEmpty(schema)   || column(2).equalsIgnoreCase(schema)) &amp;&amp;
                            column(3).equalsIgnoreCase(table) &amp;&amp;
                            column(4).equalsIgnoreCase(column)
  }
}

In addition, I have to mention that in Scala dealing with nulls in business code is very bad practice, you should change nullable parameters to Option[String] and change a little bit expressions to deal with that type. I will show 3 ways of doing that:

def checkColumn(serverOpt: Option[String], databaseOpt: Option[String], schemaOpt: Option[String], String table, String column): Boolean =
  columns.exists { column =&gt;
      serverOpt.filterNot(_.isEmpty).map(server =&gt; column(0).equalsIgnoreCase(server).getOrElse(true) &amp;&amp;
      databaseOpt.filterNot(_.isEmpty).map(column(1).equalsIgnoreCase).getOrElse(true) &amp;&amp;
      schemaOpt.filterNot(_.isEmpty).fold(true)(column(2).equalsIgnoreCase)&amp;&amp;
      column(3).equalsIgnoreCase(table) &amp;&amp;
      column(4).equalsIgnoreCase(column)
  }

And when you call it, you should wrap your nullable strings with Option constructor like this:

checkColumn(Option(nullableServer), Option(nullableDatabase), Option(nullableshema), table, column)

Further improvements would be to use refinement types techniques and change the type of nullable and maybe empty strings from just Option[String] to Option[NonEmptyString].

huangapple
  • 本文由 发表于 2020年9月18日 21:36:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/63956847.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定