Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compose a zero-argument function?

What I want to achieve is the 2 functions (one of them is no arg function) are composed into one.

Here is an example to give you idea what I am doing:

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

}

Compilation error:

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

Any idea what's wrong?

[UPD] The Scala version is 2.13.1 (if it matters).

like image 324
egordoe Avatar asked Dec 09 '19 10:12

egordoe


1 Answers

Desugaring () => 10 we have

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

and Function0 does not have compose or andThen methods

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

so it seems Function0 cannot be composed.

On the other hand (i: Int) => s"hello $i" and (s: String) => println(s) correspond to Function1 which does have compose method defined, hence they can be composed.

Consider changing () => 10 to (_: Unit) => 10 which changes the type from Function0 to Function1, and then

(intArgFunc compose zeroArgFunc)()

outputs res4: String = hello 10.


Addressing comment by @Duelist, IMHO Function0[T] is not semantically equivalent to Function1[Unit, T]. For example, given

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

then

f()
g()

indeed outputs

res7: Int = 10
res8: Int = 10

however

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

where we see the two do not have the same behaviour. Nevertheless, if you would like to consider them as equivalent perhaps you could define an extension method on Function0 and be explicit about conversion, for example

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

would allow the following syntax

(intArgFunc compose zeroArgFunc.toFun1)()

Addressing comment by @egordoe, out-of-the-box compose is only ever defined for Function1, thus Function2, Function3, etc., cannot be composed just like Function0. However we could define extension composeN methods on function, for example, say we want to compose Function1 with Function0, then

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

gives

(intArgFunc compose0 zeroArgFunc)()
like image 56
Mario Galic Avatar answered Oct 06 '22 00:10

Mario Galic