【问题标题】:Map with higher-order functions具有高阶函数的映射
【发布时间】:2015-10-07 16:31:20
【问题描述】:

这些语句有什么区别?

val numbers = List(1, 2, 3, 4)
numbers map println
// vs.
numbers map (x => println(x))

Scala 怎么知道 println 会重复处理每个数量的 numbers

谢谢。

【问题讨论】:

  • 你在第二个例子中所做的是不必要的函数包装 - 你有一个函数 (println) 已经做了你想要的,然后你把它包装在一个匿名函数中(一个 lambda 表达式) 它只是将参数传递给println
  • println 不是函数,而是方法。您不能传递方法,方法不是 Scala 中的对象。所以,你必须将它包装在一个函数中无论如何。您可以通过 η-expansion 使用 _ 下划线 (println _) 来执行此操作,或者,如果 Scala 可以从上下文推断您不想调用该方法而是将其转换为函数(就像case here),然后它会为你执行 implicit η-expansion。无论哪种方式,无论您使用显式 η-expansion、隐式 η-expansion 还是显式 λ,您都必须从该方法创建一个函数。

标签: scala


【解决方案1】:

这些语句有什么区别?

val numbers = List(1, 2, 3, 4)
numbers map println
// vs.
numbers map (x => println(x))

map 将转换单个元素的函数作为其参数。然后map 会将此函数应用于每个元素并返回一个包含转换后元素的集合。 (顺便说一句,这就是为什么这段代码没有意义的原因:a)map 的返回值被忽略,b)println 总是返回Unit,所以你最终会得到一个Units 的列表. foreach 是在这里使用的正确方法。)

在您的第二个示例中,您将 λ(一个匿名的一等函数对象)传递给 map

在第一个示例中,究竟你传递给map的是什么?看起来您正在传递一个方法,但那不可能:a) map 接受一个函数,而不是一个方法,并且 b) 方法不是对象,所以您只是 不能 传递反正他们在身边。您必须以某种方式将方法转换和/或包装到函数中。

在您的第二个示例中,您非常明确地这样做:您有一个调用该方法的函数。第一个例子更加隐含。 Scala 有一个叫做 η-expansion 的特性,它可以让你“提升”一个方法来运行,这会将方法部分应用到它的隐式 this 参数,但不绑定它的其他(常规)参数。

其语法是无处不在的下划线:foo.bar _ 表示“采用foo 的方法bar,将其this 绑定到foo 并将其转换为函数”。在这种特殊情况下,您使用println,将其this 绑定到顶级REPL 上下文对象(这并不重要,因为println 实际上并不关心它的this,所以使用它像一个过程)并把它变成一个函数。

但是等等,没有下划线?这是正确的。如果 Scala 编译器可以确定您实际上不想调用该方法,而是将其包装到一个函数中(即,当您不传递参数列表时,该方法需要一个参数列表,并且上下文需要一个与方法的类型兼容的类型),那么 Scala 将为您执行 隐式 η-expansion

那么,让我们回到你的问题:

这些语句有什么区别?

嗯,一方面,差异是我上面描述的:第一个示例使用隐式 η 展开,第二个示例使用显式 λ。 OTOH,没有区别:他们都从 println 方法创建一个函数,第二个只是以更复杂的方式。

Scala 怎么知道 println 会重复处理每个数量的 numbers

它没有。 a) “处理每个数字”的不是 println,而是 map,并且 b) Scala 什么都不知道,这就是 map 的编写方式。

非常简单,map 看起来像这样:

sealed trait MyList[+T] {
  def map[U](function: T ⇒ U): MyList[U]
  def foreach(procedure: T ⇒ Unit)
}

case object EmptyList extends MyList[Nothing] {
  // mapping the empty list returns the empty list
  def map[U](function: Nothing ⇒ U) = EmptyList

  def foreach(procedure: Nothing ⇒ Unit) = ()
}

final case class ListCell[+T](first: T, rest: MyList[T]) extends MyList[T] {
  // mapping a non-empty list returns the result of transforming the first 
  // element of the list and recursively mapping the rest of the list
  def map[U](function: T ⇒ U) = ListCell(function(first), rest map function)

  def foreach(procedure: T ⇒ Unit) = { procedure(first); rest foreach procedure }
}

下面是我们如何使用这个简单的小列表:

val list: MyList[Short] = ListCell(1, ListCell(2, ListCell(3, EmptyList)))

list map (1+)
// => ListCell(2, ListCell(3, ListCell(4, EmptyList)))

list map(_.toString)
// => ListCell(1, ListCell(2, ListCell(3, EmptyList)))

list map println
// 1
// 2
// 3
// => ListCell((), ListCell((), ListCell((), EmptyList)))
// here you can see the useless list of Units that is returned

list foreach println
// 1
// 2
// 3
// foreach just returns nothing

【讨论】:

    【解决方案2】:

    map 期待一个函数,该函数具有一个值并产生一个值。在您的第一个示例中,您通过提供 lambda 显式提供这样的函数。在第二种情况下,编译器可以检查类型并看到println : A => Unit。请注意,通常情况下,您不希望将map 用于产生Unit 的函数,这更适合foreach

    【讨论】:

      【解决方案3】:

      这两个语句具有相同的语义,第二个版本只是添加了一个实际上并不需要的间接级别。

      问题是 map 函数将一个函数作为参数,该函数能够对 Int 进行操作并生成一个 Unit:

      final def map[B](f: (A) ⇒ B): List[B]
      

      在您的示例中,A 是 Int,B 是 Unit。

      现在,如果您查看 println 方法的签名和您的匿名函数之一,您会发现它们实际上是相同的:

      def println(x: Any): Unit
      (x => println(x)): Any -> Unit
      

      发生的事情是 map 函数遍历列表中的元素,并且对于每个元素 x,它调用函数 f(x)。由于 println 和匿名函数都接受 Any 作为输入映射,因此可以安全地将 x Int 传递给它们。

      使用匿名函数只是添加了一个中间函数调用步骤。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-25
        • 1970-01-01
        • 2020-03-11
        • 1970-01-01
        • 2021-06-24
        相关资源
        最近更新 更多