为什么变量不能在函数文字中分配占位符?

时间:2013-10-25 13:28:08

标签: scala

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

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 = _'被解释为别的吗?谢谢!

3 个答案:

答案 0 :(得分: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所期望的函数的期望类型,你就不会想到在前面的语句中使用下划线,因为裸下划线需要预期的类型来指示下划线意味着什么。

这是类型系统告诉你,你的下划线不在正确的位置。

附录:在评论中你问:

  

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

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

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

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

阶> (0 /:是)(_ + _) res10:Int = 15

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

这不起作用,但人们可能想知道为什么。也就是说,我们知道折叠需要什么,我们想用arg来应用。哪个arg?无论在部分应用的部分函数之后剩下的是什么。

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

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

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

答案 1 :(得分:1)

因为在这两种情况下,下划线(_)意味着两种不同的东西。在函数的情况下,它是lambda函数的语法糖,你的l.filter(_ > 0)后来变为l.filter(x => x > 0)。但是在var的情况下,它具有另一个含义,不是lambda函数,而是默认值,并且此行为仅针对var定义:

class Test {
  var num: Int = _
}

此处num将初始化为其类型Int确定的默认值。您无法使用val执行此操作,因为val是最终的,如果是vars,您可以稍后为它们分配一些不同的值,使用val这没有意义。

<强>更新

考虑这个例子:

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

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

更新2

scala repl中有一个格栅选项: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)),过滤器实际需要的是一个函数,IntBoolean的函数。以下也有效:

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

因为这里是从Int到Boolean的部分应用函数,但是下划线没有分配给第一个参数,它是关闭关闭范围的:

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
});

答案 2 :(得分:1)

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

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

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

在破坏的情况下,您正在尝试空初始化/默认。

对于具有init约定的基本类型:

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

一般情况:

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

为了得到迂腐,它起作用,因为nullscala.Null的唯一实例的引用,是任何类型的子类型,由于协方差,它总是满足绑定的类型。看HERE

ScalaTest中一种非常常见的使用场景:

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

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

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

各种约束,例如时间或范围,使其有用。 这与lazy不同,在_中,您可以预定义将在需要时进行评估的表达式。

有关Scala中{{1}}的更多用法,请查看HERE