2

我无法理解函数文字中的下划线。

val l = List(1,2,3,4,5)
l.filter(_ > 0)

工作正常

l.filter({_ > 0})

工作正常

l.filter({val x=1; 1+_+3 > 0}) // ie you can have multiple statements in your function literal and use the underscore not just in the first statement.

工作正常

但是:

l.filter({val x=_; x > 0})
e>:1: error: unbound placeholder parameter
l.filter({val x=_; x > 0})

_即使以下是合法的函数文字,我也无法将其分配给变量:

l.filter(y => {val x=y; x > 0})

工作正常。

是什么赋予了?我的 'val x=_' 是否被解释为其他内容?谢谢!

4

3 回答 3

8

实际上,您必须后退一步。

您误解了牙套的工作原理。

scala> val is = (1 to 5).toList
is: List[Int] = List(1, 2, 3, 4, 5)

scala> is map ({ println("hi") ; 2 * _ })
hi
res2: List[Int] = List(2, 4, 6, 8, 10)

如果println是传递给 的函数的一部分map,您会看到更多问候。

scala> is map (i => { println("hi") ; 2 * i })
hi
hi
hi
hi
hi
res3: List[Int] = List(2, 4, 6, 8, 10)

您的额外大括号是一个块,它是一些语句,后跟一个结果表达式。结果 expr 是函数。

一旦你意识到只有结果 expr 的预期类型是 预期的函数map,你就不会考虑在前面的语句中使用下划线,因为裸下划线需要预期的类型来确定下划线的含义。

那是类型系统告诉您下划线不在正确的位置。

附录:在您提出的评论中:

如何使用下划线语法将函数文字的参数绑定到变量

这是一个“愚蠢”的问题,请原谅这种表达方式吗?

下划线是这样你不必命名参数,然后你说你想命名它。

一个用例可能是:传入的参数很少,但我只想命名其中一个。

scala> (0 /: is)(_ + _) res10: Int = 15

scala> (0 /: is) { case (acc, i) => acc + 2 * i } res11: Int = 30

这不起作用,但有人可能想知道为什么。也就是说,我们知道折叠期望什么,我们想应用带有 arg 的东西。哪个参数?在部分应用的偏函数之后剩下的任何东西。

scala> (0 /: is) (({ case (_, i) => _ + 2 * i })(_))

或者

scala> (0 /: is) (({ case (_, i) => val d = 2 * i; _ + 2 * d })(_))

SLS 6.23“匿名函数的占位符语法”提到了“expr”边界,当你必须知道下划线代表什么时——它本身不是一个范围。如果您为下划线提供类型归属,它仍然会抱怨预期的类型,大概是因为类型推断是从左到右的。

于 2013-10-25T23:31:06.703 回答
1

因为在这两种情况下,下划线 ( _) 表示两种不同的东西。在函数的情况下,它是 lambda 函数的语法糖,您l.filter(_ > 0)稍后会脱糖成l.filter(x => x > 0). 但在 a 的情况下,var它有另一种含义,不是 lambda 函数,而是一个默认值,并且此行为仅针对var's 定义:

class Test {
  var num: Int = _
}

这里num将被初始化为其类型确定的默认值Int。你不能这样做,val因为 vals 是最终的,如果在 vars 的情况下你可以稍后为它们分配一些不同的值,使用 vals 这没有意义。

更新

考虑这个例子:

l filter {
  val x = // compute something
  val z = _
  x == z
}

根据你的想法,z应该绑定到第一个参数,但是scala应该如何理解这个,或者你在这个计算中有更多的代码然后下划线。

更新 2

scala repl 中有一个 grate 选项:scala -Xprint:type. 如果您打开它并在 ( l.filter({val x=1; 1+_+3 > 0})) 中打印您的代码,您将看到以下内容:

private[this] val res1: List[Int] = l.filter({
  val x: Int = 1;
  ((x$1: Int) => 1.+(x$1).+(3).>(0))
});

1+_+3 > 0脱糖成一个函数:((x$1: Int) => 1.+(x$1).+(3).>(0)),过滤器实际上期望你做什么,一个函数 from Intto Boolean。以下也有效:

l.filter({val x=1; val f = 1+(_: Int)+3 > 0; f})

因为这里的 f 是从 Int 到 Boolean 的部分应用函数,但是下划线没有分配给第一个参数,它对 closes 范围进行了取消:

private[this] val res3: List[Int] = l.filter({
  val x: Int = 1;
  val f: Int => Boolean = ((x$1: Int) => 1.+((x$1: Int)).+(3).>(0));
  f
});
于 2013-10-25T13:34:55.873 回答
1

下划线语法主要是用户用于以下替换:

coll.filter(x => { x % 2 == 0});
coll.filter(_ % 2 == 0);

这只能替换单个参数。这是占位符语法。lambda 的简单语法糖。

在重大情况下,您正在尝试空初始化/默认设置。

对于具有 init 约定的原始类型:

var x: Int = _; // x will be 0

一般情况:

var y: List[String] = _; // y is null
var z: Any = _; // z = null;

为了变得迂腐,它之所以有效,是因为它null是 的唯一实例的引用,它是scala.Null任何类型的子类型,由于协变,它将始终满足类型绑定。看这里

一个非常常见的使用场景,在 ScalaTest 中:

class myTest extends FeatureTest with GivenWhenThen with BeforeAndAfter {
    var x: OAuthToken = _;
   before {
      x = someFunctionThatReturnsAToken;
   }
}

您还可以看到为什么不应该将它与 一起使用val,因为重点是在初始化后更新值。

编译器甚至不会让你,失败:error: unbound placeholder parameter。这是你的确切情况,编译器认为你是默认的,一个未定义的行为val

诸如时序或范围之类的各种约束使这很有用。这与 不同lazy,您在其中预定义将在需要时评估的表达式。

有关_Scala 中的更多用法,请查看此处

于 2013-10-25T13:44:22.523 回答