Function used as Boolean

Can someone please help me understand why the compiler issues no error in this code?
The line if (M.forall(t.typep)) is accidentally omitting the iteration variable which should be passed to the typep method.
I’ve tried this code in my local IntelliJ and also in Scastie, and neither complains.
I think something must be happening different that I think is happening.

sealed abstract class Type {

  def typep(a: Any): Boolean

}

case class MemberType(M: Any*) extends Type {
  override def typep(a: Any): Boolean = M.contains(a)

  def subtypep(t: Type): Option[Boolean] = {
    if (M.forall(t.typep))  /// why is this not an error?
      Some(true)
    else
      Some(false)
  }
}

Perhaps I understand after staring a while. t.typep is indeed a function of type Any=>Boolean, which is what is expected as argument of M.forall

Yes, but actually no.

t.type is a method call, that in most languages would have failed as you said.

However, Scala provides something called eta-expansion which automatically creates a function wrapping a method when the latter is passed and the former is needed.

Nevertheless, the idea of the existence of eta-expansion is that you as an user shouldn’t need to know (or at least not care) about the differences between methods and functions. But sadly, in Scala 2 functions have a lot of limitations which makes knowing the distinction important; thankfully most of them are removed on Dotty (aka Scala 3).

1 Like

Unrelated to the question, this could equivalently be expressed as

Some(M.forall(t.typep))

…and the Option return type is somewhat questionable, given that it always returns a Some, anyway.

1 Like

The code here is a small excerpt. The subtypep method returns None in some cases, just not for the MemberType class.

Question about the supposed optimization. As stated above subtypep returns either Some(true), Some(false), or None, which indicate that the two types are 1) in a subtype relation, 2) not in a subtype relation, or 3) it could not be determined whether they are in a subtype relation.

Is the code any more efficient if I change the if/then/else to simply Some(M.forall(t.typep)) ? It seems to me that the compiler ought to generate the same code, especially for such trivial optimizations. Does the programmer really need to worry about making these micro optimizations?

Yes, allowing the programmer to ignore the distinction between method and function removes an ugly leaky abstraction, which as I recall was really confusing when I started using Scala a few of years ago. Bravo Scala 3!

Hard to be sure – this is the sort of situation where trying both and looking at the output with javap can be helpful, if you really care. Also, even if the compiler doesn’t optimize it, there’s a fair chance that the JVM itself might do so.

That said, micro-optimizations tend to mostly be a waste of effort, unless it’s a known hot path.

I would say that in this case is not about performance, but rather readability.

One of first things I learned on programming 101 was

// This is redundant. 
if (foo) {
  return true;
} else {
  return false;
} 

// Just write this. 
return foo;

With regard to performance. I’m not 100% happy with the following code.
Imagine that the subtypep method is expensive to call. This implementation iterates twice through U. First to see if the recursive call always returns Some(true). If not, iterate again to see if the recursive call ever returns Some(false). And if both checks fail, then return None.

Can I do this in one iteration? If I use reduce, I’m forced to finish the iteration, as there’s not clean way to break out of the loop. Also if I use map to compute the sequence of Options, var saveOptions = U.map(_.subtypep(t)) then I’m forced to always iterate entirely through the loop.

case class UnionType(U: Type*) extends Type {
  /// ...
  override def subtypep(t: Type): Option[Boolean] = {
    // TODO : Only a single pass ?
    if (U.forall(_.subtypep(t).contains(true)))
      Some(true)
    else if (U.exists(_.subtypep(t).contains(false))) 
      Some(false)
    else 
      None
  }
  // ...
}

Potentially, this deserves more discussion. I’ll ask it as its own question.

I agree in this case, if you look only at this method. But most all the methods are a longer sequence like the following. It’s not clear to me whether the overall code readability improves but optimizing one method so that it looks different than the others.

if (something)
   Some(x1)
else if (something-else)
   Some(x2)
else if (something-else-else)
   Some(x3)
else
   super.m(...)

You can do something like

List(foo, bar, baz).exist(cond => cond()).getOrElse(false)

Sorry, I don’t understand which comment that was in response to.

About having multiple if-else you can have a List of Function0[Boolean] and use combinators like exists, find or collectFirst to get the result you want.