42

私はしばらくの間 S​​cala を使用しており、これを使用して 10,000 行以上のプログラムを作成しましたが、内部の仕組みのいくつかにまだ混乱しています。Java、C、Lisp に精通した後、私は Python から Scala に移行しましたが、それでもなかなか進まず、大きな問題は、オブジェクト/型の内部の仕組みを調査しようとするときによく見つけたイライラする困難です。 /クラス/など。Python と比較して Scala REPL を使用します。Python では、任意のオブジェクトfoo(型、グローバル変数内のオブジェクト、組み込み関数など) を調査して、そのfoo評価結果を確認したり、type(foo)その型を表示しdir(foo)たり、呼び出すことができるメソッドを示したりすることができます。help(foo)組み込みのドキュメントを取得します。次のようなこともできますhelp("re")re関連付けられたオブジェクトがなくても、指定されたパッケージ (正規表現オブジェクトとメソッドを保持する) に関するドキュメントを見つけることができます。

Scala では、オンラインでドキュメントを試し読みしたり、ソース コードをライブラリで調べたりすることができますが、どこにあるのか、あるいはそれが何であるかさえわからない場合、これは非常に困難な場合があります (多くの場合、大量の型階層を考えると、噛むべき大きな塊です) -- さまざまな場所に物が浮かんでいます (パッケージscalaPredef、さまざまな暗黙の変換、そのような記号::は Google にはほとんど不可能です)。REPL は直接探索する方法であるべきですが、実際には、物事ははるかに不可解です。どこかへの参照を見たfooが、それが何であるかはわかりません。「REPL を使用して Scala のことを体系的に調査するためのガイド」のようなものは明らかに存在しませんが、次のようなものがあります。

  1. fooが値 (おそらく、変数に格納されているものに加えて、コンパニオン オブジェクトやその他の Scala を含む) の場合は、直接object評価できますfoo。これにより、結果の型と値がわかります。結果が役立つ場合もあれば、そうでない場合もあります。
  2. fooが値の場合、使用してその型を取得できます:type foo。(必ずしも啓発的ではありません。) 関数呼び出しでこれを使用すると、関数を呼び出さずに戻り値の型を取得します。
  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単に $ は、コンパニオン オブジェクトを Java に押し込む方法だと思いますが、Google の Scala ドキュメントでは、これはどうすれば REPL から推測できますか? そして、どうすればその内容を調べることができますか?typescala.Predef$Predefobject Predef extends LowPriorityImplicits

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 からできる限り最善の方法でそれらを調査するために、真の Scala の専門家が推奨する最良の方法は何ですか?
  2. :javap組み込みのものを REPL から操作するにはどうすればよいですか? (デフォルトで動作するはずではありませんか?)

啓発をありがとう。

4

4 に答える 4

33

あなたは、Scala に少し欠けている重要な点、つまりドキュメントについて言及しました。

REPL は素晴らしいツールですが、それほど素晴らしいものではありません。不足している機能や改善できる機能が多すぎます - それらのいくつかはあなたの投稿で言及されています. Scaladoc も優れたツールですが、完璧には程遠いです。さらに、API の多くのコードはまだドキュメント化されていないか、あまりドキュメント化されておらず、コード例が欠落していることがよくあります。IDE は完全なバグであり、Java IDE の可能性と比較すると、幼稚園のおもちゃのように見えます。

それにもかかわらず、私が 2 ~ 3 年前に Scala を学び始めたときに利用可能なツールと比べて、Scala の現在のツールには大きな違いがあります。当時、IDE はバックグラウンドでゴミを永続的にコンパイルし、コンパイラは数分ごとにクラッシュし、一部のドキュメントはまったく存在しませんでした。私は頻繁に怒りの攻撃を受け、Scala の作者の死と腐敗を願っていました。

そしていま?これらの怒りの攻撃はもうありません。私たちが現在持っているツールは素晴らしいものですが、完璧ではありません!

多くの優れたドキュメントをまとめたdocs.scala-lang.orgがあります。チュートリアル、チートシート、用語集、ガイド、その他多くの優れたものがあります。もう 1 つの優れたツールはScalexです。これは、考えられる最も奇妙な演算子でさえ見つけることができます。それは Scalas Hoogleあり、彼の偉大な理想にはまだ達していませんが、非常に便利です。

Scala 2.10 では、Scala 独自の 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)

新しい 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)

Scala コードが内部でどのように変換されているかを知りたい場合、これはさらに重要です。以前は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)
*/

ls.implicit.lyと呼ばれる、GitHub でホストされているライブラリを見つけることができるツールがあります。

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である を指しています。代わりに に向けてください。typeclassscala.collection.immutable.List

さて、ほとんどの場合、値を入力して結果の型を確認するだけで十分です。を使用:typeすると役立つ場合があります。ただし、使用するgetClassのは本当に悪い方法だと思います。

また、タイプと値を混在させることもあります。たとえば、ここでは object を参照します::

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 に多くの時間を費やしていないのではないかと思います。他の doc ツールはそれに対応していないからです。Javadoc が頭に浮かびます。パッケージやクラスをブラウジングするのはひどいものです。

特定のスタック オーバーフロー スタイルの質問がある場合は、Symbol Houndを使用してシンボルで検索します。

毎晩のScaladocsを使用してください。使用しているバージョンとは異なりますが、常に最も完全なものになります。さらに、現在は多くの点ではるかに優れています。TAB を使用してフレーム間を切り替えることができ、検索ボックスにオートフォーカスを使用できます。矢印を使用してフィルタリング後に左のフレームをナビゲートし、ENTER を使用して選択した要素を取得できます。右側のフレームに表示されます。それらには暗黙的なメソッドのリストがあり、クラス図があります。

私ははるかに強力ではない REPL とはるかに貧弱な Scaladoc で間に合わせました - それらは一緒に動作します。確かに、タブ補完を手に入れるためだけに、トランク (現在は HEAD) にスキップしました。

于 2012-07-09T20:49:45.733 に答える
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 に答える