Scala 下划线_ 简化匿名函数注意事项

Scala 下划线_ 简化匿名函数注意事项

eta-expansion 概念

  • 把 x => func(x) 简化为 func _ 或 func 的过程称为 eta-conversion
  • 把 func 或 func _ 展开为 x => func(x) 的过程为 eta-expansion

Eta Expansion 的就近expand 解析原则
Underscores extend outwards to the closest closing Expr: top-level expressions or expressions in parentheses
翻译:下划线_ 组成的表达式遇到括号() 或者最顶层表示它就会发生函数的扩展eta-expansion

  • 重点1:下划线组成的表达式,如果仅有下划线_ 不算是表达式
  • 重点2:下划线表达式遇到括号() 就会发生eta-expansion

多个括号嵌套时不要用_ 简写

# 使用scala REPL 演示
scala << 'EOF'
// 错误写法
List(1, 2, 3).map((_*2)+1)
// _*2 属于下划线表达式,遇到括号它就会转化为(x)=>{x*2}
// ((_*2)+1) 就变成了((x)=>{x*2} + 1)// 一个匿名函数+1,结果不知道是什么东西自然报错

// 正确写法
List(1, 2, 3).map(_*2+1)
// (_*2+1) 转化为(x)=>{x*2+1}
// 正常的匿名函数,函数参数类型根据List 元素推断为Int

// 建议使用正常的匿名函数写法,避免简写解析错误
List(1, 2, 3).map(x=>{x*2+1})

// 另外,单个_ 不构成表达式,以下写法都是正确的,且效果一样
List(1, 2, 3).map(_+1)
List(1, 2, 3).map((_)+1)
// 但是_.toString 就属于表达式了
List(1, 2, 3).foreach(println(_))           // 正常
List(1, 2, 3).foreach(println(_.toString))  // 报错
// 原因同上,下划线表达式(_.toString) 转化成了x=>x.toString 匿名函数
// 而println 只接收String 类型的参数,所以运行报错

// 退出scala REPL
:quit
EOF

参数类型不确定时不能用_ 简写

# 使用scala REPL 演示
scala << 'EOF'
// 定义高阶函数(普通函数作为其参数)
def ff(f: => Unit) { }

// 错误传入函数
ff(_ + 1)  // 报错
// 下划线表达式(_ + 1) 会被转化成((x) => {x + 1})
// x 的数据类型无法推断导致报错,这和集合的map 方法不同
// map 会根据集合元素推断作为参数的匿名函数的参数类型
List(1, 2, 3).map(_ + 1)  // 正常

// 正确传入函数
ff((x: Int) => {x + 1})   // 正常
// 明确作为参数的匿名函数的参数类型
// 实际上,ff 定义时忽略了参数函数的参数类型
// 即ff 可以接收没有参数的代码块{}
// 但代码块也是明确告诉高阶函数参数函数没有参数
ff({ })  // 正常
// 只要是明确指出,无论有没有参数都可以正常运行

// 退出scala REPL
:quit
EOF

参考资料

scala 下划线解析报错
scala eta-expansion

你可能感兴趣的:(Scala 下划线_ 简化匿名函数注意事项)