Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda type inference and implicit conversions

I've defined a the following class:

class TransparentFunction1[-T1, +R1](val func : T1 => R1, val text : String) {
  @inline
  def apply(t : T1) = func(t)

  override def toString = text
}

Basically, TransparentFunction1 is just a wrapper around a Function1 that provides a human-readable text field describing what the function is.

I'd like to define an implicit conversion that can convert any Function1 into a TransparentFunction1, passing the function's code to the text parameter.

I've defined such an implicit conversion using a macro:

  implicit def transparentFunction1[T1, R1](expression : T1 => R1) : TransparentFunction1[T1, R1] = macro Macros.transparentImpl[T1, R1, TransparentFunction1[T1, R1]]

object Macros {
  def transparentImpl[T : context.WeakTypeTag, U : context.WeakTypeTag, V : context.WeakTypeTag](context : scala.reflect.macros.whitebox.Context) (expression : context.Expr[T => U]) : context.Expr[V] = {
      import context.universe._
      context.Expr[V](
        Apply(
          Select(
            New(
              TypeTree(
                appliedType(weakTypeOf[V].typeConstructor, weakTypeOf[T] :: weakTypeOf[U] :: Nil)
              )
            ),
            termNames.CONSTRUCTOR
          ),
          List(expression.tree, Literal(Constant(expression.tree.toString)))
        )
      )
    }
}

This works. However, it causes a problem for type inference.

For example, if I try to call a method named "map" that takes an argument of type TransparentFunction1[Int, Int] like this:

map(_ + 2)

I get an error "missing parameter type for expanded function", whereas if map's parameter type was just Int => Int, the type inference works correctly.

Is there a way to fix the macro so that type inference will continue to work?

like image 557
Nimrand Avatar asked Dec 30 '25 10:12

Nimrand


2 Answers

To fix this you only need to have TransparentFunction1 extend Function1 (which seems natural anyway, given that TransparentFunction1 is conceptually very much a Function1, it just adds a custom toString but should otherwise act as a plain funciton):

class TransparentFunction1[-T1, +R1](val func : T1 => R1, val text : String) extends (T1 => R1){
  @inline
  def apply(t : T1) = func(t)

  override def toString = text
}

There are only a handfull of reasons I can see for defining a function-like class that does not extend Function1. Off the top of my head the main reason is when your class is designed to be used as implicit values (eg a type class), and you don't want to have the compiler automatically use those implicit values as implicit conversions (which it will if it extends Function1). It does not seem to be the case here so having TransparentFunction1 extend Function1 seems the right thing to do.

like image 152
Régis Jean-Gilles Avatar answered Jan 02 '26 07:01

Régis Jean-Gilles


I don't think there's a way to do that. I've tried, some time ago, to do the same thing for js.FunctionN in Scala.js (e.g., implicitly convert a T1 => R into a js.Function1[T1, R]), and I could never make the type inference work for the parameters of the lambda. It seems to be impossible, unfortunately.

like image 33
sjrd Avatar answered Jan 02 '26 08:01

sjrd



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!