$ scala
Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_31).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import scala.reflect.runtime.{universe => u}
import scala.reflect.runtime.{universe=>u}
scala> class MyContext(val n: Int)
defined class MyContext
Пример AST для объекта с методом с implicit параметрами
scala> val implicitMethod = u reify { object Test1 {def method1(s: String)(implicit c: MyContext): String = s"${c.n}...";} }
implicitMethod: reflect.runtime.universe.Expr[Unit] =
Expr[Unit]({
object Test1 extends AnyRef {
def <init>() = {
super.<init>();
()
};
def method1(s: Predef.String)(implicit c: $read.MyContext): Predef.String = StringContext.apply("", "...").s(c.n)
};
()
})
scala> object Test1 {def method1(s: String)(implicit c: MyContext): String = s"${c.n}...";}
defined object Test1
пример вызова метода объекта без явной передачи implicit параметров
вызов выглядит в AST как карирование 😕 (см. строку val res1: ...
)
scala> implicit val defContext = new MyContext(0)
defContext: MyContext = MyContext@5f780a86
scala> val implicitMethodCall = u reify { val res1: String = Test1.method1("test1"); }
implicitMethodCall: reflect.runtime.universe.Expr[Unit] =
Expr[Unit]({
val res1: Predef.String = $read.Test1.method1("test1")($read.defContext);
()
})
Посмотрим как выглядит явная передача implicit параметров
scala> val context2 = new MyContext(2)
context2: MyContext = MyContext@4fd74223
scala> val explicitMethodCall = u reify { val res1: String = Test1.method1("test1")(context2); }
explicitMethodCall: reflect.runtime.universe.Expr[Unit] =
Expr[Unit]({
val res1: Predef.String = $read.Test1.method1("test1")($read.context2);
()
})
Всё также.
Всё это был бы праздный вопрос, если бы не желание писать декораторы на методы.
Вот пример:
class SimpleDecorator[I](
val postfix: String,
val func: (I) => String,
val keyFunc: (I) => String //to demonstrate other internal usage of params
//ex: computer cache key
) {
def apply(params: I): String = {
val key = keyFunc(params)
s"[${func(params)}]=$postfix (call with key=$key)"
}
}
представим есть некоторые функции, которые хочется задекорировать (произвольной арности):
object TestObj {
def simple(name: String): String = s"name: $name"
def complex(a: String, b: Int): String = s"a: $a, b: $b"
}
Декорируем и всё ок:
val arity1Func: String => String = TestObj.simple
val decoratedSimpleFunc = new SimpleDecorator[String](
"decorator1",
arity1Func,
(name: String) => s"name=$name"
)
val arity2Func: Tuple2[String, Int] => String = (TestObj.complex _).tupled
val decoratedComplexFunc = new SimpleDecorator[(String, Int)](
"decorator2",
arity2Func,
{case (a: String, b: Int) => s"key[a=$a,b=$b]"}
)
scala> TestObj.simple("Vasya")
res3: String = name: Vasya
scala> decoratedSimpleFunc("Vasya")
res4: String = [name: Vasya]=decorator1 (call with key=name=Vasya)
scala> TestObj.complex("R", 3)
res5: String = a: R, b: 3
scala> decoratedComplexFunc("R", 3)
res6: String = [a: R, b: 3]=decorator2 (call with key=key[a=R,b=3])
object TestObjWithImplicit {
def simple(s: String)(implicit c: MyContext): String = s"s:$s (x${c.n})"
def complex(a: String, b: Int)(implicit c: MyContext): String = s"a: $a (x${b * c.n})"
def curried(a: String, b: Int)(c: MyContext): String = s"a: $a (x${b * c.n})"
}
Обратим внимание на тип
val simpleFuncWithImplicit: String => String = TestObjWithImplicit.simple
Вызов возможен, но передать явно implicit больше нельзя
scala> simpleFuncWithImplicit("a")
res10: String = s:a (x0)
scala> context2
res12: MyContext = MyContext@5833f5cd
scala> context2.n
res13: Int = 2
scala> simpleFuncWithImplicit("a")(context2)
<console>:14: error: type mismatch;
found : MyContext
required: Int
simpleFuncWithImplicit("a")(context2)
Что логично глядя на тип выше, там где стоит context2 - это уже получение символа у строки.
Implicit параметр уже подставляется на эта создания анонимной фукнции и поменять его потом нельзя, даже хаком.
scala> u reify {val simpleFuncWithImplicit: String => String = TestObjWithImplicit.simple}
res19: reflect.runtime.universe.Expr[Unit] =
Expr[Unit]({
val simpleFuncWithImplicit: Function1[Predef.String, Predef.String] = {
((s) => $read.TestObjWithImplicit.simple(s)($read.defContext))
};
()
})
scala> val hack1 = (f: (String) => String, param: String, c: MyContext) => {implicit val context = c; f(param)}
hack1: (String => String, String, MyContext) => String = <function3>
scala> hack1(simpleFuncWithImplicit, "a", context2)
res20: String = s:a (x0)
Для начала хочется разобраться, как вернуть возможность подстановки implicit параметров в анонимную фукнцию. Или для этого нужно сразу сделать обёртку без implicit?
А второй более практический вопрос: как делать такие декораторы? Может это слишком сложный способ и есть проще? Trait'ы? Естественно декораторы хочется наслаивать друг на друга.