我无法理解函数文字中的下划线。
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 = _'被解释为别的吗?谢谢!
答案 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))
,过滤器实际需要的是一个函数,Int
到Boolean
的函数。以下也有效:
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;
为了得到迂腐,它起作用,因为null
是scala.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。