16

Can I turn a method which takes an implicit parameter into a function?

trait Tx

def foo(bar: Any)(implicit tx: Tx) {}

foo _ // error: could not find implicit value for parameter tx: Tx

I am trying to achieve the following, preferably if I can somehow make it work with the plain call withSelection(deleteObjects):

trait Test {      
  def atomic[A](fun: Tx => A): A

  def selection: Iterable[Any]

  def withSelection(fun: Iterable[Any] => Tx => Unit) {
    val sel = selection
    if (sel.nonEmpty) atomic { implicit tx =>
      fun(sel)(tx)
    }
  }

  object deleteAction {
    def apply() {
      withSelection(deleteObjects)  // !
    }
  }

  def deleteObjects(xs: Iterable[Any])(implicit tx: Tx): Unit
}

I found this question, however it does not deal with the lifting from methods to functions as far as I can see.

Community
  • 1
  • 1
0__
  • 66,707
  • 21
  • 171
  • 266

2 Answers2

9

Implicits only work for methods. But you have to pass a function to withSelection. You can get around by wrapping the method in a function:

withSelection(a => b => deleteObjects(a)(b))

Its impossible to pass deleteObjects directly because foo _ does not work for a foo with an implicit parameter list defined.

Martin Ring
  • 5,404
  • 24
  • 47
4

To the best of my knowledge, implicit resolution must take place at usage site, and cannot be curried away. My own moment of disappointment was when I was trying to work around ´ExecutionContext´ proliferation in my code.

One compromise I've been considering was:

type Async[A] = ExecutionContext => Future[A]

def countFiles(root: String): Async[Int] = implicit ec =>
  // ...

The ´implicit´ only holds within the function -- we have to compromise on invocation:

implicit class AsyncExt[A](async: Async[A]) {
  def invoke()(implicit ec: ExecutionContext) = async(ec)
}

implicit val ec = ...
countFiles("/").invoke()

Another compromise -- the one I chose and lived to regret:

class AsyncFileCounter(ec: ExecutionContext) {
  def countFiles(root: String): Future[A] = ...
}

class FileCounter {
  def async(implicit ec: ExecutionContext) = new AsyncFileCounter(ec)
}

This changes the usage from the naive (but desired):

implicit val ec = ...
val counter = new FileCounter
counter.countFiles("/") // <-- nope

To the following:

implicit val ec = ...
val counter = new FileCounter
counter.async.countFiles("/") // yep!

Depending on your context, this could be bearable. You could add a ´def transactional´ where I used ´def async´.

I do regret this however, as it complicates inheritance, and incurs some allocation overhead (though that should be JITed away).

Bottom line is that you'll have to come up with a more explicit piecemeal method of invoking your function -- one that is less elegant than currying alone.

nadavwr
  • 1,820
  • 16
  • 20
  • 2
    About your second case (i.e. FileCounter), couldn't you define an implicit conversion `implicit def counterToAsync(c: FileCounter): AsyncFileCounter = c.async` so you can once again `counter.countFiles("/")` ? – pagoda_5b Jun 08 '13 at 10:53
  • Nice! In my case the implicit conversion will be defeated by synchronous methods with similar signatures that exist on the containing object. However, it's definitely a good boilerplate workaround! I really need to verify that escape analysis really does eliminate the heap allocation for the Async object, though. – nadavwr Jun 08 '13 at 20:29
  • I'd say that synchronous methods would have a different signature, since they won't return a `Future[A]` but simply an `A`, so the compiler can tell you that. You should simply delimit scopes where you need the async call and import the implicit conversion there. As for avoiding heap allocation I can't bet on that... – pagoda_5b Jun 08 '13 at 22:13
  • Alas, Scala does not support method overloading based solely on return values. Nonetheless, the implicit conversion you suggested can be useful in many cases. Regarding heap allocation -- once JIT determines that the 'Async' object never leaves the stack frame (which it shouldn't), then JIT will make sure it will allocate to stack only instead of heap. – nadavwr Jun 09 '13 at 01:26