Пошаговое соединение между функцией высокого порядка Scala и приведенными примерами - PullRequest
7 голосов
/ 26 февраля 2012

Мне трудно понять, как перейти от определения функции высокого порядка Scala к приведенному примеру. Это было предоставлено в этом слайд-шоу на слайде 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. У меня просто недостаточно опыта в чтении Scala и создании связующих предположений.

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

Я пытался исследовать это. Сначала я отправился в «Библию» Scala, «Программирование в Scala, 2-е издание» , и попытался разобраться, если оттуда (стр. 165-9). Затем я выполнил поиск здесь в StackOverflow. И я нашел несколько ссылок, которые говорят вокруг области. Но на самом деле ничего, STEP-BY-STEP, не показывает мне связь между определением функции высокого порядка Scala и предоставленными примерами таким образом, который сопоставляет конкретный экземпляр на этом слайде.

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

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

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

Ответы [ 7 ]

6 голосов
/ 26 февраля 2012

Я думаю, что это просто пример подписи, предназначенной для отображения некоторых свойств коллекции 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 - это 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!") 
6 голосов
/ 26 февраля 2012

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

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

То есть (1 to 10) map { x => x * 2 }, что является синтаксическим сахаром для (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
})
5 голосов
/ 26 февраля 2012

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

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.

Пока все хорошо. Давайте составим список.

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

Теперь мы хотим преобразовать список Ints в новый список String s, преобразовав каждый элемент. В 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)
3 голосов
/ 26 февраля 2012

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

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

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

Итак: функция A => B - это то, что принимает один экземпляр типа 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], применив функцию к каждому элементу в списке, построение нового списка с результатами.

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

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 )

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

2 голосов
/ 26 февраля 2012

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

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

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

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

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

Бит, который вы можете упустить, это то, что A => B - это сокращение от Function1[A, B]. Function1 - это тип функциональных объектов, принимающих 1 аргумент. (A, B) => C - это сокращение от Function2[A, B, C] и т. Д. Вы можете создавать свои собственные Function типы в Java - это забавное упражнение. Функциональный объект - это, по сути, просто объект, имеющий метод 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. Другим примером может быть функция, отображающая String в его длину:

val f: String => Int = _.length

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

val f = (_: String).length

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

2 голосов
/ 26 февраля 2012

Ваш пример относится к Scala Collection Framework, который сам по себе имеет изощренное использование системы типов для получения наиболее конкретного типа при преобразовании коллекции.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Механизм * 100 2 *, который позволяет это понять, трудно понять, и он не очень подходит для примера.Функция высшего порядка - это просто функция или метод , который принимает в качестве аргумента (или возвращает) другие функции .Пример несколько затемняется добавлением параметров типа и отсутствием упоминания об использовании имплицитов в Scala Collection Frameworks.

1 голос
/ 27 февраля 2012

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

Некоторые замечания:

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

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

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

  • Подставка (_) может быть заменена на x => x


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

Примечание:

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

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

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

...