This is probably going to be overkill, but you could do something like this using shapeless.
We can represent a couple of side effecting callback functions as an HList :
import shapeless._
type Callback[A] = (A => Unit) => Unit
def f1(f: Int => Unit): Unit = f{ println("f1"); 5 }
val f2: Callback[String] = f => f{ println("f2"); "foo" }
val callbacks = f1 _ :: f2 :: HNil
// Callback[Int] :: Callback[String] :: HNil
Now we want to turn this last HList into a function which takes an (Int, String) => Unit as argument.
First we will introduce a type class OneCallback which can turn an HList like callbacks into one callback taking an HList as an argument (consisting all the different arguments of the multiple functions). For callbacks we will get a Callback[Int :: String :: HNil].
trait OneCallback[F, Args] {
def apply(funs: F): Callback[Args]
}
object OneCallback {
implicit val hnilOneCallback: OneCallback[HNil, HNil] =
new OneCallback[HNil, HNil] {
def apply(funs: HNil): Callback[HNil] = callback => callback(HNil)
}
implicit def hconsCallback[A, FT <: HList, AT <: HList](implicit
oneCallbackTail: OneCallback[FT, AT]
) = new OneCallback[Callback[A] :: FT, A :: AT] {
def apply(funs: Callback[A] :: FT): Callback[A :: AT] =
(f: (A :: AT) => Unit) =>
funs.head { argH =>
oneCallbackTail(funs.tail) { argsT =>
f(argH :: argsT)
}
}
}
}
Lastely we will create a function which can turn this Callback[Int :: String :: HNil] into a ((Int, String) => Unit) => Unit :
import shapeless.ops.function.FnToProduct
def oneCallback[CS <: HList, A <: HList, C](callbacks: CS)(implicit
ocb: OneCallback[CS, A],
ftp: FnToProduct.Aux[C, A => Unit]
): C => Unit = c => ocb(callbacks).apply(ftp(c))
Now we can combine multiple callback functions as :
val cb = oneCallback(f1 _ :: f2 :: HNil)
cb { (i, s) => println(s * i) }
// f1
// f2
// foofoofoofoofoo