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