2015-07-15 2 views
3

я определил следующий класс:Lambda умозаключение типа и неявное преобразование

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

    override def toString = text 
} 

В принципе, TransparentFunction1 просто обертка Function1, которая обеспечивает текстовое поле удобочитаемого, описывающее то, что функция.

Я хотел бы определить неявное преобразование, которое может преобразовать любой Function1 в TransparentFunction1, передавая код функции в текстовый параметр.

Я определил такое неявное преобразование с помощью макроса:

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))) 
     ) 
    ) 
    } 
} 

Это работает. Однако это вызывает проблему вывода типа.

Например, если я пытаюсь вызвать метод, названный «карта», которая принимает аргумент типа TransparentFunction1[Int, Int] так:

map(_ + 2) 

я получаю ошибку «отсутствует параметр типа для расширенной функции», а если Тип параметра карты был только Int => Int, вывод типа работает правильно.

Есть ли способ исправить макрос, чтобы тип вывода продолжал работать?

ответ

2

Чтобы это исправить, нужно только иметь TransparentFunction1 расширить Function1 (который, кажется естественным в любом случае, учитывая, что TransparentFunction1 концептуально очень много Function1, это только добавляет пользовательские toString но должен иначе действовать как обычный 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 
} 

Есть только несколько причин, по которым я могу видеть, для определения функционально-подобного класса, который делает не extend Function1. С моей точки зрения основной причиной является то, что ваш класс предназначен для использования в качестве неявных значений (например, типа), и вы не хотите, чтобы компилятор автоматически использовал эти неявные значения как неявные преобразования (которые он будет если он распространяет Function1). Кажется, это не так, потому что TransparentFunction1 простирается Function1 кажется правильным.

+0

(пахнет лбом) Не знаю, почему я об этом не думал. Предварительно это, кажется, устраняет проблему. Я должен экспериментировать с макросом, чтобы быть уверенным. – Nimrand

+0

Ow ... это ... очень удивительно. Нет никакой причины, по которой вывод типа должен иметь возможность выводить типы в этом случае, но не в пример OP. Это похоже на артефакт реализации ^^ Но хорошая новость для вас, а затем :) – sjrd

+0

Это действительно имеет смысл. Когда компилятор видит лямбду, он смотрит на тип параметра метода. Поскольку ожидаемый тип параметра является своего рода Function1 [T, R], он может знать из аргумента первого типа T, что параметр _ имеет тип Int. Если типы не были связаны друг с другом, у него нет способа вывести тип лямбды до тех пор, пока не будет применено неявное преобразование. Но он не может найти неявное преобразование, потому что он не знает, с какого типа его преобразование. Курица и яйцо. Алгоритм вывода, вероятно, можно было бы скорректировать для размещения, но это было бы сложно. – Nimrand

2

Я не думаю, что есть способ сделать это. Я попытался сделать то же самое для js.FunctionN в Scala.js (например, неявно преобразовать T1 => R в js.Function1[T1, R]), и я никогда не смог бы сделать операцию вывода типа для параметров лямбда. К сожалению, это кажется невозможным.

+0

Вы пытались использовать 'js.Function1' extend' scala.Function1'? Или есть в вашем случае хорошая причина не делать (в случае с OP я не думаю, что есть)? –

+0

Для Scala.js это не вариант. Если бы это было так, не было бы причин не использовать 'scala.FunctionN' в первую очередь ;-) Однако я не могу подробно рассказать о причинах Scala.js в комментарии SO. Это довольно глубоко. Тем не менее, я не думаю, что расширение в этом направлении помогло бы решить проблему вывода типа, поскольку выражение лямбда было бы супертипом ожидаемого типа и, следовательно, по-прежнему нуждалось бы в неявном преобразовании, через которое, по-видимому, неспособный искать типы параметров. – sjrd

+0

Жаль. Но для чего это стоит, расширение 'Function1' ** делает ** помощь. В случае OP это все, что необходимо для компиляции 'map (_ + 2)'. –

Смежные вопросы