2

I'm trying to create some simple custom String interpolator, and I'm successful as long as I don't try to use a type parameter.

import scala.concurrent.Future

object StringImplicits {
  implicit class FailureStringContext (val sc : StringContext) extends AnyVal {

    // This WORKS, but it's specific to Future :(  

    def fail[T](args : Any*): Future[T] = {
      val orig = sc.s (args : _*)
      Future.exception[T](new Exception(orig))
    }

    // I want this to work for Option,Try,Future!!

    def fail[M,T](args:Any*): M[T] = {
      val orig = sc.s (args : _*)

      // Obviously does not work.. 
      M match {
        case Future => Future.exception(new Exception(orig))
        case Option => None
        case Try => Failure(new Exception(orig))
        case  _ => ???
      }
    }
  }
}

Can I get this to work? I can't use parametric polymorphism because I'm not the one defining those three types.

What's the equivalent in the type level for that pseudo-code pattern match?

LATEST ATTEMPT

My latest attempt was to use implicitly, but I don't have such implicit! I'd be actually interested to grab a hold of the type that the compiler wants me to return according to type inference.

def fail[T, M[T]](args:Any*): M[T] = {
  val orig = sc.s(args: _*)

  implicitly[M[T]] match {
    case _:Future[T] => Future.exception(new Exception(orig))
    case _ => ???
  }
}


<console>:18: error: could not find implicit value for parameter e: M[T]
             implicitly[M[T]] match {
                       ^
<console>:19: error: value exception is not a member of object scala.concurrent.Future
               case _: Future[T] => Future.exception(new Exception(orig))
                                           ^
8
  • See: def test[A,B](a:Any):A[B] = ??? => compilation error: A does not take type parameters. A is generic and can be Int for example. Int[String] does not exist. So compiler will always complain. Commented Jul 7, 2016 at 11:08
  • so maybe I need to tell the compiler that I want A to be a higher kind? How? Commented Jul 7, 2016 at 11:12
  • 1
    def fail[T, M[T]](...) gets you part way. But I don't see how you will specify the M at the call site. Are you expecting the compiler to infer what to use for M based on the return type required? Commented Jul 7, 2016 at 11:26
  • @sscarduzio: Sorry to derail your question, but it soooo much seems like a bad idea. Is there a compelling reason why you'd feel in need to save a few key strokes (and trade Future.failed with a much less obvious fail"...", making it polymorphic on top of that... and having the string silently ignored in case of an Option)? Or is it just for kicks? Anyway, if you feel like you need it, what is wrong with good old overloading? Defining a distinct overload for each of the types you want to support seems like the sanest solution. Commented Jul 7, 2016 at 11:29
  • 1
    Fair enough, that's a totally reasonable way to learn things (I tends abuse features too, just for fun and to explore). Just wanted to make sure you were not genuinely thinking it was a good idea for production code. Commented Jul 7, 2016 at 11:35

1 Answer 1

2

In my opinion the simplest is to rely on good old overloading: just define a different overload for each type that you want to handle.

Now of course, there is the problem of having different overloads with the same signature, and as usual in scala, you can use tricks to work around them. Here we'll add dummy implicit parameters to force each overload to have a distinct signature. Not pretty but it works and will suffice in this case.

import scala.concurrent.Future
import scala.util.{Try, Failure}

implicit class FailureStringContext (val sc : StringContext) extends AnyVal {

  def fail[T](args : Any*): Future[T] = {
    Future.failed[T](new Exception(sc.s (args : _*)))
  }

  def fail[T](args : Any*)(implicit dummy: DummyImplicit): Option[T] = {
    Option.empty[T]
  }

  def fail[T](args : Any*)(implicit dummy: DummyImplicit, dummy2: DummyImplicit): Try[T] = {
    Failure[T](new Exception(sc.s (args : _*)))
  }
}

And tada:

scala> fail"oops": Option[String]
res6: Option[String] = None

scala> fail"oops": Future[String]
res7: scala.concurrent.Future[String] = scala.concurrent.impl.Promise$KeptPromise@6fc1a8f6

scala> fail"oops": Try[String]
res8: scala.util.Try[String] = Failure(java.lang.Exception: oops)
Sign up to request clarification or add additional context in comments.

8 Comments

Neat but the need to specify the type when you call it rather removes the point of overloading in the first place, it seems to me. Just call the fail functions different names - failOption etc.
There is no way around it, you have to give the expected type one way or another. But like for any type ascription, it is only necessary if you're not already in a context where the expected type is known. If you're using fail as the return value of a method with an explicit return type) by example, or passing as an argument to a non-generic method, no need to specify the type any more.
And admitedly, it's cleaner to just specify the type you want than to perform manual name-mangling...
You're using dummy to allow overloading, right? That's a hacky trick right there :)
TIL "DummyImplicit"
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.