Strange Scala Syntax wherein Future is mapped such that "==" and "!=" appear with only one operand (not two)

Viewed 123

I came across a puzzling, but interesting, code construct that I whittled down to a small example, and that I still have trouble wrapping my head around.

The example is shown below. Note that I have a simple Future that immediately returns a String. I map this to a comparison of the Future itself using != and ==

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

object Dummy extends App {
  val taskReturningString: Future[String] =  Future{ "foo"}

  val falseResult: Future[Boolean] = taskReturningString.map(taskReturningString ==)
  System.out.println("false result:" + Await.result(falseResult, 0 nanos) )

  val trueResult: Future[Boolean] = taskReturningString.map(taskReturningString !=)
  System.out.println("true result:" + Await.result(trueResult, 0 nanos) )
}

The output is

    false result:false
    true result:true

But I'm not sure why I got those results. In the case of ==, and != the first item being compared is 'taskReturningString' -- the Future. But what is it being compared to ? I am assuming that what is happening is a comparison, but I've never seen a case where the operators == and != appear with one operand instead of two.

2 Answers

That behavior is due to eta expansion. Those map need a function String => Boolean (because type inference) and taskReturningString == is a method that can be expanded to that kind of function.

Here is a simplified example.

val equals: String => Boolean = "foo" ==

println(equals("foo"))
// true

println(equals("bar"))
// false

or with +

val plusTwo: Int => Int = 2 +
println(plusTwo(2))
// 4

drop from String

val drop: Int => String =  "abcd".drop
println(drop(2))
// cd

or ::: from List

val concat: List[Int] => List[Int] = List(1,2,3) :::
println(concat(List(4,5,6)))
// List(4,5,6,1,2,3)

The use of _ is not always necessary if the compiler realizes that it can expand a method with missing parameters and the types are correct.

This code doesn't work because there is no way that the compiler knows the type of equals

val equals = "foo" ==

so I need to help it with _

val equals = "foo" == _

The answer for this lies with following facts,

Fact 1: Even operators are methods in Scala,

// something like
val a = "abc" == "def"

// is actually
val a = "abc".==("def")

So, taskReturningString == is actually method taskReturningString.==

Fact 2: methods can be converted to functions (by using _),

val string1 = "abc"

val func1 = string1.== _
// func1: Any => Boolean = sof.A$A0$A$A0$$Lambda$1155/266767500@12f02be4

// or if you want more specific type,
val func2: String => Boolean = string1.== _
// func2: String => Boolean = sof.A$A0$A$A0$$Lambda$1156/107885703@19459210

Fact 3: Scala compiler is smart. It supports eta-expansion which is conversion of a method to an appropriate function (to match the requirement)(if possible). So, if we tell the compiler that we want a Function of type String => Boolean and give it a method, it will smartly convert it to function.

// so our func3 did not need that explicit conversion using `_`

val func3: String => Boolean = string1.==
// func3: String => Boolean = sof.A$A1$A$A1$$Lambda$1161/1899231632@4843e7f0

Now, since your taskReturningString is a Future[String] thus taskReturningString.map wants an function of type String => A for any type A.

Also, taskReturningString.== takes an argument of type Any and return type of Boolean,

the compiler will expand it to a function String => Boolean,

val future1 = Future("abc")

val func4: String => Boolean = future1.==
// func4: String => Boolean = sof.A$A4$A$A4$$Lambda$1237/1577797787@2e682ccb

// And since `func4` will compare the argument string with `future1` for equality
// it will always return `false`

// So what you are doing is actually,
val falseResult: Future[Boolean] = future1.map(func4)
Related