Статьи

Власть с контролем: управляющие структуры и абстракции Scala

Таким образом, наращивая линейку Scala 101, я подумал, что сейчас подходящий момент для введения управляющих структур в Scala. В определенной степени работа с языком Scala представляет собой перспективу, в которой разработчику предоставляется гораздо большая свобода, чем во многих других средах, но в этом заключается множество вариантов и чувство ответственности . Таким образом, я сознательно пытался ограничить этот пост, чтобы охватить некоторые из основных вариантов и вариантов управления потоком данных и итерациями в Scala, как они отличаются и предоставить примеры использования. Я ожидаю, что это будет что-то вроде «белой езды» через строительные блоки языка, и надеюсь предоставить и контекстное руководство, и ориентир для использования. Мой идеал заключается в том, что в сочетании со знанием основ того, как классы и объекты различаются и создаются в Scala, это должно обеспечить стартовую площадку для возможности написания продуктивного кода Scala.  

Примечание : это покрытие не претендует или даже не пытается быть авторитетным, но пытается охватить некоторые из стилей и конструкций итерации, которые я нашел наиболее полезными.

Так что без дальнейших церемоний, пусть щелчок цепей мурлычет, когда мы начинаем наше восхождение ..

если()

… Является одной из немногих встроенных структур управления в Scala и использует синтаксис, знакомый большинству разработчиков Java. В Scala реализация немного отличается от реализации в Java, поскольку все операторы должны возвращать результат . Это что-то вроде общей тенденции в функциональных языках: ошибаться на стороне связывания и возврата переменных и значений и (эффективно) выступать в качестве конвейера выполнения и обогащения, а не обработки, ориентированной на «побочные эффекты» . Таким образом, Scala использует модель троичного оператора в качестве значения по умолчанию, в результате чего значения могут быть назначены на основе результата вычисления if ().

например, если () как троичное выражение

1
val result = if (1 > 2) true else false
Примечание : — если оценка не может вернуть результат, то исходная переменная (если она уже инициализирована) не будет выполнена из-за несоответствия типов, или переменная (если она не инициализирована) будет инициализирована для AnyVal.
— инициализированные значения не могут быть переназначены, поскольку они неизменны

например, если в качестве троичного с объявленным только одним возвращаемым значением

1
var result2 = if(1>2) 1 // assigns result2 = 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 // note this has to be a 'var' type, as we hope to reassign the variable during iteration
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()) // returns a List of Type 'Car'
Также обратите внимание: этот список поддерживает тривиальные операции, такие как 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)
// the individual elements of the Tuple can be accessed in the usual way, e.g.:
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) (_-_) // This takes the seed value for the consolidated return as 1, before applying the muliplication operation on all elements of the palindromeList (effectively adding the results into a running total)
 
// to see the difference in foldLeft and foldRight for noncommutative operations, let's look at the minus operations applied with a foldLeft and a foldRight (using the shorthand syntax), and observe how the results differ:
 
(1 /: testList) (_-_) // left fold on the minus operation returns -54
(testList :\ 1) (_-_) // right fold on the minus operation returns -4
Примечание. Синтаксис для операций сгиба: /: (для 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 .