如何调查对象/类型/等.来自Scala REPL?

Urb*_*ond 41 scala exploratory read-eval-print-loop

我已经和Scala一起工作了一段时间,并且用它编写了10,000多行程序,但我仍然对一些内部工作感到困惑.我已经熟悉Java,C和Lisp后从Python来到Scala,但即使如此,它也很慢,而且一个巨大的问题是我在尝试研究对象/类型的内部工作时经常遇到的令人沮丧的困难/类/等.与Python相比,使用Scala REPL.在Python中,您可以查看任何对象foo(类型,全局变量中的对象,内置函数等),foo以查看事物的评估结果,type(foo)显示其类型,dir(foo)告诉您可以调用它的方法,以及help(foo)获取内置文档.你甚至可以做一些事情,比如help("re")查找名为re(包含正则表达式对象和方法)的包的文档,即使没有与之关联的对象.

在Scala中,您可以尝试在线阅读文档,查看库中的源代码等,但对于您不知道在哪里甚至是什么的事情来说,这通常很难(而且经常是这样的)一大块咬掉,考虑到庞大的类型层次) -东西在不同的地方(包左右浮动scala,Predef各种隐式转换,像符号::是几乎不可能谷歌).REPL应该是直接探索的方式,但实际上,事情更加神秘.说我看过foo某个地方的引用,但我不知道它是什么.显然没有"使用REPL系统地调查Scala事物的指南",但以下是经过大量试验和错误后我拼凑起来的内容:

  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'
Run Code Online (Sandbox Code Playgroud)

启发错误消息的示例:

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
              ^
Run Code Online (Sandbox Code Playgroud)

好的,现在让我们尝试一个简单的例子.

scala> 5
res63: Int = 5

scala> :type 5
Int

scala> 5.getClass
res64: java.lang.Class[Int] = int
Run Code Online (Sandbox Code Playgroud)

很简单......

现在,让我们尝试一些真实案例,但不是那么明显:

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

scala> :type Predef
type

scala> Predef.getClass
res66: java.lang.Class[_ <: object Predef] = class scala.Predef$
Run Code Online (Sandbox Code Playgroud)

这是什么意思?为什么类型Predef简单type,而类是scala.Predef$?我收集了$是同伴对象被硬塞到Java的方式......但在谷歌文档斯卡拉告诉我那Predefobject 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
Run Code Online (Sandbox Code Playgroud)

好吧,这让我无可救药地困惑,最终我不得不去阅读源代码来理解这一切.

所以,我的问题是:

  1. 从真正的Scala专家那里推荐使用REPL来理解Scala对象,类,方法等的最佳方法是什么,或者至少可以从REPL中进行最佳调查?
  2. 如何:javap使用REPL处理内置的东西?(它不应该默认工作吗?)

感谢任何启示.

kir*_*uku 32

您提到了Scala缺少的重要一点:文档.

REPL是一个很棒的工具,但它并不是那么棒.有太多缺少的功能和功能可以改进 - 其中一些在您的帖子中提到.Scaladoc也是一个不错的工具,但它远​​非完美.此外,API中的许多代码尚未记录或文档太少,并且通常缺少代码示例.这些IDE是完全的错误,与Java IDE向我们展示它们看起来像幼儿园玩具的可能性相比.

然而,与我在2 - 3年前开始学习Scala时可用的工具相比,Scalas当前工具存在巨大差异.那时IDE在后台永久编译了一些垃圾,编译器每隔几分钟就崩溃了,而且一些文档绝对不存在.我经常遭到愤怒的攻击,并希望Scala的作者死亡和腐败.

现在?我再也没有任何这种愤怒攻击了.因为我们目前拥有的工具很棒但是并不完美!

docs.scala-lang.org,它总结了很多很棒的文档.有教程,备忘单,词汇表,指南和许多更棒的东西.另一个很棒的工具是Scalex,它甚至可以找到人们可以想到的最奇怪的操作员.它是Scalas Hoogle,虽然它还不如他的伟大理想,但它非常有用.

Scalas以Scalas自己的Reflection库的形式出现了很大的改进:

// 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)
Run Code Online (Sandbox Code Playgroud)

新的Reflection库的文档仍然缺失,但正在进行中.它允许在REPL中以简单的方式使用scalac:

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)
Run Code Online (Sandbox Code Playgroud)

当我们想知道内部如何翻译Scala代码时,这就更好了.以前需要输入scala -Xprint:typer -e "List(1,2,3) map (_+1)" 才能获得内部表示.此外,在新版本中找到了一些小的改进,例如:

scala> :type Predef
scala.Predef.type
Run Code Online (Sandbox Code Playgroud)

Scaladoc将获得一些类型层次结构图(单击类型层次结构).

使用宏,现在可以以一种很好的方式改进错误消息.有一个名为expecty的库,它执行此操作:

// 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)
*/
Run Code Online (Sandbox Code Playgroud)

有一个工具允许人们找到在GitHub上托管的库,名为ls.implicit.ly.

IDE现在有一些语义突出显示,以显示成员是否是对象/类型/方法/等等.ScalaIDE的语义突出显示功能.

REPL的javap功能只是对本机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{
...
Run Code Online (Sandbox Code Playgroud)

前段时间我已经写了一个关于如何将Scala代码编译为Bytecode摘要,它提供了很多要了解的内容.

最好的:这都是在过去几个月里完成的!

那么,如何在REPL中使用所有这些东西呢?嗯,这是不可能的......还没有.;)

但我可以告诉你,总有一天我们会有这样的REPL.一个REPL,如果我们想看到它,它会向我们展示文档.一个让我们与之沟通的REPL(也许就像lambdabot).一个让我们做酷事的REPL,我们仍然无法想象.我不知道什么时候会出现这种情况,但我知道在过去的几年中已经完成了很多工作,我知道未来几年还会有更多的事情要做.

  • 我一定会喜欢你的答案更新以及过去三年的变化. (2认同)

Dan*_*ral 5

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$
Run Code Online (Sandbox Code Playgroud)

在这里你参考课程:::

scala> classOf[`::`[Int]] res81: java.lang.Class[::[Int]] = class
scala.collection.immutable.$colon$colon
Run Code Online (Sandbox Code Playgroud)

对象和类不是一回事,实际上,对象和类的共同模式具有相同的名称,并且具有特定的关系名称:伴侣.

而不是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
Run Code Online (Sandbox Code Playgroud)

如果您进入电源模式,您将获得更多信息,但这对初学者来说很难.以上显示了类型,方法和方法签名.Javap会反编译,但这需要你对字节码有一个很好的处理.

那里还有其他的东西 - 一定要查看:help,看看有什么可用.

文档只能通过scaladoc API获得.在浏览器上保持打开状态,并使用其搜索功能快速查找类和方法.另请注意,与Java相反,您无需浏览继承列表以获取方法的说明.

他们确实搜索完美的符号.我怀疑你没有花太多时间在scaladoc上,因为那里的其他文档工具并不适合它.Javadoc浮现在脑海中 - 浏览包和类是非常糟糕的.

如果您有特定问题Stack Overflow样式,请使用Symbol Hound搜索符号.

使用夜间 Scaladocs:它们会与你使用的任何版本不同,但它们总是最完整的.此外,现在它们在很多方面都要好得多:你可以使用TAB在帧之间交替,在搜索框上自动对焦,你可以使用箭头在过滤后在左边框架上导航,然后使用ENTER来选择所选元素出现在右边的框架上.它们具有隐式方法列表,并具有类图.

我已经做了一个功能强大的REPL和一个差得多的Scaladoc - 他们一起工作.当然,我跳过后备箱(现在是HEAD)只是为了让我的手完成标签.