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



我已经使用Scala工作了一段时间,并用它编写了一个10000多行的程序,但我仍然对一些内部工作方式感到困惑。我从Python来到Scala之前,我已经非常熟悉Java、C和Lisp,但即便如此,它还是进展缓慢,一个巨大的问题是,当我试图研究对象/类型/类等的内部工作时,经常发现令人沮丧的困难。与Python相比,使用Scala REPL。在Python中,您可以使用foo来研究任何对象foo(类型、全局变量中的对象、内置函数等),type(foo)显示其类型,dir(foo)告诉您可以对其调用的方法,help(foo)获得内置文档。您甚至可以执行help("re")之类的操作来查找名为re的包(其中包含正则表达式对象和方法)上的文档,即使没有与之关联的对象

在Scala中,你可以尝试在线阅读文档,去库中查找源代码,等等。,但对于那些你不知道它们在哪里,甚至不知道它们是什么的东西来说,这往往是非常困难的(考虑到庞大的类型层次结构,这通常是一大块要咬的东西)——东西在不同的地方浮动(包scalaPredef、各种隐式转换,像::这样的符号,谷歌几乎不可能做到)。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'

启发性错误消息示例:

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文档告诉我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中为内置内容工作?(默认情况下,它不应该工作吗?)

谢谢你的启发。

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

REPL是一个很棒的工具,但它并没有那么棒。有太多缺失的功能和可以改进的功能,其中一些在你的帖子中提到过。Scaladoc也是一个不错的工具,但还远远不够完美。此外,API中的许多代码还没有或文档太少,代码示例经常缺失。IDE是十足的ob bug,与Java IDE向我们展示的可能性相比,它们看起来像幼儿园的玩具。

然而,与2-3年前我开始学习Scala时可用的工具相比,Scalas当前的工具有着巨大的差异。当时IDE在后台永久编译一些垃圾,编译器每隔几分钟就会崩溃,一些文档根本不存在。我经常受到愤怒的攻击,并希望Scala的作者们能死并堕落。

现在呢?我再也没有这种愤怒的攻击了。因为我们目前拥有的工具非常棒,尽管它们并不完美!

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

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)

新反射库的文档仍然缺失,但正在进行中。它允许在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的调用,因此它不是一个非常富featue的工具。您必须完全限定模块的名称:

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,它让我们做一些我们仍然无法想象的酷事情。我不知道什么时候会出现这种情况,但我知道过去几年做了很多事情,我知道未来几年还会做更多的事情。

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,看看有什么可用的。

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

他们确实非常善于搜索符号。我怀疑你没有花太多时间在scaladoc上,因为其他的文档工具根本达不到它的要求。

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

使用夜间Scaladocs:它们会与您使用的任何版本有所不同,但它们始终是最完整的。此外,现在它们在很多方面都要好得多:可以使用TAB在帧之间交替,在搜索框上自动聚焦,可以在过滤后使用箭头在左帧上导航,并使用ENTER将选定的元素显示在右帧上。它们有隐式方法的列表,并且有类图。

我已经用了一个功能差得多的REPL和一个功能低得多的Scaladoc——它们确实可以一起工作。当然,我跳到主干(现在是HEAD)只是为了完成我的选项卡。

注意scala 2.11.8 scala REPL中的新选项卡完成可以促进类型探索/发现。

它现在包括:

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

  • 通过键入名称的任何CamelCased部分来查找成员:
    尝试:
    CCD_ 42TAB,得到getAnnotationsByTypegetComponentType

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

  • 按两次TAB查看方法签名:
    尝试:
    List(1,2,3).partTAB
    其完成为:
    List(1,2,3).partition
    再次按选项卡显示:
    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:binscalascala-2.9.1-1lib>javap -classpath scala-library.jar "scala.collection.immutable.List"

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

相关内容

  • 没有找到相关文章

最新更新