如何组成一个零参数函数?

How to compose a zero-argument function?

我想实现的是将2个函数(其中一个是无参数函数)合二为一。

这里有一个例子可以让你了解我在做什么:

object Test extends App {

  val zeroArgFunc = () => 10
  val intArgFunc = (i: Int) => s"hello $i"
  val stringArgFunc = (s: String) => println(s)

  // This line works perfectly fine.
  val intAndThenString: Int => Unit = stringArgFunc compose intArgFunc

  // But this line fails with 'type mismatch' compilation error.
  val zeroAndThenInt: () => String = intArgFunc compose zeroArgFunc

}

编译错误:

[error]  found   : () => Int
[error]  required: ? => Int
[error]   val zeroAndThenInt: () => String = intArgFunc compose zeroArgFunc
[error]                                                         ^
[error] one error found

知道哪里出了问题吗?

[UPD] Scala 版本是 2.13.1(如果重要的话)。

脱糖 () => 10 我们有

new Function0[Int] { def apply() = 10 }

Function0没有composeandThen方法

trait Function0[... +R] extends ... { ...
  def apply(): R
  override def toString(): String = "<function0>"
}

所以好像Function0是编不出来的。

另一方面,(i: Int) => s"hello $i"(s: String) => println(s) 对应于 Function1,它确实定义了 compose 方法,因此它们可以组合。

考虑将 () => 10 更改为 (_: Unit) => 10,这会将类型从 Function0 更改为 Function1,然后

(intArgFunc compose zeroArgFunc)()

输出res4: String = hello 10.


针对@Duelist 的评论,恕我直言 Function0[T] 在语义上不等同于 Function1[Unit, T]。例如,给定

val f = () => 10
val g = (_: Unit) => 10

然后

f()
g()

确实输出

res7: Int = 10
res8: Int = 10

然而

f(println("woohoo")) // error: no arguments allowed for nullary method apply                                                             
g(println("woohoo")) // OK!

我们看到两者没有相同的行为。尽管如此,如果您想将它们视为等效的,也许您可​​以在 Function0 上定义一个扩展方法并明确转换,例如

implicit class Fun0ToFun1[A, B](f: () => A) {
  def toFun1: Unit => A = (_: Unit) => f()
}

将允许以下语法

(intArgFunc compose zeroArgFunc.toFun1)()

解决 @egordoe 的评论,开箱即用的 compose 只为 Function1 定义,因此 Function2Function3 等, 就像 Function0 一样组成。然而,我们可以在函数上定义扩展 composeN 方法,例如,假设我们想将 Function1Function0 组合,然后

implicit class ComposeFun1WithFun0[A, B](f1: A => B) {
  def compose0(f2: () => A): () => B = () => f1(f2())
}

给予

(intArgFunc compose0 zeroArgFunc)()

compose on Function1intArgFunc 是)只是 defined 只接受单参数函数:

def compose[A](g: (A) => T1): (A) => R

您可以编写辅助函数来转换 () => A to/from Unit => A

def toUnitFun[A](f: () => A): Unit => A = _ => f()
def fromUnitFun[A](f: Unit => A): () => A = () => f(())

然后

val zeroAndThenInt = fromUnitFun(intArgFunc compose toUnitFun(zeroArgFunc))

您甚至可以通过将 to/fromUnitFun 标记为 implicit 来让您的原始代码正常工作。