42

我已经使用 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 objects),您可以直接评估foo。这应该告诉您结果的类型和值。有时结果是有帮助的,有时没有。
  2. 如果foo是一个值,您可以使用:type foo它来获取它的类型。(不一定有启发性。)如果您在函数调用中使用 this,您将获得返回值的类型,而无需调用该函数。
  3. 如果foo是一个值,您可以使用foo.getClass它来获取它的类。(通常比前面的更有启发性,但是对象的类与其类型有何不同?)
  4. 对于一个 class 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 中的方式......但是 Google 上的 Scala 文档告诉我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

4 回答 4

33

您提到了 Scala 缺少的一点:文档。

REPL 是一个很棒的工具,但它并没有它可以做的那么出色。有太多缺失的功能和可以改进的功能 - 您的帖子中提到了其中一些。Scaladoc 也是一个不错的工具,但距离完美还差得很远。此外,API 中的许多代码还没有记录或记录得太少,并且经常缺少代码示例。IDE 完全是 ob 错误,与 Java IDE 向我们展示的可能性相比,它们看起来像一些幼儿园玩具。

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

现在?我不再有这些狂暴攻击了。因为我们目前拥有的工具虽然并不完美,但非常棒!

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

Scala2.10 以 Scala 自己的反射库的形式出现了巨大的改进:

// 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)

新反射库的文档仍然缺失,但正在进行中。它允许人们在 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)

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

scala> :type Predef
scala.Predef.type

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)
*/

有一个工具可以让人们找到托管在 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{
...

前段时间我写了一篇关于如何将 Scala 代码编译为 Bytecode 的总结,其中提供了很多要了解的东西。

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

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

但我可以告诉你,有一天我们会有这样的 REPL。如果我们想查看它,它会向我们显示文档的 REPL。一个让我们与之通信的 REPL(可能像lambdabot)。一个 REPL 让我们做一些我们仍然无法想象的很酷的事情。我不知道什么时候会这样,但我知道过去几年做了很多事情,我知道未来几年会做更多的事情。

于 2012-07-09T11:47:16.710 回答
5

Javap 有效,但您将其指向scala.Predef.List,这是 a 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,看看有什么可用的。

文档只能通过 scaladoc API 获得。在浏览器上保持打开状态,并使用其搜索功能快速查找类和方法。另外,请注意,与 Java 不同,您不需要浏览继承列表来获取方法的描述。

他们确实可以很好地搜索符号。我怀疑您没有在 scaladoc 上花费太多时间,因为那里的其他文档工具无法胜任。想到 Javadoc —— 浏览包和类是很糟糕的。

如果您有 Stack Overflow 风格的特定问题,请使用Symbol Hound使用符号进行搜索。

使用每晚的Scaladocs:它们会与您使用的任何版本不同,但它们始终是最完整的。此外,现在它们在很多方面都好得多:您可以使用 TAB 来在帧之间切换,自动聚焦在搜索框上,您可以使用箭头在过滤后在左侧帧上导航,以及 ENTER 具有选定的元素出现在右侧框架上。它们具有隐式方法列表,并具有类图。

我已经使用了一个功能更弱的 REPL 和一个更差的 Scaladoc——它们确实可以一起工作。诚然,我跳到主干(现在是 HEAD)只是为了完成制表符。

于 2012-07-09T20:49:45.733 回答
4

请注意,Scala REPL 中的 scala 2.11.8 New tab-completion可以促进类型探索/发现。

它现在包括:

  • CamelCase 完成:
    try:
    (l: List[Int]).rroTAB
    它扩展为:
    (l: List[Int]).reduceRightOption

  • 通过键入名称的任何 CamelCased 部分来查找成员:
    try:
    classOf[String].typTAB、to getgetAnnotationsByTypegetComponentType其他

  • 无需键入 get 即可完成 bean getter:
    尝试:
    (d: java.util.Date).dayTAB

  • TAB两次查看方法签名:
    try:,完成 为:
    List(1,2,3).partTAB; 再按一次显示:

    List(1,2,3).partition
    TAB
    def partition(p: Int => Boolean): (List[Int], List[Int])

于 2016-03-14T08:30:06.140 回答
2

您需要将完全限定的类名传递给javap.

首先使用它classOf

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

然后使用javap(对我来说不适用于 repl:“:javap 在此平台上不可用。”)所以示例来自命令行,我相信在 repl 中,您不需要指定类路径:

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

但我怀疑这会对你有所帮助。可能您正在尝试使用您在动态语言中使用的技术。我很少在 scala 中使用 repl(而在 javascript 中经常使用它)。IDE 和资源就是我的全部。

于 2012-07-09T10:53:07.390 回答