5

I'm trying to do something that I'm not sure if Scala's type system will allow me to do.

I basically want to create a closure from a generic definition and return that closure, while executing a function internally of the same type.

For example:

val f = async[(str:String, i:Int, b:BigInt) => Unit]({ (String, Int, BigInt) =>
  // Code here...
})

// 'f' would have a type of (String, Int, BigInt) => Unit and would wrap the passed anonymous function

Theoretical example of a definition:

  def async[T](
    shell: Shell,
    success: T,
    failure: (Throwable) => Unit): T = {
        new T {
          val display = shell.getDisplay()
          display.asyncExec(new Runnable() {
            def run(): Unit = {
              try {
                success(_)
              } catch {
                case e:Throwable =>
                  failure(e)
              }
            }
          })
        }
  }

This would then allow me to have a simple system of creating asynchronous callbacks for SWT, while keeping SWT out of my business logic.

Hakkar
  • 2,391
  • 4
  • 23
  • 33
  • possible duplicate of [Scala generics - why I can't create parametrised object inside generic class?](http://stackoverflow.com/questions/5336648/scala-generics-why-i-cant-create-parametrised-object-inside-generic-class) – 0__ Jul 15 '12 at 23:49
  • I don't think it's a 100% duplicate, because even if I create a class of type T with an apply method, I would still need to take in the proper parameters provided by T. However, type erasure may make this particular problem impossible to solve. – Hakkar Jul 16 '12 at 02:24

2 Answers2

9

You can do this more generically with the Shapeless library. We define wrap as follows:

import shapeless._, Functions._

def wrap[F, A <: HList, R](f: F)(implicit
  h: FnHListerAux[F, A => R],
  u: FnUnHListerAux[A => R, F]
): F = { (args: A) => 
  println("Before f")
  val result = f.hlisted(args)
  println("After f")
  result
}.unhlisted

And then can use it like this:

scala> val sum: (Int, Int) => Int = _ + _
sum: (Int, Int) => Int = <function2>

scala> val wrappedSum = wrap(sum)
wrappedSum: (Int, Int) => Int = <function2>

scala> wrappedSum(100, 1)
Before f
After f
res0: Int = 101

This works with a function of any arity.

So it is possible in Scala, although doing something equivalent without Shapeless would almost certainly be an enormous headache.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • This looks very intriguing! And it doesn't entail any (significant) runtime overhead, e.g., by using reflection? – Malte Schwerhoff Jul 16 '12 at 06:54
  • Nope, no reflection. The type classes will have some runtime overhead, but it shouldn't be excessive. – Travis Brown Jul 16 '12 at 07:00
  • Thank you. That's exactly what I wanted. This could become a really useful pattern for converting anonymous classes to anonymous functions. – Hakkar Jul 16 '12 at 21:15
2

How about something along these lines:

scala> def wrap[T1, T2, T3, R](f: (T1, T2, T3) => R) = {
 |   (v1: T1, v2: T2, v3: T3) =>
 |     println("Before f")
 |     val r = f(v1, v2, v3)
 |     println("After f")
 |     r
 | }
wrap: [T1, T2, T3, R](f: (T1, T2, T3) => R)(T1, T2, T3) => R

scala> def foo(x: String, y: Int, z: BigInt) = (x, y, z)
foo: (x: String, y: Int, z: BigInt)(String, Int, BigInt)

scala> val wrapped = wrap(foo _)
wrapped: (String, Int, BigInt) => (String, Int, BigInt) = <function3>

scala> wrapped("foo", 42, 12345)
Before f
After f
res0: (String, Int, BigInt) = (foo,42,12345)

If the function you want to wrap could have different numbers of arguments then you will, unfortunately, have to define your wrap function once for each different arity :-(

Paul Butcher
  • 10,722
  • 3
  • 40
  • 44
  • Thanks for the answer. The different number of arguments portion is unfortunate :(. Though, you could force the listener to take a single tuple and pass in the type that way to get around the limitation. It would be awesome to be able to forward the generic type. I'm not sure if Scala has that functionality though :/. – Hakkar Jul 16 '12 at 02:19