Главная страница
Навигация по странице:

  • Листинг 3.6.

  • Рис. 3.3.

  • Листинг 3.9.

  • Сбалансированный подход Scala-программистов

  • Scala. Профессиональное программирование 2022. Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста


    Скачать 6.24 Mb.
    НазваниеОдерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста
    Дата27.04.2023
    Размер6.24 Mb.
    Формат файлаpdf
    Имя файлаScala. Профессиональное программирование 2022.pdf
    ТипДокументы
    #1094967
    страница10 из 64
    1   ...   6   7   8   9   10   11   12   13   ...   64
    81
    В данном случае вторая строка кода, jetSet
    +=
    "Lear"
    , фактически является сокращенной формой записи следующего кода:
    jetSet = jetSet + "Lear"
    Следовательно, во второй строке кода листинга 3.5 var
    ­переменной jetSet присваивается новое множество, содержащее "Boeing"
    ,
    "Airbus"
    и "Lear"
    Наконец, последняя строка листинга 3.5 определяет, содержит ли множество строку "Cessna"
    (как и следовало ожидать, результат — false
    ).
    Если нужно изменяемое множество, то следует, как показано в листинге 3.6, воспользоваться инструкцией
    import
    Листинг 3.6. Создание, инициализация и использование изменяемого множества import scala.collection.mutable val movieSet = mutable.Set("Spotlight", "Moonlight")
    movieSet += "Parasite"
    // movieSet теперь содержит: "Spotlight", "Moonlight", "Parasite"
    В первой строке данного листинга выполняется импорт scala.collecti- on.mu table
    . Инструкция import позволяет использовать простое имя, например
    Set
    , вместо длинного полного имени. В результате при указа­
    нии mutable.Set во второй строке компилятор знает, что имеется в виду sca la.col lection.mutable.Set
    . В этой строке movieSet инициализиру­
    ется новым изменяемым множеством, содержащим строки "Spotlight"
    и "Moonlight"
    . В следующей строке к изменяемому множеству добавляется "Parasite"
    , для чего в отношении множества вызывается метод
    +=
    с переда­
    чей ему строки "Parasite"
    . Как уже упоминалось,
    +=
    — метод, определенный для изменяемых множеств. При желании можете вместо кода movieSet
    +=
    "Parasite"
    воспользоваться кодом movieSet.+=("Shrek")
    1
    Рассмотренной до сих пор исходной реализации множеств, которые вы­
    полняются изменяемыми и неизменяемыми фабричными методами
    Set
    , скорее всего, будет достаточно для большинства ситуаций. Однако време­
    нами может потребоваться специальный вид множества. К счастью, при
    1
    Множество в листинге 3.6 изменяемое, поэтому повторно присваивать значение movieSet не нужно, и данная переменная может относиться к val
    ­переменным.
    В отличие от этого, использование метода
    +=
    с неизменяемым множеством в ли­
    стинге 3.5 требует повторного присваивания значения переменной jetSet
    , поэтому она должна быть var
    ­переменной.

    82 Глава 3 • Дальнейшие шаги в Scala этом используется аналогичный синтаксис. Следует просто импортировать нужный класс и применить фабричный метод в отношении его объекта­
    компаньона. Например, если требуется неизменяемый
    HashSet
    , то можно сделать следующее:
    import scala.collection.immutable.HashSet val hashSet = HashSet("Tomatoes", "Chilies")
    val ingredients = hashSet + "Coriander"
    // ingredients содержит "Tomatoes", "Chilies", "Coriander"
    Еще одним полезным трейтом коллекций в Scala является отображение —
    Map
    . Как и для множеств, Scala предоставляет изменяемые и неизменяемые версии
    Map с применением иерархии классов. Как показано на рис. 3.3, иерар­
    хия классов для отображений во многом похожа на иерархию для множеств.
    В пакете scala.collection есть основной трейт
    Map и два трейта­наследника отображения
    Map
    : изменяемый вариант в scala.collection.mutable и неиз­
    меняемый в scala.collection.immutable
    Реализации
    Map
    , например
    HashMap
    ­реализации в иерархии классов, пока­
    занной на рис. 3.3, расширяются либо в изменяемый, либо в неизменяемый трейт. Отображения можно создавать и инициализировать, используя фа­
    бричные методы, подобные тем, что применялись для массивов, списков и множеств.
    Рис. 3.3. Иерархия классов для отображений Scala

    Шаг 10 . Используем множества и отображения 83
    Листинг 3.7. Создание, инициализация и использование изменяемого отображения import scala.collection.mutable val treasureMap = mutable.Map.empty[Int, String]
    treasureMap += (1 –> "Go to island.")
    treasureMap += (2 –> "Find big X on ground.")
    treasureMap += (3 –> "Dig.")
    val step2 = treasureMap(2) // " Find big X on ground."
    Например, в листинге 3.7 показана работа с изменяемым отображением: в первой строке оно импортируется, затем определяется val
    ­переменная treasureMap
    , которая инициализируется пустым изменяемым отображе­
    нием, имеющим целочисленные ключи и строковые значения. Оно пустое, поскольку вызывается фабричный метод с именем empty и указывается
    Int в качестве типа ключа и
    String в качестве типа значения
    1
    . В следующих трех строках к отображению добавляются пары «ключ — значение», для чего ис­
    пользуются методы
    –>
    и
    +=
    . Как уже было показано, компилятор Scala преоб­
    разует выражения бинарных операций вида
    1
    –>
    "Go to island."
    в код
    (1).–>
    ("Go to island.")
    . Следовательно, когда указывается
    1
    –>
    "Go to island."
    , фактически в отношении объекта
    1
    вызывается метод по имени
    –>
    , которому передается строка со значением "Go to island."
    . Метод
    –>
    , который можно вызвать в отношении любого объекта в программе Scala, возвращает двух­
    элементный кортеж, содержащий ключ и значение
    2
    . Затем этот кортеж пере­
    дается методу
    +=
    объекта отображения, на который ссылается treasureMap
    И наконец, в последней строке ищется значение, соответствующее клю­
    чу
    2
    в treasureMap
    . После выполнения этого кода переменная step2
    будет ссылаться на "Find big
    X
    on ground"
    Если отдать предпочтение неизменяемому отображению, то ничего импор­
    тировать не нужно, поскольку это отображение используется по умолчанию.
    Пример показан в листинге 3.8.
    1
    Явная параметризация типа "[Int,
    String]"
    требуется в листинге 3.7 из­за того, что без какого­либо значения, переданного фабричному методу, компилятор не в состоянии выполнить логический вывод типов параметров отображения. В от­
    личие от этого компилятор может выполнить вывод типов параметров из значений, переданных фабричному методу map, показанному в листинге 3.8, поэтому явного указания типов параметров там не требуется.
    2
    Механизм Scala, позволяющий вызывать такие методы, как
    –>
    для объектов, которые не объявляют их напрямую, называется методом расширения. Он будет рассмотрен в главе 22.

    84 Глава 3 • Дальнейшие шаги в Scala
    Листинг 3.8. Создание, инициализация и использование неизменяемого отображения val romanNumeral = Map(
    1 –> "I", 2 –> "II", 3 –> "III", 4 –> "IV", 5 –> "V"
    )
    val four = romanNumeral(4) // "IV"
    Учитывая отсутствие импортирования, при указании
    Map в первой строке данного листинга вы получаете используемый по умолчанию экземпляр класса scala.collection.immutable.Map
    . Фабричному методу отображения передаются пять кортежей «ключ — значение», а он возвращает неизменяе­
    мое
    Map
    ­отображение, содержащее эти переданные пары. Если запустить код, показанный в листинге 3.8, то переменная
    4
    будет ссылаться на
    IV
    Шаг 11 . Учимся распознавать функциональный стиль
    Как упоминалось в главе 1, Scala позволяет программировать в императив­
    ном стиле, но побуждает вас переходить преимущественно к функциональ­
    ному. Если к Scala вы пришли, имея опыт работы в императивном стиле, к примеру, вам приходилось программировать на Java, то одной из основных возможных сложностей станет программирование в функциональном стиле.
    Мы понимаем, что поначалу этот стиль может быть неизвестен, и в данной книге стараемся перевести вас из одного состояния в другое. От вас также потребуются некоторые усилия, которые мы настоятельно рекомендуем при­
    ложить. Мы уверены, что при наличии опыта работы в императивном стиле изучение программирования в функциональном позволит вам не только стать более квалифицированным программистом на Scala, но и расширит ваш кругозор, сделав вас более ценным программистом в общем смысле.
    Сначала нужно усвоить разницу между двумя стилями, отражающуюся в коде. Один верный признак заключается в том, что если код содержит var
    ­переменные, то он, вероятнее всего, написан в императивном стиле.
    Если он вообще не содержит var
    ­переменных, то есть включает только val
    ­переменные, то, вероятнее всего, он написан в функциональном стиле.
    Следовательно, один из способов приблизиться к последнему — попытаться обойтись в программах без var
    ­переменных.
    Обладая багажом императивности, то есть опытом работы с такими языка­
    ми, как Java, C++ или C#, var
    ­переменные можно рассматривать в качестве обычных, а val
    ­переменные — в качестве переменных особого вида. В то же

    Шаг 11 . Учимся распознавать функциональный стиль 85
    время, если у вас имеется опыт работы в функциональном стиле на таких языках, как Haskell, OCaml или Erlang, val
    ­переменные можно представ­
    лять как обычные, а var
    ­переменные — как некое кощунственное обращение с кодом. Но с точки зрения Scala val
    ­ и var
    ­переменные — всего лишь два разных инструмента в вашем арсенале средств и оба одинаково полезны и не отвергаемы. Scala побуждает вас к использованию val
    ­переменных, но, по сути, дает возможность применять тот инструмент, который лучше подходит для решаемой задачи. И тем не менее, даже будучи согласными с подобной философией, вы поначалу можете испытывать трудности, связанные с из­
    бавлением от var
    ­переменных в коде.
    Рассмотрим позаимствованный из главы 2 пример цикла while
    , в котором используется var
    ­переменная, означающая, что он выполнен в императивном стиле:
    def printArgs(args: List[String]): Unit =
    var i = 0
    while i < args.length do println(args(i))
    i += 1
    Вы можете преобразовать этот код — придать ему более функциональный стиль, отказавшись от использования var
    ­переменной, например, так:
    def printArgs(args: List[String]): Unit =
    for arg <- args do println(arg)
    или вот так:
    def printArgs(args: List[String]): Unit =
    args.foreach(println)
    В этом примере демонстрируется одно из преимуществ программирования с меньшим количеством var
    ­переменных. Код после рефакторинга (более функциональный) выглядит понятнее, он более лаконичен, и в нем труднее допустить какие­либо ошибки, чем в исходном (более императивном) коде.
    Причина навязывания в Scala функционального стиля заключается в том, что он помогает создавать более понятный код, при написании которого труднее ошибиться.
    Но вы можете пойти еще дальше. Метод после рефакторинга printArgs нельзя отнести к чисто функциональным, поскольку у него имеются побоч­
    ные эффекты. В данном случае такой эффект — вывод в поток стандартного устройства вывода. Признаком функции, имеющей побочные эффекты,

    86 Глава 3 • Дальнейшие шаги в Scala выступает то, что результирующим типом у нее является
    Unit
    . Если функция не возвращает никакое интересное значение, о чем, собственно, и свиде­
    тельствует результирующий тип
    Unit
    , то единственный способ внести этой функцией какое­либо изменение в окружающий мир — проявить некий побочный эффект. Более функциональным подходом будет определение метода, который форматирует передаваемые аргументы в целях их после­
    дующего вывода и, как показано в листинге 3.9, просто возвращает отфор­
    матированную строку.
    Листинг 3.9. Функция без побочных эффектов или var-переменных def formatArgs(args: List[String]) = args.mkString("\n")
    Теперь вы действительно перешли на функциональный стиль: нет ни побоч­
    ных эффектов, ни var
    ­переменных. Метод mkString
    , который можно вызвать в отношении любой коллекции, допускающей последовательный перебор элементов (включая массивы, списки, множества и отображения), возвра­
    щает строку, состоящую из результата вызова метода toString в отношении каждого элемента, с разделителями из переданной строки. Таким образом, если args содержит три элемента,
    "zero"
    ,
    "one"
    и "two"
    , то метод formatArgs возвращает "zero\none\ntwo"
    . Разумеется, эта функция, в отличие от мето­
    дов printArgs
    , ничего не выводит, но в целях выполнения данной работы ее результаты можно легко передать функции println
    :
    println(formatArgs(args))
    Каждая полезная программа, вероятнее всего, будет иметь какие­либо по­
    бочные эффекты. Отдавая предпочтение методам без побочных эффектов, вы будете стремиться к разработке программ, в которых такие эффекты све­
    дены к минимуму. Одним из преимуществ такого подхода станет упрощение тестирования ваших программ.
    Например, чтобы протестировать любой из трех показанных ранее в этом разделе методов printArgs
    , вам придется переопределить метод println
    , перехватить передаваемый ему вывод и убедиться в том, что он соответствует вашим ожиданиям. В отличие от этого функцию formatArgs можно проте­
    стировать, просто проверяя ее результат:
    val res = formatArgs(List("zero", "one", "two"))
    assert(res == "zero\none\ntwo")
    Имеющийся в Scala метод assert проверяет переданное ему буле­
    во выражение и, если последнее вычисляется в false
    , выдает ошибку
    AssertionError
    . Если же переданное булево выражение вычисляется

    Шаг 12 . Преобразование с отображениями и for-yield 87
    в true
    , то метод просто молча возвращает управление вызвавшему его коду.
    Более подробно о тестах, проводимых с помощью assert
    , и тестировании речь пойдет в главе 25.
    И все­таки нужно иметь в виду: ни var
    ­переменные, ни побочные эффекты не следует рассматривать как нечто абсолютно неприемлемое. Scala не явля­
    ется чисто функциональным языком, заставляющим вас программировать в функциональном стиле. Scala — гибрид императивного и функционального языков. Может оказаться, что в некоторых ситуациях для решения текущей задачи больше подойдет императивный стиль, и тогда вы должны прибег­
    нуть к нему без всяких колебаний. Но чтобы помочь вам разобраться в про­
    граммировании без использования var
    ­переменных, в главе 7 мы покажем множество конкретных примеров кода с использованием var
    ­переменных и рассмотрим способы их преобразования в val
    ­переменные.
    Сбалансированный подход Scala-программистов
    Старайтесь отдавать предпочтение val
    ­переменным, неизменяе­
    мым объектам и методам без побочных эффектов. Используйте var
    ­
    переменные, изменяемые объекты и методы с побочными эффектами тогда, когда у вас есть конкретная необходимость и обоснование для их использования.
    Шаг 12 . Преобразование с отображениями и for-yield
    При программировании в императивном стиле вы видоизменяете суще­
    ствующие структуры данных до тех пор, пока не достигнете цели алгоритма.
    В функциональном стиле для достижения цели вы преобразуете неизменя­
    емые структуры данных в новые.
    Важным методом, упрощающим функциональные преобразования неиз­
    меняемых коллекций, является map
    . Как и foreach
    , map принимает функцию в качестве параметра. Но в отличие от foreach
    , который использует пере­
    данную функцию для выполнения побочного эффекта для каждого элемента, map использует переданную функцию для преобразования каждого элемента в новое значение. Результатом работы map является новая коллекция, содер­
    жащая эти новые значения. Например, учитывая этот список строк: val adjectives = List("One", "Two", "Red", "Blue")

    88 Глава 3 • Дальнейшие шаги в Scala вы можете преобразовать его в новый список из новых строк, например:
    val nouns = adjectives.map(adj => adj + " Fish")
    // List(One Fish, Two Fish, Red Fish, Blue Fish)
    Другой способ выполнить преобразование — использовать выражение for
    , в котором вы вводите тело функции с ключевым словом yield вместо do
    :
    val nouns =
    for adj <- adjectives yield adj + " Fish"
    // List(One Fish, Two Fish, Red Fish, Blue Fish)
    For-yield дает точно такой же результат, что и map
    , потому что компилятор преобразует выражение for-yield в вызов map
    1
    . Поскольку список, воз­
    вращаемый map
    , содержит значения, созданные переданной функцией, тип элементов возвращаемого списка будет такой же, как и результат функции.
    В предыдущем примере переданная функция возвращает строку, поэтому map возвращает
    List[String]
    . Если функция, переданная map
    , приводит к друго­
    му типу, то список, возвращаемый map
    , будет содержать этот тип в качестве типа элемента. Например, ниже функция map преобразует строку в целое число, равное длине каждого элемента строки. Следовательно, результатом map является новый
    List[Int]
    , содержащий эти длины:
    val lengths = nouns.map(noun => noun.length)
    // List(8, 8, 8, 9)
    Как и раньше, вы также можете использовать выражение for-yield для до­
    стижения того же преобразования:
    val lengths =
    for noun <- nouns yield noun.length
    // List(8, 8, 8, 9)
    Метод map присутствует во многих типах, не только в
    List
    . Это позволяет использовать выражения со многими типами. Одним из примеров является
    Vector
    неизменяемая последовательность, обеспечивающая «фактически фиксированное время» для всех своих операций. Поскольку
    Vector пред­
    лагает метод map с соответствующей сигнатурой, вы можете выполнять те же виды функциональных преобразований в
    Vectors
    , что и в
    Lists
    , либо напрямую вызывая map
    , либо используя for-yield
    . Например:
    1
    Подробности того, как компилятор переписывает выражения, будут даны в раз­
    деле 7.3.

    Шаг 12 . Преобразование с отображениями и for-yield 89
    val ques = Vector("Who", "What", "When", "Where", "Why")
    val usingMap = ques.map(q => q.toLowerCase + "?")
    // Vector(who?, what?, when?, where?, why?)
    val usingForYield =
    for q <- ques yield q.toLowerCase + "?"
    // Vector(who?, what?, when?, where?, why?)
    Обратите внимание, что при сопоставлении
    List вы получаете новый
    List
    Когда вы сопоставляете
    Vector
    , вы получаете обратно новый
    Vector
    . В даль­
    нейшем вы поймете, что этот шаблон верен для большинства типов, которые определяют метод map
    В качестве последнего примера рассмотрим тип
    Option в Scala. Scala ис­
    пользует
    Option для представления необязательного значения, избегая традиционной техники Java, использующей для этой цели null
    1
    . Параметр
    Option
    — это либо
    Some
    , что указывает на то, что значение существует, либо
    None
    , которое указывает, что значение не существует.
    В качестве примера, показывающего
    Option в действии, рассмотрим метод find
    . Все типы коллекций Scala, включая
    List и
    Vector
    , предлагают find
    , который ищет элемент, соответствующий заданному предикату, — функцию, которая принимает аргумент типа элемента и возвращает булево значение.
    Тип результата find

    Option[E]
    , где
    E
    — тип элемента коллекции. Метод find выполняет итерации по элементам коллекции, передавая каждый из них предикату. Если функция возвращает true
    , find прекращает итерацию и возвращает этот элемент, заключенный в
    Some
    . Если find доходит до конца элементов без передачи предикату, он возвращает
    None
    . Вот несколько при­
    меров, в которых тип результата поиска всегда
    Option[String]
    :
    val startsW = ques.find(q => q.startsWith("W")) // Some(Who)
    val hasLen4 = ques.find(q => q.length == 4) // Some(What)
    val hasLen5 = ques.find(q => q.length == 5) // Some(Where)
    val startsH = ques.find(q => q.startsWith("H")) // None
    Хотя
    Option не является коллекцией, он предлагает map
    ­метод
    2
    . Если
    Opti- on является
    Some
    , который называется «определенным» параметром, map
    1
    В Java 8 к стандартной библиотеке был добавлен тип
    Optional
    , но многие суще­
    ствующие библиотеки Java по­прежнему используют null для обозначения от­
    сутствующего необязательного значения.
    2
    Однако
    Option можно представить как набор, который содержит либо ноль (случай
    None
    ) элементов, либо один (случай
    Some
    ).

    1   ...   6   7   8   9   10   11   12   13   ...   64


    написать администратору сайта