В Scala, в чем разница между использованием `_` и именованным идентификатором?
Почему я получаю сообщение об ошибке при попытке использовать _
вместо использования именованного идентификатора?
scala> res0
res25: List[Int] = List(1, 2, 3, 4, 5)
scala> res0.map(_=>"item "+_.toString)
<console>:6: error: missing parameter type for expanded function ((x$2) => "item
".$plus(x$2.toString))
res0.map(_=>"item "+_.toString)
^
scala> res0.map(i=>"item "+i.toString)
res29: List[java.lang.String] = List(item 1, item 2, item 3, item 4, item 5)
3 ответа
Символы подчеркивания, используемые вместо имен переменных, являются специальными; N-е подчеркивание означает N-й аргумент анонимной функции. Таким образом, следующее эквивалентно:
List(1, 2, 3).map(x => x + 1)
List(1, 2, 3).map(_ + 1)
Но, если вы сделаете это:
List(1, 2, 3).map(_ => _ + 1)
Затем вы отображаете список с помощью функции, которая игнорирует единственный аргумент и возвращает функцию, определенную как _ + 1
, (Этот конкретный пример не будет компилироваться, потому что компилятор не может определить тип второго подчеркивания.) Эквивалентный пример с именованными параметрами будет выглядеть так:
List(1, 2, 3).map(x => { y => y + 1 })
Короче говоря, использование подчеркивания в списке аргументов функции означает "я игнорирую эти аргументы в теле этой функции". Использование их в теле означает "Компилятор, пожалуйста, сгенерируйте список аргументов для меня". Два использования не очень хорошо сочетаются.
В дополнение к другим ответам, вот несколько примеров, показывающих, почему в некоторых случаях вы используете "пропущенный тип параметра" при использовании "_" в качестве параметра-заполнителя.
Вывод типа Scala рассматривает "ожидаемый" тип выражения на основе его контекста. Если нет контекста, он не может определить тип параметров. Обратите внимание на сообщение об ошибке первого и второго экземпляров _
заменяются идентификаторами, сгенерированными компилятором x$1
а также x$2
,
scala> _ + _
<console>:5: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
_ + _
^
<console>:5: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))
_ + _
^
Добавление надписи типа ко всему выражению обеспечивает достаточный контекст, чтобы помочь логическому выводу:
scala> (_ + _) : ((Int, Int) => Int)
res3: (Int, Int) => Int = <function2>
Кроме того, вы можете добавить атрибуцию типа для каждого заполнителя параметра:
scala> (_: Int) + (_: Int)
res4: (Int, Int) => Int = <function2>
В приведенном ниже вызове функции с предоставленными аргументами типа контекст однозначен, а тип функции выводится.
scala> def bar[A, R](a1: A, a2: A, f: (A, A) => R) = f(a1, a2)
bar: [A,R](a1: A,a2: A,f: (A, A) => R)R
scala> bar[Int, Int](1, 1, _ + _)
res5: Int = 2
Однако, если мы попросим компилятор вывести параметры типа, если произойдет сбой:
scala> bar(1, 1, _ + _)
<console>:7: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
bar(1, 1, _ + _)
^
<console>:7: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))
bar(1, 1, _ + _)
^
Мы можем помочь, хотя, карри списки параметров. Здесь аргументы для первого списка параметров (1, 1)
, сделай вывод, что параметр type A
должно быть Int
, Затем он знает, что тип аргумента f
должно быть (Int, Int) => ?)
и тип возвращаемого значения R
выводится как Int
, результат целочисленного сложения. Вы увидите тот же подход, используемый в Traversable.flatMap
в стандартной библиотеке.
scala> def foo[A, R](a1: A, a2: A)(f: (A, A) => R) = f(a1, a2)
foo: [A,R](a1: A,a2: A)(f: (A, A) => R)R
scala> foo[Int, Int](1, 1) { _ + _ }
res1: Int = 2
scala> foo(1, 1) { _ + _ }
res0: Int = 2
Если вы не собираетесь связывать идентификатор, просто пропустите эту часть.
res0.map("item "+_.toString)