передача и использование неявной функции без ее определения

#scala #implicit

#scala #неявный

Вопрос:

Я нигде не определил функцию ev . Тогда как работает приведенный ниже код? Разве имплициты не должны быть определены где-то в области видимости, чтобы их можно было использовать?

 def same[T, U](x: U)(implicit ev: U => T): T = {ev(x)}
same(2) // 2
  

Комментарии:

1. Чтобы расширить ответ @TravisBrown, вы также можете сделать: same[Long, Int](2) и посмотреть, что он все еще компилируется, потому Int int2long что для него определен неявный called , который бы заставил это работать. Когда происходит подобное волшебство, всегда думайте о том, какие импликации попадают в область видимости для вас, без явного запроса их

2. Спасибо, Ювал, запутанная часть заключалась в том, на каком основании компилятор выводил T (до того, как он искал неявное). Это мое первое знакомство с неявным методом $conforms .

Ответ №1:

Каждый раз, когда у вас возникают подобные вопросы, хорошим началом является использование API отражения Scala в REPL, чтобы спросить компилятор, что происходит:

 scala> import scala.reflect.runtime.universe.{ reify, showCode }
import scala.reflect.runtime.universe.{reify, showCode}

scala> def same[T, U](x: U)(implicit ev: U => T): T = ev(x)
same: [T, U](x: U)(implicit ev: U => T)T

scala> showCode(reify(same(2)).tree)
res0: String = $read.same(2)(Predef.$conforms)
  

So ev предоставляется <a rel="noreferrer noopener nofollow" href="https:///www.scala-lang.org/api/2.11.8/index.html#scala.Predef$@$conforms[A]:<: Predef.$conforms неявным методом, который предоставит вам экземпляр A <:< A для любого A , где <:< расширяется Function1 .

Итак, это одна подсказка. Для выяснения остального требуется немного подумать о выводе типов. При вызове same(2) компилятор определяет, что выражение 2 имеет тип Int , и делает вывод, что U это Int так . Затем ему нужно выяснить, что T есть, и для этого он ищет неявные функции от Int до x для некоторого типа x .

Вот тут $conforms -то и приходит. Это единственный такой метод в области видимости, поэтому компилятор выбирает его, что означает, что ev он имеет тип Int => Int и T должен быть Int , и все готово.

Комментарии:

1. Это аккуратный трюк с отражением. Я всегда использую scalac -Xprint.. для просмотра дерева.

2. @YuvalItzchakov Да, scalac -Xprint это тоже полезно, но я нахожу это более шумным и менее удобным для автономных примеров, подобных этому.

3. Это определенно создает гораздо больше шума. Я обязательно запомню это 🙂