Пошаговое соединение между функцией высокого порядка Scala для приведенных примеров

мне трудно понять, как сделать переход от определения функции высокого порядка Scala к приведенному примеру. Это было предусмотрено в это слайд-шоу on слайд 81.

вот определение функции высокого порядка:

trait X[A] { def map[B](f: A => B): X[B] }

вот приведенные примеры:

(1 to 10) map { x => x * 2 } // evaluates to Vector(2, 4, ..., 20)
(1 to 10) map { _ * 2 }      // shorthand!

да?! Здесь просто должны быть какие-то шаги, которых мне не хватает. Я понимаю, что примеры могут использовать как определение функции, так и некоторые тонкости Скала. У меня просто недостаточно опыта чтения Scala и создания связующих предположений.

мой фон является Java OO. Теперь я изучаю Scala и функциональное программирование. И это не первый пример, подобного которому я не понял. Это только первый, где я почувствовал, что у меня хватило смелости опубликовать, зная, что я буду выглядеть невежественным.

Я пытался исследовать это. Во-первых, я пошел в Scala "Библия","программирование в Scala 2nd Издание", и попытался понять, если оттуда (страницы 165-9). Затем я сделал поиск здесь, на StackOverflow. И я нашел несколько ссылок, которые говорят по всему району. Но ничто на самом деле не показывает мне, шаг за шагом, связь между определением функции высокого порядка Scala и приведенными примерами таким образом, который сопоставляется с конкретным экземпляром на этом слайде.

вот что я нашел на StackOverflow:

  1. Scala: Мастерская Совет
  2. подробнее о общих функциях Scala
  3. Scala: как определить "общие" параметры функции?

Я только сейчас понял, что я пропустил Google и пришел прямо в StackOverflow. Хммм. Если вы google и найти только правильную ссылку, я хотел бы видеть его. У меня закончилось время, чтобы просеять все ссылки Google, которые используют такие термины, как monkey-monad, blastomorphisms и т. д. оставляя меня еще больше. сбит с толку и вряд ли попытается разобраться в этом.

7 ответов


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

trait X[A] { def map[B](f: A => B): X[B] }

я бы прочитал это как: Дан класс сборник X над элементами типа A:

  • он имеет map функция, параметризованная для типа B
  • на map функция принимает функцию f преобразование одного A один B
  • map возвращает коллекцию того же типа X над элементами типа B.

затем он переходит к пример иллюстрирует использование:

(1 to 10) map { x => x * 2 }

Итак, соединяя точки:

  • коллекция X - тип (от 1 до 10), здесь Range
  • f: A => B is x => x * 2 который выводится как функция, принимающая Int и возврат и Int.
  • учитывая подпись, вы думаете, что это вернет Range над Int, но это на самом деле возвращает IndexedSeq.

лучшим примером может быть:

List(1, 2, 3).map(i => i + "!") // a List[Int]
// returns a List[String]: List("1!", "2!", "3!") 

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

в этом случае это метод под названием map определено на таких вещах, как списки, массивы, а также многие другие виды контейнеров. Когда вызывается на 1 to 10 диапазона чисел от 1 до 10, в лице Range[Int] в Scala он пересекает их один за другим и применяет функцию (ту, которая была передана как параметр) для каждого числа внутри диапазона. Результаты этой функции накапливаются в новом контейнере -Vector[Int] в этом случае, который возвращается в результате map метод.

так (1 to 10) map { x => x * 2 } который является синтаксическим сахаром btw для (1 to 10).map(x => x * 2) применяется x => x * 2 на номера от 1 to 10. Вы можете думать об этом как о функции обратного вызова. Вы также могли бы написать так:

(1 to 10).map( new Function1[Int, Int] {
   override def apply(x: Int) = x * 2
})

давайте определим тип данных с помощью метода map, односвязного списка.

sealed abstract class MyList[+A] {
  def map[B](f: A => B): MyList[B]  // higher order function declaration.
  def head: A
  def tail: MyList[A]
}
case class Cons[A](head: A, tail: MyList[A]) extends MyList[A] {
  def map[B](f: A => B): MyList[B] = Cons[B](f(head), tail.map(f))
}
case object Nil extends MyList[Nothing] {
  def map[B](f: Nothing => B): MyList[B] = this
  def head = sys.error("head on empty list")
  def tail = sys.error("tail on empty list")
}

список либо пуст, либо это одно значение (head) в паре с остальной частью списка (tail). Мы представляем два случая как иерархию классов, простирающуюся от запечатанного родительского класса MyList.

обратите внимание на реализацию Cons#map мы использовали параметр f дважды, во-первых, для выполнения функции с помощью head, а во-вторых, чтобы перейти к рекурсивный вызов tail.map.

синтаксис f(head) сокращенно от f.apply(head), стоимостью f является экземпляром класса Function1, что определено apply метод.

пока все хорошо. Позволять.s создайте список.

val list: MyList[Int] = Cons(1, Cons(2, Nil))

теперь мы хотим преобразовать список Ints новый список Strings, Преобразуя каждый элемент. В Java, вы бы явно анонимно подкласс Function1, следующим образом.

// longhand:
val stringList1: MyList[String] = list.map[String](new Function1[Int, String] {
  def apply(a: Int): String = a.toString
})

вот законно в Scala, но отношение сигнал / шум не велико. Давайте вместо этого используем синтаксис анонимной функции.

val stringList2: MyList[String] = list.map[String]((a: Int) => a.toString)

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

во-первых, давайте выведем тип параметра a, на основе типа элемента list.

val stringList3: MyList[String] = list.map[String](a => a.toString)

действительно простые функции, подобные этим, также могут быть выражены с помощью синтаксиса заполнителя. Вместо того чтобы объявив параметры, просто напишите код и используйте _ для любого неизвестного количества. Делая это, а также позволяя тип stringList4 вывод:

val stringList4 = list.map(_.toString)

давайте просто сосредоточимся на карта метод, определенный как определено на вашем признаке X

def map[B](f: A => B): X[B]

Ok, так что это метод с одним параметром,f. Тип f (бит после двоеточия) - это A => B. Это тип функции; это сокращение для признака Function1[A, B] но я предпочитаю не думать об этих чертах вообще и просто как-то который может произвести B для заданного значения a.

так: функцияA => B is то, что принимает один экземпляр типа A и возвращает один экземпляр типа B. Вот несколько примеров их объявления

val f = (i: Int) => i.toString //Int => String
val g = (_ : String).length    //String => Int, using placeholder syntax

так что теперь подумайте о том, что X[A] - это мог бы быть типом коллекции, например List. Если у вас есть List[String] и String => Int функции g выше, то вы очевидно можете найти List[Int] in, применяя функцию к каждому элементу в списке, построение нового списка с результатами.

Итак, теперь вы можете сказать:

strings map g //strings is a List[String]

но Scala позволяет объявлять функцию анонимно. Что это значит? Ну, это означает, что вы можете объявить его в точке использования inline, а не объявлять его как val или var. Часто их называют "лямбда". Синтаксис, который вас озадачивает, - это два варианта таких функций.

strings map { (x: String) => x.length }
strings map { x => x.length }
strings map { _.length }
strings map ( _.length )

это все в основном одно и то же. Первый явно объявляет функцию, передаваемую map. Поскольку scala имеет вывод типа, в этом случае можно опустить тип ввода функции. Заполнитель синтаксис _ используется вместо идентификатора x и хороший кусочек сахара в случае, если вам нужно только обратиться к входу один раз. И вы можете использовать скобки вместо фигурных скобок во многих случаях, за исключением нескольких функций.


ваш пример относится к Scala Collection Framework, которая сама имеет некоторое сложное использование системы типов для получения наиболее конкретного типа при преобразовании коллекции. Теперь,механизм что позволяет это трудно понять и на самом деле не имеет отношения к примеру. Функция более высокого порядка просто функция или метод который принимает как аргумент (или возвращает) другие функции. Пример несколько затемняется добавлением типа параметры и не упоминая использование фреймворков коллекции Scala implicits.


не уверен точно, что вы не получите, но объяснить примеры:

trait X[A] { def map[B](f: A => B): X[B] }

A trait похож на интерфейс Java, который также может иметь конкретные методы.

X - имя признака и [A] является параметром типа-подумайте о Java generics <A>. (Часто A, B etc используются для типов элементов в коллекциях и T в другом месте, но это всего лишь условность.)

признак указывает член с именем map, который является метод с другим параметром типа [B], и принимая аргумент функции типа A => B, С типом возврата X[B]. Здесь это абстрактно, потому что нет тела метода.

бит, который вы можете пропустить, это A => B сокращенно Function1[A, B]. Function1 тип объектов функция принимает 1 аргумент. (A, B) => C сокращенно Function2[A, B, C] etc. Вы можете сделать свой собственный Function типы на Java-это забавное упражнение. Объект function по существу является только тем, который имеет apply метод, производящий результат из некоторых аргументов.

(1 to 10) map { x => x * 2 }    

к ним относятся точечные обозначения, где a.method(b) написано a method b. Так что to метод RichInt принимать Int и получения Range. map метод Range принимая аргумент Function1 (помните, что функция - это просто объект типа Function1).

=> также используется для написания самих функций (в дополнение к уровню типа, как описано выше.) Таким образом, все объекты типа Int => Int:

(x: Int) => x + 1
new Function1[Int, Int] { def apply(x: Int) = x + 1 }
  // note Function1 is a trait, not a class, 
  // so this the same as `new Object with Function[Int, Int]`
new (Int => Int) { def apply(x: Int) = x + 1 }

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

val f : Int => Int  =  x => x + 1
val f : Int => Int  =  _ + 1

надеюсь, вы можете увидеть, что означает эта нотация подчеркивания. Подчеркивание полезно, Так как в противном случае всегда будет некоторое повторение, как RHS функции определение должно использовать параметры из LHS. Другим примером может быть отображение функции a String по длине:

val f: String => Int = _.length

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

val f = (_: String).length

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


Скала: (1 to 10) map { x => x * 2 }
     Английский:возьмите значения от 1 до 10 и умножьте каждый на 2.

обратите внимание:

  • (от 1 до 10), Scala распознает, что это набор целых чисел, в частности Range[Int]. Он может быть преобразован в другой тип коллекции, например. (1 to 10).toList

  • карта нижнего регистра. Думайте глаголом, чтобы отобразить от вещи к другой.

  • {x => x * 2}, окружен фигурные-фигурные скобки. Это означает, что это функция без имени,анонимная функция.

  • Underbar ( _ ) можно заменить на x => x


Скала: trait X[A] { def map[B](f: A => B): X[B] }
     Английский:
       мы определяем признак, который мы можем добавить в класс X, тип A.
       это есть метод, который принимает значение и отображает его в другое значение для нового класса Х.

Примечание:

  • X[A] и X[B] имеют один и тот же тип коллекции, но могут иметь элементы разного типа, например. `(1 к 10).список карта { _.toSTring } отобразит список [Int] в список[String].

  • f: A => B, это означает, что map принимает функцию в качестве аргумента и имеет один параметр типа A и возвращает тип B.

  • map определяется во всех типах коллекции Scala. Вы бы обычно не определяешь это сам.