Объединение наборов, содержащихся в наборе объектов, функционально в Scala - PullRequest
2 голосов
/ 13 мая 2011

Я только начинаю в Scala и преобразовываю некоторый Java-код в Scala и пытаюсь сделать его красивым и функционально элегантным.

У меня есть следующий код, содержащий метод (getRequiredUploadPathKeys), который даетобъединение всех ключей пути, требуемых всеми доступными шаблонами пути в MyClass.

trait PathTemplate {
    def getRequiredPathKeys:Set[PathKey]
}

class MyClass(accessPaths:Set[PathTemplate], targetPath:PathTemplate){

    def getAllRequiredPathKeys: Set[PathKey] = {
        val pathKeys = HashSet[PathKey]()
        pathKeys.addAll(targetPath.getRequiredPathKeys)

        for (accessTemp <- accessPaths) {
            pathKeys.addAll(accessTemp.getRequiredPathKeys)
        }
        return pathKeys
    }
}

Этот метод может показаться более лаконичным в scala.Кто-нибудь может подсказать мне, как его туда достать?

Спасибо, Пол

Ответы [ 3 ]

6 голосов
/ 13 мая 2011
def getAllRequiredPathKeys = 
  (accessPaths + targetPath).flatMap(_.getRequiredPathKeys)

Или с помощью

def getAllRequiredPathKeys = 
  for(path <- accessPaths + targetPath; 
      key <- path.getRequiredPathKeys) yield key
6 голосов
/ 13 мая 2011

Любопытно, что вы используете Java HashSet - в Scala нет addAll - и в этом нет необходимости.

Итак, я собираюсь прокомментировать различные дизайнерские решения и то, как они отличаются от Java до Scala. Во-первых,

    val pathKeys = HashSet[PathKey]()

Idiomatic Scala обычно не ссылается на класс, реализующий интерфейс, так как интерфейс имеет фабрику. Так что вы обычно просто используете Set[PathKey](). Кроме того, это, очевидно, фабрика Scala, поскольку здесь нет ключевого слова new, что означает, что остальная часть кода не будет работать, поскольку в 1011 Scala Set нет addAll.

    pathKeys.addAll(targetPath.getRequiredPathKeys)

Здесь вы не используете targetPath.getRequiredPathKeys напрямую, поскольку Java Set является изменяемым. Scala Set по умолчанию является неизменным, что делает его бесполезным - вы можете просто использовать targetPath.getRequirePathKeys в качестве базового набора, не добавляя его элементы в другую коллекцию.

С точки зрения производительности, Scala-подобные функциональные языки - используют постоянные структуры данных в своих реализациях неизменяемых коллекций. Это означает, что он повторно использует части одной структуры данных для создания производной структуры данных, а не копирует все каждый раз, когда вы создаете новую структуру данных. Это возможно только из-за гарантий неизменности.

В любом случае, вы можете просто сделать это:

val pathKeys = targetPath.getRequiredPathKeys

Далее

    for (accessTemp <- accessPaths) {
        pathKeys.addAll(accessTemp.getRequiredPathKeys)
    }

Во-первых, идиоматическое использование для понимания должно возвращать значение, то есть для получения. Вышеупомянутое использование должно быть ограничено только побочными эффектами вашего кода. Чтобы быть более понятным, вышесказанное можно сделать в два этапа:

    // Get all required Path Keys
    val requiredPathKeys = for {
        accessPath <- accessPaths
        requiredPathKey <- accessPath.getRequiredPathKeys
    } yield requiredPathKey

    // Then add them
    pathKeys.addAll(requiredPathKeys)

Однако, учитывая, что вы не addAll вещи в Scala Set, вам нужно либо сделать pathKeys a var, либо использовать изменяемую структуру данных, либо просто изменить порядок:

    val requiredPathKeys = for {
        accessPath <- accessPaths
        requiredPathKey <- accessPath.getRequiredPathKeys
    } yield requiredPathKey

    val pathKeys = targetPath.getRequiredPathKeys ++ requiredPathKeys

Заметьте, однако, повторение getRequiredPathKeys. Функциональные программисты ненавидят повторение. Иногда слишком, на мой взгляд, но в этом случае его легко удалить:

    val pathKeys = for {
        accessPath <- accessPaths + targetPath
        requiredPathKey <- accessPath.getRequiredPathKeys
    } yield requiredPathKey

Приведенный выше код предлагает еще одно улучшение. Если вы увидите этот вопрос о Scala для понимания, вы увидите, что вышеизложенное эквивалентно

    val pathKeys = (accessPaths + targetPath).flatMap( accessPath =>
        accessPath.getRequiredPathKeys.map( requiredPathKey => requiredPathKey ))

map в конце избыточен, так как ничего не делает. Фактически, всякий раз, когда yield возвращает просто простой идентификатор, в выражении появляется избыточный map. Удаление этого дает нам:

    val pathKeys = (accessPaths + targetPath).flatMap(accessPath =>
        accessPath.getRequiredPathKeys)

Или, используя синтаксис параметров анонимной функции Scala,

    val pathKeys = (accessPaths + targetPath).flatMap(_.getRequiredPathKeys)

Наконец,

    return pathKeys

Ключевое слово return в Scala используется для обозначения исключения в рабочем процессе - точки, в которой выполнение метода прерывается вместо завершения до конца. Не то чтобы это не бесполезно, но, как и сами исключения, его нельзя использовать без причины. Здесь вы должны были использовать

    pathKeys

Но на этом этапе ваш код стал просто двумя строками:

    val pathKeys = (accessPaths + targetPath).flatMap(_.getRequiredPathKeys)
    pathKeys

Что делает назначение val полностью избыточным. Таким образом, вы можете уменьшить его до:

    (accessPaths + targetPath).flatMap(_.getRequiredPathKeys)

Таким образом, метод становится

def getAllRequiredPathKeys: Set[PathKey] = {
    (accessPaths + targetPath).flatMap(_.getRequiredPathKeys)
}

Теперь, я знаю, я сказал "наконец" там, но есть еще один лакомый кусочек соглашения Scala, который нужно применить. Всякий раз, когда метод содержит одно выражение вместо нескольких операторов, соглашение заключается в том, чтобы опустить фигурные скобки. Другими словами, сделайте это:

def getAllRequiredPathKeys: Set[PathKey] =
    (accessPaths + targetPath).flatMap(_.getRequiredPathKeys)

Или, если позволяет пространство, поместите все в одну строку. На самом деле, если вы удалите getAllRequiredPathKeys тип, он будет хорошо вписываться. С другой стороны, явные типы возврата для открытого метода приветствуются.

Так что это даст вам неизменный Скала Set. Допустим, ваш ввод и вывод должны быть в Java Set. В этом случае я не вижу ничего, что можно было бы сделать, чтобы упростить ваш код, если только вы не захотели конвертировать между наборами Java и Scala. Вы можете сделать это так:

trait PathTemplate {
    def getRequiredPathKeys:java.util.Set[PathKey]
}

import scala.collection.JavaConverters._

class MyClass(accessPaths:java.util.Set[PathTemplate], targetPath:PathTemplate){
    def getAllRequiredPathKeys: java.util.Set[PathKey] =
        (accessPaths.asScala + targetPath).flatMap(_.getRequiredPathKeys.asScala).asJava
}

Это, однако, использует Scala mutable Set, что означает, что каждая операция, создающая новый Set, будет копировать все содержимое старого Set.

4 голосов
/ 13 мая 2011

Вы имеете в виду что-то вроде:

def getAllRequiredPathKeys = (accessPaths map { _.getRequiredPathKeys }).flatten ++ targetPath.getRequiredPathKeys
...