Scala лямбда функция как параметр

Обновлено: 05.07.2024

Этот код ищет в массиве строк первый элемент, содержащий подстроку " nut ". Таким образом, он выдает следующий результат:

И пока он работает, его можно улучшить.

Корень проблемы здесь в том, что std::find_if требует, чтобы мы передали ей указатель на функцию. Из-за этого мы вынуждены определять функцию, которая будет использоваться только один раз, ей нужно дать имя и поместить в глобальную область видимости (потому что функции не могут быть вложенными!). К тому же эта функция такая короткая, что легче понять, что она делает, по строкам кода, чем по названию и комментариям.

Лямбды спешат на помощь

Лямбда-выражение (также называемое лямбда (lambda) или замыкание (closure)) позволяет нам определять анонимную функцию внутри другой функции. Вложенность важна, поскольку она позволяет нам избежать загрязнения пространств имен и определять функцию как можно ближе к тому месту, где она используется (обеспечивая дополнительный контекст).

Синтаксис лямбда-выражений – одна из самых странных вещей в C++, и к нему нужно немного привыкнуть. Лямбды имеют вид:

Захват и параметры могут быть пустыми, если они не нужны.

Тип возвращаемого значения является необязательным, и если он опущен, будет использоваться значение auto (т.е. использование вывода типа для определения типа возвращаемого значения). Хотя мы ранее отмечали, что следует избегать вывода типа для типов, возвращаемых функцией, в этом контексте его можно использовать (поскольку эти функции обычно очень тривиальны).

Также обратите внимание, что лямбды не имеют имени, поэтому нам не нужно его указывать.

В качестве отступления.

Это означает, что определение простейшего лямбда-выражения выглядит так:

Давайте перепишем приведенный выше пример с помощью лямбда-выражения:

Это работает так же, как и случай с указателем на функцию, и дает идентичный результат:

Обратите внимание, насколько похоже наше лямбда-выражение на нашу функцию containsNut . У них обоих одинаковые параметры и тела функций. Лямбда не имеет захвата (что такое захват, мы объясним в следующем уроке), потому что он не нужен. И мы в лямбде опустили завершающий тип возвращаемого значения (для краткости), но поскольку operator!= возвращает bool , наша лямбда также вернет bool .

Тип лямбды

В приведенном выше примере мы определили лямбду именно там, где это было необходимо. Такое использование лямбды иногда называют функциональным литералом.

Однако запись лямбды в той же строке, где она используется, иногда может затруднить чтение кода. Подобно тому, как мы можем инициализировать переменную литеральным значением (или указателем на функцию) для использования позже, мы также можем инициализировать лямбда-переменную с помощью определения лямбда-выражения, а затем использовать ее позже. Именованная лямбда вместе с хорошим именем функции может упростить чтение кода.

Например, в следующем фрагменте мы используем std::all_of , чтобы проверить, все ли элементы массива четны:

Мы можем улучшить читаемость следующим образом:

Но какой тип у лямбды isEven ?

Оказывается, лямбды не имеют типа, который мы могли бы использовать явно. Когда мы пишем лямбду, компилятор генерирует только для этой лямбды уникальный тип, который нам не предоставляется.

Для продвинутых читателей

На самом деле лямбды не являются функциями (что является частью того, как они избегают ограничения C++, не поддерживающего вложенные функции). Это особый вид объектов, называемых функторами. Функторы – это объекты, которые содержат перегруженный operator() , который делает их вызываемыми как функции.

Хотя мы не знаем тип лямбды, есть несколько способов сохранить лямбду для использования после определения. Если лямбда имеет пустой список захвата, мы можем использовать обычный указатель на функцию. В следующем уроке мы познакомимся с лямбда-захватами, указатель на функцию в этот момент больше не будет работать. Однако для лямбда-выражений можно использовать std::function , даже если они что-то захватывают.

Единственный способ использовать реальный тип лямбды – использовать auto . У auto также есть преимущество в отсутствии дополнительных затрат по сравнению с std::function .

К сожалению, мы не всегда можем использовать auto . В случаях, когда фактическая лямбда неизвестна (например, потому что мы передаем лямбду в функцию в качестве параметра, и вызывающий определяет, какая лямбда будет передана), мы не можем использовать auto без компромиссов. В таких случаях можно использовать std::function .

Вывод этой программы:

Если бы мы использовали auto для типа fn , вызывающий функцию не знал бы, какие параметры и возвращаемый тип должна иметь fn . Кроме того, функции с параметрами auto нельзя разделить на заголовочный и исходный файл. Мы объясняем причину этого ограничения, когда будем говорить о шаблонах.

Правило

Используйте auto при инициализации переменных лямбда-выражениями, или std::function , если вы не можете инициализировать переменную лямбда-выражением.

Обобщенные лямбда-выражения

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

Одним примечательным исключением является то, что, начиная с C++14, нам разрешено использовать auto для параметров (примечание: в C++20 обычные функции также смогут использовать auto для параметров). Когда лямбда имеет один или несколько параметров auto , компилятор из вызовов лямбды определит, какие типы параметров необходимы.

Поскольку лямбда-выражения с одним или несколькими параметрами auto потенциально могут работать с широким спектром типов, они называются обобщенными лямбда-выражениями.

Для продвинутых читателей

При использовании в контексте лямбда-выражения auto – это просто сокращение для шаблонного параметра.

Давайте посмотрим на обобщенную лямбду:

В приведенном выше примере мы используем параметры auto для захвата наших строк по константной ссылке. Поскольку все строковые типы разрешают доступ к своим отдельным символам через operator[] , нам не нужно заботиться о том, передает ли пользователь std::string , строку в стиле C или что-то еще. Это позволяет нам написать лямбду, которая могла бы принимать любой из этих типов, а это означает, что если мы изменим тип через несколько месяцев, нам не придется переписывать лямбду.

Однако auto не всегда лучший выбор. Рассмотрим следующий код:

В этом примере использование auto приведет к выводу типа const char* . Со строками в стиле C нелегко работать (если не считать использования operator[] ). В этом случае мы предпочитаем явно определить параметр как std::string_view , что позволяет нам намного проще работать с базовыми данными (например, мы можем запросить у строкового представления его длину, даже если пользователь передал массив в стиле C).

Обобщенные лямбды и статические переменные

Следует знать, что для каждого типа, в который выводится auto , будет сгенерирована уникальная лямбда. В следующем примере показано, как одна обобщенная лямбда превращается в две разные лямбды:

В приведенном выше примере мы определяем лямбду, а затем вызываем ее с двумя разными параметрами (строковый литеральный параметр и целочисленный параметр). Это генерирует две разные версии лямбды (одна со строковым литеральным параметром, а другая с целочисленным параметром).

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

Мы можем видеть это в приведенном выше примере, где каждый тип (строковые литералы и целые числа) имеет свой уникальный счетчик! Хотя мы написали лямбду только один раз, было сгенерировано две лямбды, и каждая имеет свою версию callCount . Чтобы иметь общий счетчик для двух сгенерированных лямбда-выражениях, нам нужно определить глобальную переменную или статическую локальную переменную вне лямбда-выражения. Как вы знаете из предыдущих уроков, как глобальные, так и статические локальные переменные могут вызывать проблемы и затруднять понимание кода. Мы сможем избежать этих переменных после того, как поговорим о лямбда-захватах в следующем уроке.

Вывод возвращаемого типа и завершающие возвращаемые типы

Если используется вывод типа возвращаемого значения, возвращаемый тип лямбды выводится из инструкций return внутри лямбды. В этом случае все инструкции return в лямбда-выражении должны возвращать один и тот же тип (иначе компилятор не будет знать, какой из них предпочесть).

Это приводит к ошибке компиляции, поскольку тип возврата первой инструкции return ( int ) не соответствует типу возврата второй инструкции return ( double ).

В случае, если мы возвращаем разные типы, у нас есть два варианта:

  1. выполните явное приведение, чтобы все возвращаемые типы совпадали, или
  2. явно укажите тип возвращаемого значения для лямбда-выражения и позвольте компилятору выполнить неявные преобразования.

Второй вариант – обычно лучший выбор:

Таким образом, если вы когда-нибудь решите изменить тип возвращаемого значения, вам (обычно) нужно будет только изменить тип возвращаемого значения лямбды, и не касаться тела лямбда.

Функциональные объекты стандартной библиотеки

Для распространенных операций (например, сложения, отрицания или сравнения) вам не нужно писать свои собственные лямбды, потому что стандартная библиотека поставляется с множеством базовых вызываемых объектов, которые можно использовать вместо этого. Они определены в заголовке .

Вместо того чтобы преобразовывать нашу функцию greater в лямбду (что немного скрывает ее значение), мы можем вместо этого использовать std::greater :

Заключение

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

Лямбды – это здорово, но они не заменяют обычные функции во всех случаях. Для нетривиальных и многоразовых случаев предпочитайте использование обычных функций.

Небольшой тест

Вопрос 1

Создайте структуру Student , в которой хранятся имя и баллы учащегося. Создайте массив студентов и используйте std::max_element , чтобы найти студента, набравшего наибольшее количество баллов, затем распечатайте имя этого студента. std::max_element принимает начало и конец списка и функцию, которая принимает 2 параметра и возвращает истину, если первый аргумент меньше второго.

Проверьте код на следующем массиве.

Ваша программа должна напечатать

Вопрос 2

Используйте std::sort и лямбда-выражение в следующем коде, чтобы отсортировать сезоны по возрастанию средней температуры (температура приведена в Кельвинах).

val - константа
var - Изменяемая переменная:
- все типы являются классами (нет встроенных типов): Byte, Char, Short, Long, Float, Double, Boolean
- String используется java.lang.String но добавляет дополнительные функции (StringOps, RichInt, RichDouble . )

Операторы

Условия

- Условия возвращают значение это удобней, т.к. может быть использовано для инициализации const
аналогом c++ является x > 0 ? 1 : -1

Вывод в консоль

, где:
$a - переменная
$<> - выражение

Циклы

- while / do - аналогично java
- цикл for работает по принципу foreach - перебора элементов последовательности: 1 to n - генерирует последовательность чисел, которая обходится последовательно
последовательность может быть любая - выражение или просто строка
- нет break и continue
- в for может быть несколько итераторов через ; и выражение может содержать условие:
т.е. каждая итерация i запускает цикл j (аналог вложенного цикла с условием) и накладывает фильтр, чтобы в j не было пересечений с i
- циклы могут использоваться для генераций колекций

Функции

- Однострочный вариант: - Многострочный: - Указание типа обязательно для рекурсии, иначе нет: - Можно использовать return для немедленного выхода из функции
- параметр функции может иметь значение по умолчанию: - передавать параметры можно по имени в любом порядке: - функция с переменным числом параметров: - Чтобы передать список как несколько параметров, нужно добавить : _* - def w = .
логически похоже на переменную, которая каждый раз переинициализируется при обращении

- Способы передачи параметров в функцию:
call by value - каждый аргумент расчитывается единожды и передается в функцию
call by name (по ссылке) - аргумент не вычисляется, если он не используется в теле функции (вычисление происходит внутри функции)
т.е. если в параметре сложное выражение, то оно будет выполнено несколько раз (но если оно не испольузется внутри, то не будет ненужного вычисления как в call by value)

- Частичные (partial) функции:
partial функция - реализуется чреез pattern matching и может работать только для нужных case:
определена ли функция для значения можно проверить через isDefinedAt если вызывать для несуществующего значения, то будет исключение: может применяться в lambda функциях как параметр и будет вызываться только для применимых значений:
- Возврат Option значения
обработка ситуации NULL указателя через Option и getOrElse или обработка None
- Исключающее ИЛИ в параметрах или возвращаемом значении
Either[A, B] - исключающее или как возвращаемый параметр - может использоваться как обработку ошибок без Try
"Right - это правильно". Поэтому же map и flatMap применяются к правильной ветви, то есть к правой. И "хороший" результат оборачивают в Right, а ошибку в Left.
- чтобы перехватить иключение, можно использовать объект Try:

Процедуры

Ленивые переменные

Операция для инициализации w вызовется при первом обращении.

Исключения

Аналогично java: - в scala нет проверки на обязательность исключения во время компиляции как в java
так писать не нужно:
- перехват exception идет через case:

Массивы

- Фиксированный массив: - с начальной инициализацией: - Переменной длины:
Аналог java arraylist: - добавить элемент в массив: - несколько значений (каждое станет значением) - обход массива:
-- если индекс не нужен: -- если нужен индекс: until почти то же самое, что to , создается множество -1 значение
- yeld - обход с преобразованием в новый массив (описано в циклах)
- обход и фильтрация массива: - arr.indices - список индексов (ключей) массива
- встроенные методы: sum, max, count(_ > 0), sorted (_ на 1 to 10, 1 -> 10
- опрделение своего оператора как в java: - f(arg1, arg2, . ) вызовет f.apply(arg1, arg2, . ) , если метода f нет в классе
- f(arg1, arg2, . ) = value - если слева от =, то вызовет update
это используется в массивах:
scores("Bob") = 100
- unapply - Обратная операция - объект разворачивается в переменные: использование: val Fraction(a, b) = f;
- динамические методы и переменные: -- динамический вызов транслируется в : -- person.lastName = "Doe"
транслируется в: реализовывать функцию нужно самим

Функции высшего порядка

- функцию можно положить в переменную: _ означает необходимость параметра для функции

- Общая сигнатура функции высшего порядка:
f - тип функции, которая принимает A и возвращает B - метод класса: переменная: (параметр: тип).функция(параметр: тип) переменная: (входные параметры) => выходной параметр = вызов функции с параметрами
- анонимная функция можно передавать ее как параметр, без переменных: более короткая запись: - функция, параметром которой является функция: - функция возвращает функцию: в функцию mulBy положена функция "(x : Double) => factor * x" - каррирование - преобразование ф. с 2 параметрами в 2 функции с 1 параметром:
каррирование удобно для создания новых функций:
т.е. это цепочка из 2 функций вложенных друг в друга(если параметров каррирования 2)
первый описывает преобразование
второй статичные параметры, которое использует действие (преобразование)

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

- monad - Монада
имеет 2 обязательных метода:
-- flatMap (примеменяет функцию f(T): монада(T) для всех элементов)
-- unit (T->монада[T]) - помещение элемента во внутренний контейнер (int в List/Set/Map)
К примеру, List - монада, т.к. помещает элементы во внутренний контейнер-список над которым можно применить flatmap
Правила монады:
-- ассоциативность: m.flatMap(f).flatMap(g) == m.flatMap(x=> f(x).flatMap(g))
-- левый unit: unit(x).flatMap(f) == f(x) //вызов flatmap c функцией f над list из 1 элемента = вызову f(один элемент)
-- правый unit: m.flatMap(unit) == m (m - уже монада = unit(x))

Коллекции

- наследование объектов: - Преобразование типов через: .to*
- все коллекции по умолчанию неизменяемые (Immutable)
т.е. если мы изменяем коллекцию, то создается копия с изменениями
- Vector - immutable расширяемый массив (ArrayBuffer - muttable)
Технически хранится в виде b-дерева, с элементами в узле (32 элемента в блоке)
- Range - последовательность чисел
Технически хранит: начало, конец и шаг
- на последовательностях сделано: Stack, Queue, Prior Queue, ListBuffer

- for yield - более короткая замена для Map/flatmap/withfilter это аналог более длинной записи: - можно приводить java объекты к scala коллекциям

Сопоставление с образцом

- аналог switch: -- не нужен brake - он есть по умолчанию
-- _ - если не будет, то выкинется исключение при проверке
-- можно перечислять несколько значений через |
-- в case может быть любое логическое условие: -- присвоение идет произвольной переменной за =>
-- сопоставление может идти с типом переменной: - сопоставление массива 2 переменным и всего остального последовательности rest: - сопоставление ключа и значения ассоц. массива может быть в цикле: - сопоставление может идти по типу класса: - объект можно скопировать с изменением параметра: - для сопоставления с пустым значением можно использовать тип Some - сравнение с коллекциями: - для сопоставления с образцом класса, нужен специальный case класс:

Аннотации

Диррективы компилятору
- определение собственной аннотации - volatile поле, которое можно менять из разных потоков
jvm выключает оптимизации: помещение в кэше процессора и т.д.
Изменения данных всегда будет видно всем процессам программы, т.к. обновление будет идти через общее хранилище. - несериализуемое поле - методы на c++ - Контролируемые исключения: - генерация геттеров/сеттеров:
- рекурсивный вызов - может быть преобразован к циклу комплиятором
Рекурсивный вызов самостоятелен (не является частью выражения) - может быть преобразован к циклу: Значение + рекурсивный вызов не может автоматически преобразоваться к циклу, т.к. требуется дойти до конца рекурсии, а потом раскручивать вызовы в обратном порядке:
- @switch - трансформировать swith в таблицу переходов, что оптимальнее if
- @elidable(500) - метод после 500 сборки удалится из скомпилированного кода
- def allDifferent[@specialized(Long, Double) T](x: T, y: T, z: T) = . - генерация специализированных функций под каждый тип
- @deprecated(message = "Use factorial(n: BigInt) instead") - устаревшая функция

Обработка XML

- создание xml переменной: - обработка элементов: - доступ к атрибуту: - обход всех атрибутов: - вставка переменной в xml: - xpath поиск:
-- поиск непосредственного тега body - любой тег - тег li -- поиск тега img на любом уровне вложенности - case => . - выберется если img с любым содержанием
- объекты неизменяемые, для изменения атрибута нужно его скопировать: - для трансформации можно использовать класс с case преобразования внутри:
- загрузка xml: - загрузка с сохранением комментариев: - сохранение xml:

Обобщенные типы

- псевдоним для типа: и обобщенного: также можно указывать ограничения: типы компоненты - псевдонимы без определения: компонентный тип с уточнением:

Дополнительные типы

- составление цепочек вызовов: для того, чтобы она работала и для наследников базового класса нужно указывать "this.type": - псевдоним для длинного типа: - необходимость передачи типа данных с методом "append" - внедрение зависимостей: - абстрактный тип: - то же самое через обобщенный тип:

Неявные преобразования

- функции с неявными преобразованием параметров Int к Fraction - добавление своей функции в стандартный класс:
-- добавляем функцию read -- неявное преобразование класса File к RichFile: -- вместо функции можно использовать класс: дальше обычно создаем объект File
помещаем функции в файл
и обязательно импортируем без префикса: чтобы минимизировать число неявных преобразований, вызов можно делать внутри кода класса
- необязательные параметры: можно заранее объявить неявный разделитель: - Неявная передача параметра ord в зависимости от типа другого параметра (A) Параметр можно задать явно: sort(xs)(Ordering.Int.reverse)
- из 2 реализаций implicit выбирается наиболее точная:
-- по типу -- по вложенности
- можно посмотреть, что подставится в implicit для заданного типа вызовом:

Конкурентное программирование Scala

Реализация многопоточности в JVM

- поток в jvm это обертка над потоком ОС (в отличии от python)
- java-style поток: - простой способ вернуть данные из потока - это объявить глобальную переменную, но использовать ее после join
- объявление синхронизированной функции: - самодельный пул потоков:
-- пул ожидающий работы (wait): -- помещение программы в пул и нотификация, что нужно перестать делать wait -- остановить пул при установке некого флага: - один из способов побороть deadlock - синхронизировать ресурсы в 1 последовательности, чтобы не получилось перекрестной взаимоблокировки
если ресурсы блокируются в 1 последовательности, то 2 поток просто дождется окончания 1
Способ: перед трансфером получить атомарный счетчик в объект и перевод всегдя делается с объекта у которого меньший счетчик, чтобы обеспечить одинаковую последовательность блокировок:

Атомарные изменчивые переменные

такие переменные изменяются за 1 такт, поэтому не могут меняться одновременно в нескольких потоках
на уровне процессора это операция: compareAndSet - установить значение, если compare выполнилось удачно
полный список описан тут: java.util.concurrent.atomic: AtomicBoolean, AtomicInteger, AtomicLong и AtomicReference
Пример использования: Аналог через synchronized: - блоки синхронизации могут вкладываться друг в друга - это лучше, чем делать 1 глобальный лок: - lazy переменные инициируются 1 раз при первом обращении
для проверки инициализации используются дополнительное атомарное поле-флаг
- muttable объекты нужно использовать с блокировками
- шаблон producer / consumer
между потребителем и производителем находится конкурентная очередь с данными:
- BlockingQueue - реализация в Scala: add/remove/element - с исключениями (poll/ofer/peak - со специальным значение) (take, put - блокирующие)
-- ArrayBlockingQueue - очередь фиксированного размера
-- LinkedBlockingQueue - неограниченная очередь (когда производители гарантированно работают быстрее потребителей)

- Конкурентные объекты: -- concurrent.TrieMap() - позволяет обходить массив по его копии, что не блокирует его самого и не дает получить изменяемые данные
-- ConcurrentSkipListSet[Int] //lock free структуры (оптимистическая блокировка с атомарной проверкой)

Параллельные коллекции данных

- параллельный запуск обработки колекции = вызов функции par -- можно распараллелить цикл: -- преобразование параллельной коллекции в последовательную:
- Left операторы не могут обрабатываться параллельно, т.к. используют результат предыдущего выполнения
- Reduce операции сохраняющий тип данных : (A, A) => A могут быть распараллены
-- Дополнительно должно выполняться условие: a op b == b op a
( к примеру сложение просто парралелиться, но не вычитание, т.к. оно зависит от порядка )
- Если хочется результат другого типа, отличного от параметров, то нужно использовать aggregate - Если нужно, чтобы функция принимала или обычную или параллельную коллекцию, то нужно использовать тип GenSeq это интерфейс обоих контейнеров
- Установка уровня параллельности для блока: --- Установка параллельности для оператора: - для преобразования последовательной коллекции к параллельной используются сплиттеры (надстройка над итератором)
-- линейноссылочные структуры List/Stream не имеют эффективного алгоритма Split
при вызове par у этих структур они сначала преобразуются к обычным массивам
- Пример создания своей параллельной коллекции (в данном случае строки): сплиттер должен быть отнаследован от трейта: - необязательные функции:
psplit - ручное задание размеров для сплита
- пример сплита на 2 части: - psplit создает сплиттер с половиной строки: - Комбинаторы - слияние сплитов обратно при использовании методов трансформации: map, filter groupBy

Объекты Future

- создание отдельного потока из пула потоков: -- другой вариант ожидания: -- альтернативный вариант без блокировки:
по результату работы асинхронно вызовется функция: -- объект Future должен возвращать результат или генерировать exception в случае ошибки

Реактивное программирование

События хранятся в сигнале - это иммутабельный объект
- значение в объекте можно менять
- оно будет автоматически проброшено на все зависимые сигналы
Signal = значение , выражение , список наблюдателей + update, который обновит значение используя выражение и оповестит всех наблюдателей о необходимости update - Сравнение классического и реактивного подхода:
-- Классический подход: -- reactive подход: Код подписчика, который считает сумму всех балансов: - зависимое событие:

Программная транзакционная память

- в основе лежит операция atomic
для работы нужна библиотека org.scala-stm атомарные операции не подвержены взаимоблокировкам, из-за этого они используются в оптимистических блокировках:
-- перед началом запоминается состояние памяти
-- если оказывается при записи, что область была изменена, то транзакция рестартуется
- для применения оптимистичной блокировки над переменной, нужно использовать ссылочный тип: - минус оптимистических блокировок:
-- рестарт транзакции, так что в транзакции нельзя вызывать неоткатываемые транзакции - к примеру http запрос
-- дополнительная нагрузка из-за рестарта
- для 1 минуса - "повторный вызов неоткатываемого запроса", можно использовать событие - aftercommit: - в случае исключения внутри atomic - она целиком откатывается
- повторение транзакции, но с таймаутом в 1с: - бесконечный повтор с интервалом в 1с: - для локальных переменных нужно использовать TxnLocal - она индивидуальна в каждой транзакции - транзакционный массив: - транзакционный словарь: -- словарь имеет возможность получить неизменяемый снапшот:

Акторы

Set - это функция, которая принимает Int и возвращает логическое значение. Союз принимает два набора и возвращает третий набор. Поэтому нам нужно сначала два экземпляра экземпляра (две функции).

Теперь у нас есть третий Set, который является объединением oneSet и twoSet

как x Int будет передан как параметр

Поскольку oneUnionTwo в основном является функцией, которая принимает Int. x является единственным параметром этой функции /Set. Вы можете передать его, вызвав Set oneUnionTwo

Почему он ожидает тип первого элемента (Int, Int) => . ?

Int это тип функции с двумя аргументами, Int и то и другое (и это то, что (a: Int, b: Int) => . всегда даст).

((Int, Int)) => . является типом функции с одним (Int, Int) аргументом. map on Array нужна функция с одним аргументом и для Array[(Int, Int)] типа этого аргумента (Int, Int) .

Поэтому вам нужно написать либо

где pair есть тип (Int, Int) , или

(с использованием сопоставления с образцом). Обратите внимание, что в этом случае необходимы фигурные скобки.

Вы создали массив Tuple. Scala tuple объединяет фиксированное количество элементов вместе, чтобы их можно было передавать в целом. В отличие от массива или списка, кортеж может содержать объекты разных типов, но они также неизменяемы.

Для того, чтобы получить доступ к значению кортежа, Scala обеспечивает ._1 , ._2 чтобы получить доступ к значению кортежа. Например

Если в кортеже имеется более двух значений, ._3 будет использоваться для получения третьего значения кортежа.

И аналогичным образом вы создали массивы кортежей2.

Другой способ использования шаблона для получения значения.

Это небольшая неинтуитивная причуда Scala, что map(whatever=>. ) на самом деле одно - тип типа Seq, который вы просматриваете, который вы можете или нужно позже деконструировать. К счастью, как отмечали другие, вы можете использовать сопоставление образцов, и для этого вам нужно заменить скобки фигурными скобками и добавить ключевое слово case и т. map > Д.

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

Читайте также: