【问题标题】:In Scala, what is the difference between using the `_` and using a named identifier?在 Scala 中,使用 `_` 和使用命名标识符有什么区别?
【发布时间】:2010-03-01 00:04:03
【问题描述】:

当我尝试使用 _ 而不是使用命名标识符时,为什么会出现错误?

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)

【问题讨论】:

    标签: scala wildcard


    【解决方案1】:

    用于代替变量名的下划线是特殊的;第 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 })
    

    简而言之,在函数的参数列表中使用下划线意味着“我忽略了这个函数体中的这些参数”。在正文中使用它们意味着“编译器,请为我生成一个参数列表”。这两种用法不能很好地混合。

    【讨论】:

    • @Scoobie 为了强调这一点,下划线在 Scala 中用于许多不同的目的。正如大卫解释的那样,您示例中的每个用法实际上都有不同的含义。还有其他含义——在 Scala 中,下划线是运算符重载引起的问题的一个很好的例子。虽然一开始我遇到了问题,但老实说,我从来没有想过改进它的方法。
    【解决方案2】:

    为了补充其他答案,这里有一些示例说明为什么在某些情况下使用“_”作为占位符参数时会出现“缺少参数类型”。

    Scala 的类型推断根据其上下文考虑表达式的“预期”类型。如果没有上下文,则无法推断参数的类型。请注意,在错误消息中,_ 的第一个和第二个实例被替换为编译器生成的标识符 x$1x$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) 的参数告诉推断类型参数A 应该是Int。然后它知道参数f的类型必须是(Int, Int) =&gt; ?),返回类型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
    

    【讨论】:

      【解决方案3】:

      如果您不打算绑定标识符,只需将那部分省略即可。

      res0.map("item "+_.toString)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-15
        • 1970-01-01
        • 1970-01-01
        • 2021-04-24
        • 2011-12-10
        • 1970-01-01
        • 2015-10-28
        • 1970-01-01
        相关资源
        最近更新 更多