Как исследовать объекты / типы / и т.д. из Scala REPL?

Я уже некоторое время работаю со Scala и написал для него более 10000 строковых программ, но некоторые внутренние механизмы меня все еще смущают. Я пришел в Scala из Python после того, как у меня уже было тесное знакомство с Java, C и Lisp, но, несмотря на это, он шел медленно, и огромная проблема - это разочаровывающая трудность, с которой я часто сталкиваюсь, пытаясь исследовать внутреннюю работу объектов / типов. / классы / и т.д.. используя Scala REPL по сравнению с Python. В Python вы можете исследовать любой объект foo (тип, объект в глобальной переменной, встроенная функция и т. д.), используя foo чтобы увидеть, что вещь оценивает, type(foo) показать свой тип, dir(foo) рассказать вам о методах, которые вы можете вызвать, и help(foo) получить встроенную документацию. Вы даже можете делать такие вещи, как help("re") узнать документацию по названному пакету re (который содержит объекты и методы регулярного выражения), даже если с ним не связано ни одного объекта.

В Scala вы можете попробовать прочитать документацию в Интернете, поискать исходный код в библиотеке и т. Д., Но это часто может быть очень трудно для вещей, в которых вы не знаете, где и даже что они есть (и это часто бывает большой кусок, чтобы откусить, учитывая объемную иерархию типов - материал плавает в разных местах (пакет scala, Predef, различные неявные преобразования, символы, такие как :: это почти невозможно для Google). REPL должен быть способом непосредственного изучения, но в действительности все гораздо более загадочно. Скажи, что я видел ссылку на foo где-то, но я понятия не имею, что это такое. По-видимому, нет такого понятия, как "руководство по систематическому исследованию штук Scala с REPL", но вот что я собрал после большого количества проб и ошибок:

  1. Если foo это значение (которое, по-видимому, включает в себя вещи, хранящиеся в переменных плюс сопутствующие объекты и другие Scala objectс) можно оценить foo непосредственно. Это должно сказать вам тип и значение результата. Иногда результат полезен, иногда нет.
  2. Если foo это значение, вы можете использовать :type foo чтобы получить его тип. (Не обязательно просвещающий.) Если вы используете это при вызове функции, вы получите тип возвращаемого значения без вызова функции.
  3. Если foo это значение, вы можете использовать foo.getClass чтобы получить свой класс. (Часто более поучительный, чем предыдущий, но чем класс объекта отличается от его типа?)
  4. Для класса foo, ты можешь использовать classOf[foo]хотя не очевидно, что означает результат.
  5. Теоретически вы можете использовать :javap foo разобрать класс - который должен быть самым полезным из всех, но для меня это не получается полностью и единообразно.
  6. Иногда вы должны соединить вещи из сообщений об ошибках.

Пример неудачного использования :javap:

scala> :javap List
Failed: Could not find class bytes for 'List'

Пример просветляющего сообщения об ошибке:

scala> assert
<console>:8: error: ambiguous reference to overloaded definition,
both method assert in object Predef of type (assertion: Boolean, message: => Any)Unit
and  method assert in object Predef of type (assertion: Boolean)Unit
match expected type ?
              assert
              ^

Хорошо, теперь давайте попробуем простой пример.

scala> 5
res63: Int = 5

scala> :type 5
Int

scala> 5.getClass
res64: java.lang.Class[Int] = int

Достаточно просто...

Теперь давайте попробуем несколько реальных случаев, когда это не так очевидно:

scala> Predef
res65: type = scala.Predef$@3cd41115

scala> :type Predef
type

scala> Predef.getClass
res66: java.lang.Class[_ <: object Predef] = class scala.Predef$

Что это значит? Почему тип Predef просто typeтогда как класс scala.Predef$? Я понимаю, что $ - это способ, которым сопутствующие объекты включаются в Java... но документы Scala на Google говорят мне, что Predef является object Predef extends LowPriorityImplicits - как я могу вывести это из REPL? И как я могу посмотреть, что в нем?

Хорошо, давайте попробуем еще одну запутанную вещь:

scala> `::`
res77: collection.immutable.::.type = ::

scala> :type `::`
collection.immutable.::.type

scala> `::`.getClass
res79: java.lang.Class[_ <: object scala.collection.immutable.::] = class scala.collection.immutable.$colon$colon$

scala> classOf[`::`]
<console>:8: error: type :: takes type parameters
              classOf[`::`]
                      ^

scala> classOf[`::`[Int]]
res81: java.lang.Class[::[Int]] = class scala.collection.immutable.$colon$colon

Хорошо, это оставило меня в безнадежном замешательстве, и в конце концов мне пришлось читать исходный код, чтобы разобраться во всем этом.

Итак, мои вопросы:

  1. Какой самый лучший способ рекомендовать истинным экспертам Scala использовать REPL для понимания объектов, классов, методов и т. Д. Scala или, по крайней мере, исследовать их так хорошо, как это можно сделать из REPL?
  2. Как я могу получить :javap работаешь из REPL для встроенных вещей? (Разве это не должно работать по умолчанию?)

Спасибо за любое просвещение.

4 ответа

Решение

Вы упомянули важный момент, которого немного не хватает в Scala: документация.

REPL - фантастический инструмент, но он не настолько фантастичен, каким может быть. Слишком много недостающих функций и возможностей, которые можно улучшить - некоторые из них упоминаются в вашем посте. Скаладок тоже хороший инструмент, но он далеко не идеален. Кроме того, много кода в API еще не задокументировано или слишком мало, и примеры кода часто отсутствуют. Среды IDE являются полными ошибками, и по сравнению с возможностями, которые Java IDE показывают, они выглядят как игрушки для детского сада.

Тем не менее, существует огромная разница между текущими инструментами Scalas по сравнению с инструментами, доступными, когда я начал изучать Scala 2-3 года назад. В то время IDE постоянно компилировали некоторую корзину в фоновом режиме, компилятор падал каждые несколько минут, а некоторая документация полностью отсутствовала. Часто я получал приступы ярости и желал смерти и коррупции авторам Scala.

И сейчас? У меня больше нет таких атак ярости. Потому что инструменты, которые у нас сейчас есть, великолепны, хотя и не идеальны!

Существует http://docs.scala-lang.org/, в котором содержится много отличной документации. Есть учебники, шпаргалки, глоссарии, руководства и многое другое. Еще один замечательный инструмент - Scalex, который может найти даже самого странного оператора, о котором только можно подумать. Это Скалас Хугл, и хотя он еще не так хорош, как его великий идеал, он очень полезен.

Scala2.10 предлагает большие улучшения в виде собственной библиотеки отражений Scalas:

// needs Scala2.10M4
scala> import scala.reflect.runtime.{universe => u}
import scala.reflect.runtime.{universe=>u}

scala> val t = u.typeOf[List[_]]
t: reflect.runtime.universe.Type = List[Any]

scala> t.declarations
res10: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++, method +:, method toList, method take, method drop, method slice, method takeRight, method splitAt, method takeWhile, method dropWhile, method span, method reverse, method stringPrefix, method toStream, method removeDuplicates)

Документация для новой библиотеки отражений все еще отсутствует, но находится в процессе разработки. Это позволяет легко использовать scalac внутри REPL:

scala> u reify { List(1,2,3) map (_+1) }
res14: reflect.runtime.universe.Expr[List[Int]] = Expr[List[Int]](immutable.this.List.apply(1, 2, 3).map(((x$1) => x$1.$plus(1)))(immutable.this.List.canBuildFrom))

scala> import scala.tools.reflect.ToolBox
import scala.tools.reflect.ToolBox

scala> import scala.reflect.runtime.{currentMirror => m}
import scala.reflect.runtime.{currentMirror=>m}

scala> val tb = m.mkToolBox()
tb: scala.tools.reflect.ToolBox[reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@32f7fa37

scala> tb.parseExpr("List(1,2,3) map (_+1)")
res16: tb.u.Tree = List(1, 2, 3).map(((x$1) => x$1.$plus(1)))

scala> tb.runExpr(res16)
res18: Any = List(2, 3, 4)

Это даже больше, когда мы хотим знать, как код Scala переводится внутри. Раньше надо набирать scala -Xprint:typer -e "List(1,2,3) map (_+1)" чтобы получить внутреннее представительство. Кроме того, в новый выпуск вошли некоторые небольшие улучшения, например:

scala> :type Predef
scala.Predef.type

Scaladoc получит некоторый граф иерархии типов (нажмите на иерархию типов).

С помощью макросов стало возможным значительно улучшить сообщения об ошибках. Есть библиотека с именем Ожидаемый, которая делает это:

// copied from GitHub page
import org.expecty.Expecty

case class Person(name: String = "Fred", age: Int = 42) {
  def say(words: String*) = words.mkString(" ")
}

val person = Person()
val expect = new Expecty()

// Passing expectations

expect {
  person.name == "Fred"
  person.age * 2 == 84
  person.say("Hi", "from", "Expecty!") == "Hi from Expecty!"
}

// Failing expectation

val word1 = "ping"
val word2 = "pong"

expect {
  person.say(word1, word2) == "pong pong"
}

/*
Output:

java.lang.AssertionError:

person.say(word1, word2) == "pong pong"
|      |   |      |      |
|      |   ping   pong   false
|      ping pong
Person(Fred,42)
*/

Существует инструмент, позволяющий находить библиотеки, размещенные на GitHub, который называется http://ls.implicit.ly/.

У IDE теперь есть некоторая семантическая подсветка, чтобы показать, является ли член объектом / типом / методом / чем-то еще. Семантическая подсветка ScalaIDE.

Функция javap в REPL - это всего лишь вызов нативного javap, поэтому он не очень полезен. Вы должны полностью указать название модуля:

scala> :javap scala.collection.immutable.List
Compiled from "List.scala"
public abstract class scala.collection.immutable.List extends scala.collection.AbstractSeq implements scala.collection.immutable.LinearSeq,scala.Product,scala.collection.LinearSeqOptimized{
...

Некоторое время назад я написал сводку о том, как код Scala компилируется в Bytecode, который предлагает много вещей, которые нужно знать.

И самое лучшее: все это сделано за последние несколько месяцев!

Итак, как использовать все эти вещи внутри REPL? Ну, это невозможно... пока нет.;)

Но я могу сказать вам, что однажды у нас будет такой ответ. REPL, который показывает нам документацию, если мы хотим ее увидеть. REPL, который позволяет нам общаться с ним (может быть, как lambdabot). Ответ, который позволяет нам делать классные вещи, которые мы до сих пор не можем себе представить. Я не знаю, когда это произойдет, но я знаю, что многое было сделано в последние годы, и я знаю, что еще больше будет сделано в следующие годы.

Javap работает, но вы указываете на это scala.Predef.List, который является typeне class, Укажите это вместо scala.collection.immutable.List,

Теперь, по большей части, достаточно просто ввести значение и посмотреть, какой тип результата достаточно. С помощью :type может быть полезным иногда. Я считаю, что использовать getClass Это действительно плохой способ сделать это.

Кроме того, вы иногда смешиваете типы и значения. Например, здесь вы ссылаетесь на объект :::

scala> `::`.getClass res79: java.lang.Class[_ <: object
scala.collection.immutable.::] = class
scala.collection.immutable.$colon$colon$

И здесь вы ссылаетесь на класс :::

scala> classOf[`::`[Int]] res81: java.lang.Class[::[Int]] = class
scala.collection.immutable.$colon$colon

Объекты и классы - это не одно и то же, и, на самом деле, существует общий шаблон объектов и классов с одинаковым именем, с определенным именем для их отношений: компаньоны.

Вместо dir, просто используйте завершение вкладки:

scala> "abc".
+                     asInstanceOf          charAt                codePointAt           codePointBefore       codePointCount
compareTo             compareToIgnoreCase   concat                contains              contentEquals         endsWith
equalsIgnoreCase      getBytes              getChars              indexOf               intern                isEmpty
isInstanceOf          lastIndexOf           length                matches               offsetByCodePoints    regionMatches
replace               replaceAll            replaceFirst          split                 startsWith            subSequence
substring             toCharArray           toLowerCase           toString              toUpperCase           trim

scala> "abc".compareTo
compareTo             compareToIgnoreCase

scala> "abc".compareTo
                             def compareTo(String): Int

Если вы войдете в режим питания, вы получите гораздо больше информации, но вряд ли для новичков. Выше показаны типы, методы и сигнатуры методов. Javap будет декомпилировать вещи, хотя для этого нужно, чтобы вы хорошо разбирались в байт-коде.

Там есть другие вещи - обязательно посмотрите :helpи посмотреть, что доступно.

Документы доступны только через скалярный API. Держите его открытым в браузере и используйте его возможности поиска для быстрого поиска классов и методов. Также обратите внимание, что, в отличие от Java, вам не нужно перемещаться по списку наследования, чтобы получить описание метода.

И они прекрасно ищут символы. Я подозреваю, что вы не потратили много времени на скаладок, потому что другие инструменты документооборота там просто не до этого. На ум приходит Javadoc - это ужасный просмотр пакетов и классов.

Если у вас есть конкретные вопросы о стиле переполнения стека, используйте Symbol Hound для поиска по символам.

Используйте ночные Scaladocs: они будут отличаться от используемой вами версии, но они всегда будут наиболее полными. Кроме того, прямо сейчас они намного лучше во многих отношениях: вы можете использовать TAB для чередования кадров, с автофокусом на полях поиска, вы можете использовать стрелки для навигации по левому кадру после фильтрации, и ENTER, чтобы выбрать выбранный элемент. появляются в правом кадре. У них есть список неявных методов и диаграммы классов.

Я обошелся с гораздо менее мощным REPL и гораздо беднее Scaladoc - они работают вместе. Конечно, я перешел на транк (теперь HEAD) только для того, чтобы заполучить завершение табуляции.

Обратите внимание, что Scala 2.11.8. Новая вкладка в Scala REPL может облегчить исследование типа / обнаружение.

Теперь оно включает в себя:

  • Завершение CamelCase:
    пытаться:
    (l: List[Int]).rroТаб,
    он расширяется до:
    (l: List[Int]).reduceRightOption

  • Найдите участников, набрав любую часть имени CamelCased:
    пытаться:
    classOf[String].typТаб, чтобы получить getAnnotationsByType, getComponentType и другие

  • Завершите сборщик бинов без ввода get:
    пытаться:
    (d: java.util.Date).dayTAB

  • Нажмите TAB дважды, чтобы увидеть подпись метода:
    пытаться:
    List(1,2,3).partТаб,
    который завершает к:
    List(1,2,3).partition;
    снова нажмите TAB для отображения:
    def partition(p: Int => Boolean): (List[Int], List[Int])

Вам нужно передать полностью определенное имя класса javap,

Сначала возьми, используя classOf:

scala> classOf[List[_]]
res2: java.lang.Class[List[_]] = class scala.collection.immutable.List

Тогда используйте javap (у меня не работает из repl: ":javap недоступен на этой платформе.") поэтому пример из командной строки, в repl, я полагаю, вам не нужно указывать classpath:

d:\bin\scala\scala-2.9.1-1\lib>javap -classpath scala-library.jar "scala.collection.immutable.List"

Но я сомневаюсь, что это поможет вам. Возможно, вы пытаетесь использовать методы, которые вы использовали в динамических языках. Я крайне редко использую repl в scala (хотя часто использую его в javascript). IDE и источники мое все.

Другие вопросы по тегам