2012-07-09 106 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])