Как исследовать объекты / типы / и т.д. из 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", но вот что я собрал после большого количества проб и ошибок:
- Если
foo
это значение (которое, по-видимому, включает в себя вещи, хранящиеся в переменных плюс сопутствующие объекты и другие Scalaobject
с) можно оценитьfoo
непосредственно. Это должно сказать вам тип и значение результата. Иногда результат полезен, иногда нет. - Если
foo
это значение, вы можете использовать:type foo
чтобы получить его тип. (Не обязательно просвещающий.) Если вы используете это при вызове функции, вы получите тип возвращаемого значения без вызова функции. - Если
foo
это значение, вы можете использоватьfoo.getClass
чтобы получить свой класс. (Часто более поучительный, чем предыдущий, но чем класс объекта отличается от его типа?) - Для класса
foo
, ты можешь использоватьclassOf[foo]
хотя не очевидно, что означает результат. - Теоретически вы можете использовать
:javap foo
разобрать класс - который должен быть самым полезным из всех, но для меня это не получается полностью и единообразно. - Иногда вы должны соединить вещи из сообщений об ошибках.
Пример неудачного использования :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
Хорошо, это оставило меня в безнадежном замешательстве, и в конце концов мне пришлось читать исходный код, чтобы разобраться во всем этом.
Итак, мои вопросы:
- Какой самый лучший способ рекомендовать истинным экспертам Scala использовать REPL для понимания объектов, классов, методов и т. Д. Scala или, по крайней мере, исследовать их так хорошо, как это можно сделать из REPL?
- Как я могу получить
: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).day
TABНажмите 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 и источники мое все.