Почему это закрытие Groovy не возвращает ожидаемое мной значение? - PullRequest
3 голосов
/ 22 февраля 2012

В приложении Grails я пытаюсь предотвратить создание циклов в ориентированном графе.Пользователь может назначить родительский узел для узла, но ни один узел не должен быть предком своего родителя.Я написал простую функцию установки, которая вызывает checkLineageForTarget, которая является рекурсивной функцией, выполняющей тяжелую работу:

boolean checkLineageForTarget(Integer target, Collection<Node>stillToProcess){
// true means that this is a safe addition
// false means that this addition creates a cycle

    boolean retVal = stillToProcess.each {
        Collection<Node> itsParents = getParentNodes(it)

        if (it.id == target){
            println("found a loop on " + target);
            return false; // loop detected!
        }
        if (itsParents.empty){ return true; } // end of the line

        return checkLineageForTarget(target, itsParents)
    }

    // at this point, retVal is always true, even when the "found a loop [...]" condition is met
    return retVal;
}

Это «работает», поскольку печатает «найденный цикл [...]"сообщение, но за пределами замыкания, retVal имеет значение true, вызывающая функция пытается добавить новые отношения родитель / потомок, и мой стек запустился.

В чем мое недоразумение?

Ответы [ 3 ]

3 голосов
/ 22 февраля 2012

Метод each возвращает ту же коллекцию, для которой он был вызван, поэтому retVal, вероятно, не является логическим значением «истина», но оценивается как «правдивый» (так как это коллекция, это будет означать, что он не пустой).

Если вы хотите проверить условие для каждого элемента в коллекции, вы можете использовать every.

boolean checkLineageForTarget(Integer target, Collection<Node>stillToProcess){
    stillToProcess.every { node ->
        node.id != target && checkLineageForTarget(target, getParentNodes(node))
    }
}

Обратите внимание, что мне не нужно проверять условие .empty в коллекции родительских узлов, поскольку оно будет отфильтровано рекурсивным вызовом checkLineageForTarget (то есть вызов .every для пустой коллекции всегда возвращает true). Кроме того, из-за короткого замыкания оператора && итерация останавливается, как только node.id == target:)

3 голосов
/ 22 февраля 2012

.each появляется, чтобы вернуть зацикленный объект, когда это будет сделано. Вы присваиваете это логическое значение, и оно приводится к true. Вы, вероятно, хотите использовать .every для своей задачи. Он возвращает true только в том случае, если каждая итерация возвращает true, и останавливает цикл при достижении первого false. Вы можете найти больше информации в отличных документах .

2 голосов
/ 22 февраля 2012

Когда вы возвращаетесь в Closure, это похоже на возврат внутри вызова метода в методе - он локально для этой области и не влияет на реальный метод, в который вызывается Closure. В этом случае вы можете использовать один из другие подходы, предложенные (например, каждый) или используют обычный цикл for, поскольку он работает так же, как каждый из Groovy (т. е. он нулевой и поддерживает, но не требует типов), но вы можете выйти из цикла или вернуться, так как вы Вы находитесь в реальном цикле for, он вернется из метода:

boolean checkLineageForTarget(Integer target, Collection<Node>stillToProcess){

   for (Node node in stillToProcess) {
      Collection<Node> itsParents = getParentNodes(node)
      ...
   }
   ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...