#java #scala #short-circuiting
#java #scala #короткое замыкание
Вопрос:
Я пытаюсь преобразовать модуль Java-программы в Scala. До сих пор мне удавалось применять парадигму функционального программирования Scala и ее синтаксис в каждом преобразованном мной модуле. Но я наткнулся на метод, который выполняет некоторые проверки, использует continue
и, наконец yield
, выводит. Ниже приведен код на 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 < 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;
}
Хотя я понимаю continue
, что в последних версиях Scala этого нет, я могу понять, как написать тот же код в Scala. Я попытался создать цикл for, как показано ниже:
val finalReturn = for {i <- 0 until columns.length
} yield bRet
Но не мог придумать способ сформировать логику всех условий if amp; continue
внутри цикла for. Может кто-нибудь сообщить мне, как я могу написать тот же код в Scala?
Ответ №1:
Вы проверяете, что (по крайней мере) один из columns
тестов соответствует всем тестам. Это exists
операция:
def checkColumn(server: String, database: String, schema: String, table: String, column: String) = {
columns.exists { col =>
(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)
}
Это будет проверять каждый элемент по очереди, пока один из столбцов не пройдет все тесты (которые вернутся true
) или список не будет исчерпан, который вернется false
.
Комментарии:
1. Так намного чище.
Ответ №2:
Невозможно написать точно такой же алгоритм с функциональным подходом, потому что в коде Java это очень императивный стиль с изменчивостью и ручным управлением потоком вычислений. Итак, вы должны подумать, что делает этот код, какова логика в коде. А затем реализовать эту логику с помощью функциональных примитивов и шаблонов.
Давайте пойдем шаг за шагом
if ((server == null) || (server.length() == 0)) {
bServer = true;
} else {
bServer = columns[i][0].equalsIgnoreCase(server);
}
if (!bServer) continue;
он проверяет, есть ли определенная строка сервера — вы должны проверить, что 1-е поле столбца
должно быть равно этой определенной строке сервера (я не знаю, что columns
именно, но я пытаюсь догадаться, что columns[i]
это column
и column[j]
является полем столбца).
В противном случае он вызывает continue и пропускает итерацию.
То же самое для других полей, за исключением того, что table
и schema
не проверяется на пустоту.
Если мы посмотрим в конец, пропуск приведет к «не установленному bRet
значению true
«. Итак, если все проверки пройдены для некоторого столбца, bRet
будет true, и после этого цикл прерывается.
Итак, мы можем сказать, что «если существует хотя бы один столбец, который проходит проверки — результат этого метода должен быть true». Хорошо, что в scala у вас есть специальный метод сбора exists
данных именно с такой логикой.
Кроме того, лучше иметь специальную вспомогательную функцию для решения проблем с обнуляемостью и пустотой.
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)) 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)
}
}
Кроме того, я должен упомянуть, что в Scala работа с нулями в бизнес-коде является очень плохой практикой, вам следует изменить параметры с нулевым Option[String]
значением и немного изменить выражения, чтобы иметь дело с этим типом. Я покажу 3 способа сделать это:
def checkColumn(serverOpt: Option[String], databaseOpt: Option[String], schemaOpt: Option[String], String table, String column): Boolean =
columns.exists { column =>
serverOpt.filterNot(_.isEmpty).map(server => 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)
}
И когда вы его вызываете, вы должны обернуть свои строки с нулевым Option
значением с помощью конструктора, подобного этому:
checkColumn(Option(nullableServer), Option(nullableDatabase), Option(nullableshema), table, column)
Дальнейшими улучшениями было бы использование методов уточнения типов и изменение типа nullable и, возможно, пустых строк с just Option[String]
на Option[NonEmptyString]
.