Rules for `? ? ?`, why is < not a member of Nothing

I do not think it is that difficult to make the compiler agree that Nothing has any method.

However, that won’t be very useful because then it doesn’t know what it would accept and what would it return. So either it just keep assuming Any and Nothing everywhere until the whole code is basically just an untyped script, or it would simply crash at some point.

For example why would ??? < ??? return a Boolean at all? Even more, what does < accepts?

Again, you find it obvious that it should infer Ints and Boolean - But it could also be a method in a class Foo that accepts a Bar and returns a Baz which has an implicit conversion to Boolean

Finally, if the compiler would allow
What you want and just turn off the typer in that section, why even compile it? Syntax is not that important anyways.

Why not just providing an empty implementation and a text document with the base script for each exercise? Or just leave all that commented?

2 Likes

There is no object of type Nothing - neither in your example nor anywhere else. There is none by definition.

The compiler would at least need to resolve the signature of the “>” method in order to validate that the type of the full expression is boolean and thus applicable for use with the “if”. How would it be supposed to do this for your example?

I don’t think I’m assuming ??? < ??? is integer comparison. The way I look at it is that it is a method call on an object of type Nothing passing a single argument of type Nothing. It seems to me it should compile in the same sense that defn x():Int = {???} compiles. It compiles to code that if executed with through an exception. It does not fail to compile simply because ??? isn’t exactly of type Int, rather its type is a subtype of Int.

Question: does the operand of if need to be exactly Boolean or could it be any subtype of Boolean? If so, then Nothing fits that description.

Again I emphasize, that even if a solution exists, it would not be worth implementing from an effort vs payoff perspective.

Any subtype of Boolean will work, that is just Liskov, is not a property of if is a rule of the language.

The problem again is not that the compiler could be able to say yeah Nothing could have a < method, but what does it accept? Well, it could see what is passed and use that so in this case, it sees another Nothing ok it could use that (other option would be to just assume Any). Then what should it return? Well, it could try to see if the scope provides enough info so in this case Boolean, but that is very difficult and something impossible so it could just assume Nothing which should always work.

However, even if that seems to work great in your if example, once you have a more complex example it starts to become more and more useless, because as I said it will slowly become an untyped script; or more and more complex to keep track of all the types. - Note that the Scala 3 compiler has a more improved type inference so it may be worth seeing if can handle it better.

But again, just out of curiosity, what would be the advantage of all that over just:

def recur(left:Double,right:Double,depth:Int):Option[Double] = {
  /**
  val mid = ???

  if ( ??? < ???)
    Some(???)
  else if (depth >= maxDepth)
    None
  else if ( f(mid))
    recur(???,???,???)
  else
    recur(???,???,???)
  */

  ???
}
1 Like

I think I understand the Liskov principle as it applies to function calls and method calls etc. But if is a lower level language construct, not a function. I’m not convinced that the Liskov principle applies to it necessarily. That was sort of the sense of my question. It is completely imaginable that the compile requires an object exactly of type Boolean as the operand of if.

yes, that’s more or less what I ended up doing. The danger therein is if I give that to my students, and within the comment I have a syntax error ( mismatch parens for example) nothing catches it.

Yes, that is a good point. The compiler tries to convert the name of a method into an actual method call, distinguishing different methods of the same name according to type of lhs and type an arity of arguments. There’s admittedly too little information for it to do that.

This problem could be handled, but with questions utility, for example with a call to thow new unknown-method-exception. It eventually becomes support for untyped code in worst case.

I think that theoretically it would be possible that when the compiler sees an expression a with type Nothing it accepts any expression a.foo(x1: Any, x2: Any, ..., xn: Any), with foo equal to any parsable name, for any n, with return type Nothing. And it could simply compile the whole expression to e.g. throw a. In fact he already does exactly that when he compiles ??? == ??? or ???.## (the runtime representation of Nothing extends Throwable).

2 Likes

does the rhs of ??? == rhs get evaluated before the throw occurs?

Since we’re talking about the fantastical theoretically possible, it should compile to throw a but assuring that the arguments are evaluated before the throw occurs.

It seems that only the lhs gets evaluated, and in fact I think that suffices. All that follows is dead code by definition. It is as specified that the lhs of an expression gets evaluated first, and the only way that the lhs can have type Nothing is if evaluating it throws an exception, or never terminates.

2 Likes

Would/should there be any compartmentalization of this hypothetical process? E.g.

(??? < ???).ifThenElse(3)(4)

would be of type Nothing. What about the original example?

if(??? < ???) 3 else 4

One could argue that the constraints of the builtin “if” breaks the proliferation and the type should be Int. OTOH, one could go with “garbage in - garbage out” and assign Nothing here, as well…

This is actually not a theoretical question:

scala> class Foo { def foo = if(???) 3 else 4 }
class Foo

scala> :javap -c Foo#foo
  public int foo();
    Code:
       0: getstatic     #21                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: invokevirtual #25                 // Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
       6: athrow

The answer with the current implementation seems to be that the expression has type Int but the bytecode for the if isn’t generated anymore.