2012-07-09 51 views
41

我一直在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,各种隐式转换,像Google几乎不可能的::等符号)。 REPL应该是直接探索的方式,但事实上,事情更加神秘。假设我在某处看过foo的引用,但我不知道它是什么。有显然是一个没有这样的事情“指南,系统地调查斯卡拉一样的东西与REPL”,但下面是试错大量后我所拼凑起来:如何调查对象/类型/等。来自Scala REPL?

  1. 如果foo是一个值(这大概包括存储在变量加伴随对象和其他Scala object s)中的东西,那么您可以直接评估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 = [email protected] 

scala> :type Predef 
type 

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

这是什么意思?为什么Predef的类型只是type,而类是scala.Predef$?我认为$是伴侣对象被爪拽到Java中的方式......但Google上的斯卡拉文档告诉我Predefobject Predef extends LowPriorityImplicits - 我怎样才能从REPL中推断出这一点?我怎样才能看看它里面有什么?

OK,让我们尝试另一个令人困惑的事情:

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 

OK,这让我绝望迷茫,最终,我不得不去阅读源代码,以使这个意义上说所有。

所以,我的问题是:

  1. 什么是从使用REPL有道理的Scala的对象,类,方法等的真实斯卡拉专家建议最好的办法,或者至少调查他们作为可以从REPL做到最好?
  2. 我如何从REPL获取:javap的内置内容? (默认情况下不应该工作吗?)

感谢您的任何启示。

回答

32

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

REPL是一个很棒的工具,但它并不像它那么棒。有太多遗漏的功能和功能可以改进 - 其中一些在您的文章中提到。 Scaladoc也是一个很好的工具,但是很远很完美。此外,API中的很多代码还没有或没有太多的文档记录,代码示例经常缺失。 IDE是完整的obb错误,与Java IDE向我们展示的看起来像是一些幼儿园玩具的可能性相比。

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

而现在呢?我没有任何这些愤怒的攻击了。因为我们目前拥有的工具虽然不完美,但却很棒!

docs.scala-lang.org,它总结了很多很棒的文档。有教程,作弊表,词汇表,指南和很多更好的东西。另一个很好的工具是Scalex,它甚至可以找到人们能想到的最怪异的运算符。它是斯卡拉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) 

文档,至今下落不明,但在进步。它允许使用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] = [email protected] 

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将获得一些type-hierarchy graph(点击式的层次结构)。

有了宏,现在可以很好地改善错误信息。有一个叫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{ 
... 

前一段时间我写了一个summary of how Scala code is compiled to Bytecode,它提供了很多的东西就知道了。

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

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

但我可以告诉你,有一天我们会有这样的REPL。如果我们想看到它,REPL向我们显示文档。让我们与之沟通的REPL(或许就像lambdabot)。让我们做一些很酷的事情的REPL,我们仍然无法想象。我不知道什么时候会出现这种情况,但我知道在过去的几年里做了很多事情,而且我知道在未来的几年里会做更多的事情。

+0

感谢一个了不起的写了!我还会提到在M4发布后不久添加的“showRaw”(明天将出现在M5中)。它让我们检查一些反射工件(包括树和类型)的内部结构。 – 2012-07-09 13:29:19

+0

@EugeneBurmako:我知道'universe.showRaw'是M4的成员之一,你的意思是另一个'showRaw'? – sschaef 2012-07-09 14:33:41

+0

是的,我正在谈论这个。在M4发布后的几天内,它被重新修改。 – 2012-07-09 16:17:00

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和来源是我的全部。

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$ 

在这里:

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--浏览整个软件包和类是非常糟糕的。

如果您有具体问题堆栈溢出风格,请使用Symbol Hound使用符号进行搜索。

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

我已经做了一个不太强大的REPL和一个更差的Scaladoc - 他们一起工作。当然,我跳到后备箱(现在头),只是为了让我的手完成标签。

+0

谢谢,我不知道Tab完成或功率模式,或不同的列表。你的信息也强调了我上面所说的,没有明显的文件告诉我任何这些事情。我确实使用了Scaladoc,但IMO并不理想,因为REPL可以让您直接查看当前Scala中实际存在的内容,而不是其他方式。至于'::',我知道这里有一个类型和一个类,但重要的是,当你不是Scala专家时,它通常并不明显,你不应该事先知道这一点当使用REPL时。 – 2012-07-13 02:00:55

4

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

现在包括:

  • 驼峰完成:
    尝试:
    (l: List[Int]).rroTAB
    它扩展为:通过键入任何驼峰格式部分
    (l: List[Int]).reduceRightOption

  • 查找会员的名称:
    尝试:
    classOf[String].typTAB, 得到getAnnotationsByTypegetComponentType和其他

  • 完全豆干将,而无需键入GET:
    尝试:
    (d: java.util.Date).dayTAB

  • TAB两次查看方法签名:
    尝试:
    List(1,2,3).partTAB
    其完成对:
    List(1,2,3).partition;
    按TAB 再次以显示:
    def partition(p: Int => Boolean): (List[Int], List[Int])

相关问题