Skip to content

Instantly share code, notes, and snippets.

@maizy
Last active August 29, 2015 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maizy/7a4ffb2f1d5d395e3c23 to your computer and use it in GitHub Desktop.
Save maizy/7a4ffb2f1d5d395e3c23 to your computer and use it in GitHub Desktop.
Как же передаются implicit параметры, почему не также как при карировании?

Как же передаются implicit параметры, почему не также как при карировании?

$ 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

1

Пример 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

2

пример вызова метода объекта без явной передачи 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);
  ()
})

3

Посмотрим как выглядит явная передача 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);
  ()
})

Всё также.

4. Нафига?

Всё это был бы праздный вопрос, если бы не желание писать декораторы на методы.

Вот пример:

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)

5. Вопросы

Для начала хочется разобраться, как вернуть возможность подстановки implicit параметров в анонимную фукнцию. Или для этого нужно сразу сделать обёртку без implicit?

А второй более практический вопрос: как делать такие декораторы? Может это слишком сложный способ и есть проще? Trait'ы? Естественно декораторы хочется наслаивать друг на друга.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment