Таким образом, наращивая линейку 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 .