Pergunta

If come across an interesting case with thunks versus functions in the presence of type Nothing:

object Test {
  def apply(thunk: => Any     ): String => Any = _ => thunk
  def apply(fun: String => Any): String => Any = fun
}

val res0 = Test { println("hello") }
res0("foo") // --> hello

val res1 = Test { x: String => println(s"hello $x") }
res1("foo") // --> hello foo

val res2 = Test { x: String => throw new RuntimeException("ex") }
util.Try(res2("foo")) // --> Failure

val res3 = Test { throw new RuntimeException("ex") } // boom!

Now the tricky bit is the last case. Is it possible to modify the apply method to make Scala choose the thunk version of it, instead of the Function1 version (which is more specific and thus preferred, and Nothing <: Function1[_,_], therefore...)

I tried to come up with low-high priority implicits and magnet pattern, but haven't found a solution yet.

Foi útil?

Solução

Here's a quick draft of a type-safe approach based on type classes:

object Test {
  trait Wrap[A, B] { def apply(a: => A): String => B }

  trait LowWrap {
    implicit def thunkWrap[A] = new Wrap[A, A] { def apply(a: => A) = _ => a }
  }

  trait MidWrap extends LowWrap {
    implicit def funcWrap[A] = new Wrap[String => A, A] {
      def apply(f: => String => A) = f
    }
  }

  object Wrap extends MidWrap {
    implicit object nothingWrap extends Wrap[Nothing, Nothing] {
      def apply(f: => Nothing) = _ => f
    }
  }

  def apply[A, B](a: => A)(implicit w: Wrap[A, B]) = w(a)
}

And then:

scala> Test { println("hello") }
res0: String => Unit = <function1>

scala> res0("foo")
hello

scala> Test { x: String => println(s"hello $x") }
res2: String => Unit = <function1>

scala> res2("foo")
hello foo

scala> Test { x: String => throw new RuntimeException("ex") }
res4: String => Nothing = <function1>

scala> util.Try(res4("foo"))
res5: scala.util.Try[Nothing] = Failure(java.lang.RuntimeException: ex)

scala> Test { throw new RuntimeException("ex") }
res6: String => Nothing = <function1>

scala> util.Try(res6("foo"))
res7: scala.util.Try[Nothing] = Failure(java.lang.RuntimeException: ex)

You might be able to simplify a bit, add variance, etc.

Outras dicas

Use:

val res3 = Test { (throw new RuntimeException("ex")): Any }

This works as expected. (No exception on creation, exception when calling res3).

A solution which uses reflection. (Still curious: can one write a static solution?)

import reflect.runtime.universe._

object Test {
  def apply[A: TypeTag](thunk: => A): String => Any =
    if (typeOf[A] =:= typeOf[Nothing]) // hah, caught you
      _ => thunk
    else if (typeOf[A] <:< typeOf[String => Any])
      thunk.asInstanceOf[String => Any]
    else
      _ => thunk
}

val res0 = Test { println("hello") }
res0("foo") // --> hello

val res1 = Test { x: String => println(s"hello $x") }
res1("foo") // --> hello foo

val res2 = Test { x: String => throw new RuntimeException("ex") }
util.Try(res2("foo")) // --> Failure

val res3 = Test { throw new RuntimeException("ex") }
util.Try(res3("foo")) // --> Failure

(a macro based version of this would be static, I guess)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top