Таким образом, наращивая линейку Scala 101, я подумал, что сейчас подходящий момент для введения управляющих структур в Scala. В определенной степени работа с языком Scala представляет собой перспективу, в которой разработчику предоставляется гораздо большая свобода, чем во многих других средах, но в этом заключается множество вариантов и чувство ответственности . Таким образом, я сознательно пытался ограничить этот пост, чтобы охватить некоторые из основных вариантов и вариантов управления потоком данных и итерациями в Scala, как они отличаются и предоставить примеры использования. Я ожидаю, что это будет что-то вроде «белой езды» через строительные блоки языка, и надеюсь предоставить и контекстное руководство, и ориентир для использования. Мой идеал заключается в том, что в сочетании со знанием основ того, как классы и объекты различаются и создаются в Scala, это должно обеспечить стартовую площадку для возможности написания продуктивного кода Scala.
Примечание : это покрытие не претендует или даже не пытается быть авторитетным, но пытается охватить некоторые из стилей и конструкций итерации, которые я нашел наиболее полезными.
Так что без дальнейших церемоний, пусть щелчок цепей мурлычет, когда мы начинаем наше восхождение ..
если()
… Является одной из немногих встроенных структур управления в Scala и использует синтаксис, знакомый большинству разработчиков Java. В Scala реализация немного отличается от реализации в Java, поскольку все операторы должны возвращать результат . Это что-то вроде общей тенденции в функциональных языках: ошибаться на стороне связывания и возврата переменных и значений и (эффективно) выступать в качестве конвейера выполнения и обогащения, а не обработки, ориентированной на «побочные эффекты» . Таким образом, Scala использует модель троичного оператора в качестве значения по умолчанию, в результате чего значения могут быть назначены на основе результата вычисления if ().
например, если () как троичное выражение
|
1
|
val result = if (1 > 2) true else false
|
Примечание : — если оценка не может вернуть результат, то исходная переменная (если она уже инициализирована) не будет выполнена из-за несоответствия типов, или переменная (если она не инициализирована) будет инициализирована для AnyVal.
— инициализированные значения не могут быть переназначены, поскольку они неизменны
например, если в качестве троичного с объявленным только одним возвращаемым значением
пока и делаю .. пока
… Считаются циклами, а не выражениями в Scala, поскольку они не должны возвращать « интересные » типы (т. Е. Они могут возвращать тип Unit, который в Scala эквивалентен void в Java). Многие функциональные языки обычно обходятся без конструкции while () и вместо этого полагаются на рекурсию в качестве управляющей абстракции, но, учитывая многопарадигмальную природу Scala (и ее использование в качестве языка шлюза для функциональной парадигмы), они поддерживаются в качестве встроенных структур управления. Следовательно, циклы while часто выделяются в пользу более функционального стиля взаимодействия (например, с использованием рекурсии ). Тем не менее, вот пример стандартного синтаксиса while и do .. while :
например, while () и делать .. while ()
|
1
2
|
var i = 0
while(i < 3) println("Hello " + {i = i + 1; i} )
|
или же
|
1
|
do { println("Hello " + { i = i + 1; i } ) } while(i < 3)
|
для (я до / до)
.. использует синтаксис, аналогичный расширенному циклу for в Java 5, с некоторыми заметными дополнениями, а именно: вывод типа использования значения выражения; повторяющееся значение инициализируется с использованием генератора, и в выражении также может быть встроено несколько генераторов; поддержка фильтра запекается в выражении. Простой пример должен прояснить это:
например, генераторы и фильтры в понимании for ()
|
1
|
for(i <- 1 to 4; j <- 1 until 4 if (i * j % 2 == 0)) { println(i * j) }
|
Как показано выше, границы генератора могут быть заданы с использованием либо «до», либо «до», а значения могут быть назначены во время каждой итерации. Фильтры Simlarly позволяют нескольким операторам (разделенным точками с запятой) обрезать, какие значения входят в тело выражения . Итерации, использующие for (), называются выражениями (в отличие от циклов ), поскольку они обычно возвращают значение и могут использоваться для инициализации переменной. Здесь есть одна тонкость, заключающаяся в том, что реализация и желание возвращать значения из конструкции for () определяются (компилятором) контекстом, в котором используется выражение. Углубившись немного глубже, мы видим, что на самом деле существует два стиля цикла for, один из которых является обязательным, а другой — функциональным.
Императивный стиль распознается тем, что он «дает» результат (который возвращается из выражения), тогда как императивный стиль используется для его побочных эффектов (как в примере, показанном выше). Для более любознательного читателя здесь очень хорошо описана полная экстраполяция тонкостей выражений for (). Это отличная статья, в которой описывается, как выражения for () в Scala на самом деле являются замыканиями и почему это может дать удивительные результаты. В качестве увлажнителя аппетита вот несколько капель детали:
- предложения, содержащиеся в выражениях for, на самом деле являются замыканиями и, как таковые, не могут «сломаться» в ожидаемом императивном смысле.
- Стремительная оценка — это реализация по умолчанию большинства выражений for ().
- оценка в режиме реального времени (то есть ленивая ) предложений о разрыве возможна с использованием .view коллекции. (Примечание: представление заменяет устаревший метод .projection [now])
- однако… чтобы действительно выйти из закрытия рано, условие разрыва должно оцениваться по отношению к прогнозируемому суррогатному объекту .
Например, пример для () понимания с использованием функциональной формы:
|
1
|
var multiplesOfTwo = for(i <- 1 to 10 if i % 2 == 0) yield i
|
например, ленивый, оцененный для понимания () с воротами разрыва
|
1
2
|
var gate = true
for(i <- testList.view.takeWhile(i => gate)) if(i < 5) println(i) else gate = false
|
Примечание. Поскольку операции просмотра и проецирования находятся в списке (и, как следует из названия), я не верю, что возможно применить эту « escape-семантику » к набору значений, созданных генератором. Однако, учитывая этот вариант использования, цикл while () удовлетворит эти потребности:
например, используя while (), чтобы избежать итерации не-Collection
|
1
2
3
4
5
6
7
|
var index = 1
var escapeGate = false
while(index < 10000000 && !escapeGate) {
if(index > 5) escapeGate = true
println(index)
i += 1
}
|
Полезные операции со списками
Списки являются одним из основных (если не основным) типов коллекций в Scala, и их можно легко создавать и манипулировать ими. Далее мы рассмотрим некоторые типичные операции со списками и их использование. Некоторые из описанных ниже ценных членов были безвозмездно получены из превосходного Scaladoc .
например, создание списка
|
1
|
val testList = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
или же
|
1
|
val testList = 1 :: 2 :: 3 :: 4 :: 5 :: 6 :: 7 :: 8 :: 9 :: 10 :: Nil
|
Примечание: операция :: является ассоциативной справа. Как правило, операции, заканчивающиеся двоеточием, имеют тенденцию быть ассоциативными. Также операция ::: может использоваться для объединения нескольких списков в новый список. Объединение списков разных типов приводит к новому списку элементов типа « наименьший общий знаменатель » для отдельных списков.
например, Наследование среди элементов списка
|
1
2
|
class Car; class Vovlo extends Car; class VW extends Car;
var lcdList = List(new Volvo()) ::: List(new VW())
|
Также обратите внимание: этот список поддерживает тривиальные операции, такие как head (чтобы вернуть первый элемент списка) и tail (который возвращает содержимое списка после первых элементов списка). Никаких дальнейших подробностей об этих операциях здесь не будет.
для каждого()
Использование: Применяет функцию к каждому элементу списка. Эта циклическая конструкция используется для создаваемых побочных эффектов.
Пример:
|
1
|
testList foreach(x => println(x*2))
|
Примечание. Если функция принимает один параметр, параметры метода не должны указываться явно. Кроме того, подстановочный знак ‘_’ может заменить весь список параметров. (NB. Существует еще один нюанс для символа подчеркивания, где за ним следует звездочка при использовании в качестве подстановки массива, например, ‘_ *’):
например, foreach с предполагаемыми параметрами
|
1
|
testList foreach(println)
|
и
|
1
|
testList foreach(println _)
|
фильтр()
Использование: Выбирает все элементы из списка, которые удовлетворяют данному предикату.
Пример:
|
1
|
val evensList = testList filter (_ % 2 == 0)
|
Примечание. Порядок элементов в возвращенном списке сохраняется из исходного списка.
раздел ()
Использование: разбивает список на 2 списка на основе представленного предиката и возвращает Tuple2, содержащий возвращенные списки.
Пример:
|
1
2
3
|
val tupleWrappedOddAndEvenLists = testList partition (_ % 2 == 0)
println(tupleWrappedOddAndEvenLists._1)
|
Примечание. Поддержка и использование кортежей в Scala выходит за рамки простой оболочки для ввода / возврата нескольких переменных, так как они также могут использоваться в экстракторах и, следовательно, повторно использоваться в сопоставлениях с образцами . Обе темы выходят за рамки этой статьи.
для всех()
Использование: возвращает логическое значение, указывающее, все ли элементы списка передают данный предикат
Пример:
|
1
|
testList forall(a => a.isInstanceOf[Int])
|
существуют()
Использование: проверяет, является ли какой-либо элемент данного списка истинным в соответствии с заданным предикатом
Пример:
|
1
|
testList exists(a => a == 4)
|
карта()
Использование: Применяет функцию ко всем элементам списка и возвращает новый список, содержащий результаты.
Пример:
|
1
|
val doubledList = testList map (_ * 2)
|
flatMap ()
Использование: аналогично операции с картой, но результаты будут объединены в один список. Например, если в качестве входных параметров задан список списков и функция, возвращается один (преобразованный) список.
Пример:
|
1
|
val palindromeList = List(testList, testList reverse) flatMap (_.toList)
|
Примечание. Эта функция особенно полезна при попытке получить единый список (преобразованных) конечных узлов из древовидной структуры, например, при просмотре файловой системы, или для получения консолидированного списка финансовых / спортивных рынков, преобразованных в требуемую структуру модели домена.
раз [влево / вправо] ()
Использование: Применяет данную функцию ко всем элементам списка для возврата агрегированного значения. Практически это необходимо при выполнении кратких операций конкатенации или суммирования. Разница между foldLeft и foldRight — это направление, в котором применяется функция. Это примечание операций, которые некоммутативны .
Пример:
|
1
2
3
4
5
6
|
(1 foldLeft testList) (_-_)
(1 /: testList) (_-_)
(testList :\ 1) (_-_)
|
Примечание. Синтаксис для операций сгиба: /: (для foldLeft) и: \ (для foldRight). Именование «/:» таково, что операция фактически «выглядит» как направление, в котором происходит сгиб, с точкой с запятой, указывающей, из какого направления вводится ввод. Следовательно, (1 foldLeft palindromeList) (_ * _) выше эквивалентно (1 /: palindromeList) (_ * _)
Связанный: Катаморфизм
уменьшить [Влево / Вправо] ()
Использование: рекурсивно применяет данную функцию к элементам из списка. Результирующее значение из первой итерации используется в качестве основного ввода для следующей итерации, и этот шаблон замещения затем применяется рекурсивно.
Пример:
|
1
|
testList reduceLeft ((a, b) => if (a > b) a else b)
|
Пока что это разброс некоторых из различных управляющих структур, абстракций и операций со списками, доступных в Scala, и должен обеспечить прочную основу для выполнения начальных каталон Scala . И еще один важный момент: имейте в виду, что компилятор Scala оптимизирует хвостовые рекурсивные вызовы, заключая любые хвостовые рекурсивные операции в циклы for (), и, следовательно, повторно использует кадр стека времени выполнения. В следующий раз я попытаюсь осветить одну из самых мощных абстракций в языке Scala, а именно сопоставление с образцом, и показать его взаимодействие с обработкой исключений в Scala. Надеемся, что этот обзор предоставил читателю необходимую широту и глубину информации для дальнейшего изучения, а также продемонстрировал некоторые возможности рекурсии и функциональный стиль программирования, который облегчает Scala. Счастливого взлома!
Ссылка: Power with control… структуры управления и абстракции в Scala от нашего партнера по JCG Кингсли Дэвиса в блоге Scalabound .