Как я могу сделать "if ..else" внутри для понимания?
-
10-10-2019 - |
Вопрос
Я задаю очень простой вопрос, который недавно сбил меня с толку.Я хочу написать Scala Для выражения, чтобы сделать что-то вроде следующего:
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
Проблема в том, что в нескольких генераторах для выражения я не знаю, куда я могу поместить каждый из них для тела выражения.
for {i <- expr1
if(i.method) // where can I write the else logic ?
j <- i
if (j.method)
} doSomething()
Как я могу переписать код в стиле Scala?
Решение
Первый код, который вы написали, абсолютно корректен, так что переписывать его нет необходимости.В другом месте вы сказали, что хотели бы знать, как это сделать в стиле Scala.На самом деле не существует "стиля Scala", но я возьму на вооружение более функциональный стиль и придерживаюсь его.
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
Первая проблема заключается в том, что это не возвращает никакого значения.Все, что он вызывает, - это побочные эффекты, которых также следует избегать.Итак, первое изменение было бы примерно таким:
val result = for (i <- expr1) yield {
if (i.method) {
for (j <- i) yield {
if (j.method) {
returnSomething()
// etc
Так вот, есть большая разница между
for (i <- expr1; j <- i) yield ...
и
for (i <- expr1) yield for (j <- i) yield ...
Они возвращают разные вещи, и бывают моменты, когда вы хотите более поздние, а не прежние.Хотя я предполагаю, что вы хотите первое.Теперь, прежде чем мы продолжим, давайте исправим код.Это некрасиво, трудно для понимания и неинформативно.Давайте реорганизуем его с помощью методов извлечения.
def resultOrB(j) = if (j.method) returnSomething else returnSomethingElseB
def nonCResults(i) = for (j <- i) yield resultOrB(j)
def resultOrC(i) = if (i.method) nonCResults(i) else returnSomethingC
val result = for (i <- expr1) yield resultOrC(i)
Это уже намного чище, но возвращается не совсем то, что мы ожидаем.Давайте посмотрим на разницу:
trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"
def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else Unrecognized
val result = for (i <- expr1) yield classifyElements(i)
Тип result
есть Array[AnyRef]
, в то время как использование нескольких генераторов дало бы Array[Element]
.Самая простая часть исправления заключается в следующем:
val result = for {
i <- expr1
element <- classifyElements(i)
} yield element
Но это само по себе не сработает, потому что сам classifyElements возвращает AnyRef
, и мы хотим, чтобы он вернул коллекцию.Сейчас, validElements
верните коллекцию, так что это не проблема.Нам нужно только исправить else
часть.С тех пор как validElements
возвращает IndexedSeq
, давайте вернемся к этому на else
тоже часть.Конечным результатом является:
trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"
def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else IndexedSeq(Unrecognized)
val result = for {
i <- expr1
element <- classifyElements(i)
} yield element
Это выполняет точно такую же комбинацию циклов и условий, как вы представили, но она гораздо более читабельна и ее легко изменить.
О Доходности
Я думаю, важно отметить одну вещь о представленной проблеме.Давайте упростим это:
for (i <- expr1) {
for (j <- i) {
doSomething
}
}
Теперь это реализовано с помощью foreach
(см . здесь, или другие подобные вопросы и ответ на них).Это означает, что приведенный выше код делает точно то же самое, что и этот код:
for {
i <- expr1
j <- i
} doSomething
В точности то же самое.Это совсем не так, когда кто-то использует yield
.Следующие выражения не дают такого же результата:
for (i <- expr1) yield for (j <- i) yield j
for (i <- expr1; j <- i) yield j
Первый фрагмент будет реализован с помощью двух map
вызывает, в то время как второй фрагмент будет использовать один flatMap
и один map
.
Таким образом, это происходит только в контексте yield
что вообще имеет какой-то смысл беспокоиться о вложенности for
циклы или использование нескольких генераторов.И, на самом деле, генераторы обозначает тот факт , что что - то находится в сгенерированный, что верно только для истинных представлений (тех yield
что-то пишущий).
Другие советы
Часть
for (j <- i) {
if (j.method) {
doSomething(j)
} else {
doSomethingElse(j)
}
}
может быть переписан как
for(j <- i; e = Either.cond(j.method, j, j)) {
e.fold(doSomething _, doSomethingElse _)
}
(Конечно, вы можете использовать урожайность вместо этого, если вы делаете, методы что -то возвращают)
Здесь это не так ужасно полезно, но если у вас есть более глубокая вложенная структура, она может ...
import scalaz._; import Scalaz._
val lhs = (_ : List[X]) collect { case j if j.methodJ => doSomething(j) }
val rhs = (_ : List[X]) map doSomethingElse
lhs <-: (expr1 partition methodI) :-> rhs
Вы не можете. Для (expr; if) конструкция просто фильтровать элемент, который должен обрабатывать в цикле.
Если заказ не важен для вызовов Dosomething () и doSomethingElse (), вы можете изменить такой код.
val (tmp, no1) = expr1.partition(_.method)
val (yes, no2) = tmp.partition(_.method)
yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())
Чтобы ответить на ваш первоначальный вопрос, я думаю, что для понимания может быть довольно хорошим для конкретных вариантов использования, и ваш пример не подходит.
Условия, указанные в Scala для операции, для фильтрации элементов из генераторов. Элементы, не удовлетворяющие условиям, отбрасываются и не представлены в блоке доходности / кода.
Это означает, что если вы хотите выполнить альтернативные операции на основе условного выражения, тест должен быть отложен на блок урожайности / кода.
Также имейте в виду, что для работы для работы относительно дорого (в настоящее время), поэтому, возможно, более простой итеративный подход может быть более подходящим, возможно, что -то вроде:
expr1 foreach {i =>
if (i.method) {
i foreach {j =>
if (j.method)
doSomething()
else
doSomethingElseA()
}
}
else
doSomethingElseB()
}
Обновлять:
Если вы должны использовать для понимания, и вы можете жить с некоторыми ограничениями, это может сработать:
for (i <- expr1; j <- i) {
if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}