Написание своего собственного метода map

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

Представим, что у класса List нет метода map, и есть необходимость его написать. Первым шагом при создании функций является точное определение проблемы. Сосредоточившись только на List[Int], получаем:

Необходимо написать метод map, который можно использовать для применения функции к каждому элементу в List[Int], возвращая преобразованные элементы в виде нового списка.

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

def map(f: (Int) => A)

Синтаксис использования универсального типа требует объявления этого символа типа перед списком параметров, поэтому добавляем объявление типа:

def map[A](f: (Int) => A)

Далее известно, что map также должен принимать List[Int]:

def map[A](f: (Int) => A, xs: List[Int])

Наконец, также известно, что map возвращает преобразованный список, содержащий элементы универсального типа A:

def map[A](f: (Int) => A, xs: List[Int]): List[A] = ???

Теперь все, что нужно сделать, это написать тело метода. Метод map применяет заданную им функцию к каждому элементу в заданном списке для создания нового преобразованного списка. Один из способов сделать это - использовать выражение for:

for x <- xs yield f(x)

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

Объединив for с сигнатурой метода, получим автономный метод map, который работает с List[Int]:

def map[A](f: (Int) => A, xs: List[Int]): List[A] =
  for x <- xs yield f(x)
Обобщим метод map

Обратим внимание, что выражение for не делает ничего, что зависит от типа Int внутри списка. Следовательно, можно заменить Int в сигнатуре типа параметром универсального типа B:

def map[A, B](f: (B) => A, xs: List[B]): List[A] =
  for x <- xs yield f(x)

Получился метод map, который работает с любым списком.

Демонстрация работы получившегося map:

def double(i : Int) = i * 2
def strlen(s: String) = s.length

map(double, List(1, 2, 3))
// res0: List[Int] = List(2, 4, 6)
map(strlen, List("a", "bb", "ccc"))
// res1: List[Int] = List(1, 2, 3)

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


Ссылки: