Nimrand
Nimrand

Reputation: 1768

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?

Upvotes: 3

Views: 206

Answers (2)

Régis Jean-Gilles
Régis Jean-Gilles

Reputation: 32719

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.

Upvotes: 2

sjrd
sjrd

Reputation: 22085

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.

Upvotes: 2

Related Questions