Skip to content

Instantly share code, notes, and snippets.

@jmhodges
Last active August 29, 2015 13:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmhodges/9282988 to your computer and use it in GitHub Desktop.
Save jmhodges/9282988 to your computer and use it in GitHub Desktop.
The catch statement doesn't print the text. Seen with scala 2.8.1, 2.9.2 and now 2.10.x.
object Welp {
def foobar(a: Int, b: Int, c:Int) { throw new IllegalArgumentException }
def main(args: Array[String]) {
List(3) map {
try {
foobar(1, 2, _)
} catch {
case e: Throwable => { // This set of braces is optional. Problem remains when removed.
println("won't be printed")
throw e
}}
}
}
}
#!/bin/bash
scalac Welp.scala && scala -classpath . Welp
@cameronhunter
Copy link

Do you know the reason why this is the case?

@codahale
Copy link

codahale commented Mar 1, 2014

Confirmed w/ 2.10.3.

@codahale
Copy link

codahale commented Mar 1, 2014

The bytecode from 2.10.3:

public final class Welp {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #16                 // Field Welp$.MODULE$:LWelp$;
       3: aload_0       
       4: invokevirtual #18                 // Method Welp$.main:([Ljava/lang/String;)V
       7: return        

  public static void foobar(int, int, int);
    Code:
       0: getstatic     #16                 // Field Welp$.MODULE$:LWelp$;
       3: iload_0       
       4: iload_1       
       5: iload_2       
       6: invokevirtual #22                 // Method Welp$.foobar:(III)V
       9: return        
}

public final class Welp$ {
  public static final Welp$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class Welp$
       3: invokespecial #12                 // Method "<init>":()V
       6: return        

  public void foobar(int, int, int);
    Code:
       0: new           #16                 // class java/lang/IllegalArgumentException
       3: dup           
       4: invokespecial #17                 // Method java/lang/IllegalArgumentException."<init>":()V
       7: athrow        

  public void main(java.lang.String[]);
    Code:
       0: getstatic     #29                 // Field scala/collection/immutable/List$.MODULE$:Lscala/collection/immutable/List$;
       3: getstatic     #34                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       6: iconst_1      
       7: newarray       int
       9: dup           
      10: iconst_0      
      11: iconst_3      
      12: iastore       
      13: invokevirtual #38                 // Method scala/Predef$.wrapIntArray:([I)Lscala/collection/mutable/WrappedArray;
      16: invokevirtual #42                 // Method scala/collection/immutable/List$.apply:(Lscala/collection/Seq;)Lscala/collection/immutable/List;
      19: aload_0       
      20: invokespecial #46                 // Method liftedTree1$1:()Lscala/Function1;
      23: getstatic     #29                 // Field scala/collection/immutable/List$.MODULE$:Lscala/collection/immutable/List$;
      26: invokevirtual #50                 // Method scala/collection/immutable/List$.canBuildFrom:()Lscala/collection/generic/CanBuildFrom;
      29: invokevirtual #56                 // Method scala/collection/immutable/List.map:(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;
      32: pop           
      33: return        
}

public final class Welp$$anonfun$liftedTree1$1$1 extends scala.runtime.AbstractFunction1$mcVI$sp implements scala.Serializable {
  public static final long serialVersionUID;

  public final void apply(int);
    Code:
       0: aload_0       
       1: iload_1       
       2: invokevirtual #21                 // Method apply$mcVI$sp:(I)V
       5: return        

  public void apply$mcVI$sp(int);
    Code:
       0: getstatic     #29                 // Field Welp$.MODULE$:LWelp$;
       3: iconst_1      
       4: iconst_2      
       5: iload_1       
       6: invokevirtual #33                 // Method Welp$.foobar:(III)V
       9: return        

  public final java.lang.Object apply(java.lang.Object);
    Code:
       0: aload_0       
       1: aload_1       
       2: invokestatic  #40                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
       5: invokevirtual #42                 // Method apply:(I)V
       8: getstatic     #48                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
      11: areturn       

  public Welp$$anonfun$liftedTree1$1$1();
    Code:
       0: aload_0       
       1: invokespecial #54                 // Method scala/runtime/AbstractFunction1$mcVI$sp."<init>":()V
       4: return        
}

@ryanoneill
Copy link

Same behavior in 2.10.2 fwiw.

@jmhodges
Copy link
Author

jmhodges commented Mar 1, 2014

Edited to make the example more clear by commenting out the throw e.

The deal is that _ wraps the "nearest" expression in an anonymous function and then scala calls that anonymous function "later".

So, the try/catch is actually wrapped around the equivalent of a new Function { def apply(...) { ... } } which, of course doesn't throw an IllegalArgumentException! That's delayed 'til later.

@acruise
Copy link

acruise commented Mar 1, 2014

IMO this shouldn't compile, making it a particularly serious kind of compiler bug. If you change the inside of the try block to this, which I think should be equivalent:

    val x = foobar(1, 2, _)
    x

you get the expected:

Error:(11, 30) missing parameter type for expanded function ((x$1) => foobar(1, 2, x$1))
        val x = foobar(1, 2, _)
                             ^

@retronym
Copy link

retronym commented Mar 3, 2014

@acruise: The fact that expr: T compiles doesn't imply that val temp = expr; temp: T compiles. Scala's type inference is driven by the expected type.

@adriaanm
Copy link

adriaanm commented Mar 3, 2014

Weird, I can't reproduce:

RUNNER=scalac scala-hash v2.10.3 /private/tmp/Welp.scala 
/private/tmp/Welp.scala:10: error: type mismatch;
 found   : Unit
 required: Int => ?
       println("won't be printed")                                                   
              ^
one error found

@som-snytt
Copy link

Typically:

apm@mara:~/tmp$ scalam -Xlint
Welcome to Scala version 2.11.0-M8 (OpenJDK 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> try ("hi") catch { case _: Throwable => () }
res0: Any = hi

scala> util.Try ("hi") recover { case _: Throwable => () }
<console>:8: warning: a type was inferred to be `Any`; this may indicate a programming error.
              util.Try ("hi") recover { case _: Throwable => () }
                                      ^
res1: scala.util.Try[Any] = Success(hi)

but actually in the map you get missing parameter type. It forces the issue.

scala> (1 to 10) map (util.Try {i: Int => i.toString} recover { case _: Throwable => -1 })
<console>:8: warning: a type was inferred to be `Any`; this may indicate a programming error.
              (1 to 10) map (util.Try {i: Int => i.toString} recover { case _: Throwable => -1 })
                                                             ^
<console>:8: error: type mismatch;
 found   : scala.util.Try[Any]
 required: Int => ?
              (1 to 10) map (util.Try {i: Int => i.toString} recover { case _: Throwable => -1 })
                                                             ^

@VladUreche
Copy link

@adriaanm: I couldn't reproduce it either, but here's a version that reproduces the problem:

object Welp {
  def foobar(a: Int, b: Int, c:Int) { throw new IllegalArgumentException }

  def main(args: Array[String]) {
    List(3) map {     
      try {
        foobar(1, 2, _)
      } catch {
      case e: Throwable =>
       println("won't be printed") // it's not printed
       throw e
      }
    }
  }
}

and here's what should happen:

object Welp {
  def foobar(a: Int, b: Int, c:Int) { throw new IllegalArgumentException }

  def main(args: Array[String]) { x =>
    List(3) map {     
      try {
        foobar(1, 2, x)
      } catch {
      case e: Throwable =>
       println("will be printed") // yes, it's printed :)
       throw e
      }
    }
  }
}

@retronym
Copy link

retronym commented Mar 3, 2014

and here's what should happen:

Don't you mean, "here's what the programmer intended to happen"?

Here is an similar example using a side-effecting identity rather than try/catch:

scala> def sideEffect[T](t: T) = { println("!"); t }
sideEffect: [T](t: T)T

scala> def foo(a: Int) = a
foo: (a: Int)Int

scala> val f = sideEffect(foo(_))
!
f: Int => Int = <function1>

scala> f(0)
res0: Int = 0

The placeholder syntax for anonymous functions is specified to create the function with the smallest possible scope.

@VladUreche
Copy link

Don't you mean, "here's what the programmer intended to happen"?

Right, sorry, that's what I meant. Seems this behavior conforms to the spec, even if it's not intuitive:

An expression e of syntactic category Expr binds an underscore section u, if the following two conditions hold: (1) e properly contains u, and (2) there is no other expression of syntactic category Expr which is properly contained in e and which itself properly contains u.

Since:

Expr  ::= (Bindings | [‘implicit’] id | ‘_’) ‘=>’ Expr
       | Expr1
Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] else Expr]
       | ‘while’ ‘(’ Expr ‘)’ {nl} Expr
       | ‘try’ ‘{’ Block ‘}’ [‘catch’ ‘{’ CaseClauses ‘}’]
                              [‘finally’ Expr]
       ...
       | SimpleExpr1 ArgumentExprs ‘=’ Expr
       ...
SimpleExpr1 := ...
       | `_'
       ...

Now I just wonder what properly contains means in plain English.

@som-snytt
Copy link

@VladUreche I think it just means "below it in the parse tree", which may be plain spokenness for something like "on the right-hand side of a production." After I read that in the spec, I was no longer misled by stuff about parens and contexts in SO answers to this issue. (Probably I found other things to be misled by.)

@retronym "side-effecting identity" is one of those phrases that make me glad to have found FP, but to me, the try-catch version is simpler and more devious somehow. If it's OK, I'll submit that version to the puzzlers. For all I know, they are already puzzling over it. (In the real world, identity is always side-effecting, if not self-effacing.)

@som-snytt
Copy link

I hope this does justice to the suffering of all involved.

/** Top-level component for welp.com domain.
 *  Therefore, must never throw an exception.
 */
object Welp {
  /** Start serving welp pages. */
  def f(a: Int, b: Int, c: String) { throw new IllegalArgumentException }

  def fail(e: Throwable) = throw new RuntimeException(s"""${"*" * 30}
    |Dan, I hope you see this in the log file,
    |because the server just crashed.
    |
    |Dan, wake up!
    |${e.getMessage}
    |${"*" * 30}
    |""".stripMargin)

  def main(args: Array[String]): Unit = args map {
    try f(1, 2, _)
    catch {
      case e: Throwable => Console.err println "Failing..." ; fail(e)
    }
  }
}

with the joke answer (because it's multiple choice)

Prints:
Failing...
and wakes Dan at 3 a.m. when someone notices the server is down.

Feel free to comment on PR; though the puzzler guys are good at polishing.

The title is "Catch me if you can." Of course.

BTW, the puzzler guys are going to cut down some trees and print puzzlers on them. If you want your name associated with said puzzles, or if you wish to deny all association, you should tell them. The puzzler links to the ML discussion, as a rule.

@jmhodges
Copy link
Author

jmhodges commented Mar 5, 2014

@adriaanm Oh, yeah, my commenting out the throw actually made it not compile. I've put it back.

@Blaisorblade
Copy link

Now I just wonder what properly contains means in plain English.

That isn't plain English, but "math English". I think "properly" there is analogous to proper subset, even though we're not talking about sets: an expression e1 is properly contained into e2 if e1 is contained in e2 and e1 is different from e2. But I guess clarifying this in the spec could be a good idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment