web-dev-qa-db-ja.com

ScalaがJVMで実行されている場合、ScalaでできることJavaどうやらそうなのでしょうか。

Scala昨日知りました。もっと知りたいのですが、Scalaウェブサイトを読んで、 ScalaがJVMで実行されている場合、ScalaソースからコンパイルされたバイトコードがJava具体化されたジェネリックなど(ただし、これに限定されません)は簡単にできませんか?

私はコンパイラがバイトコードを生成するものであることを理解しています。したがって、コンパイラがソースコードをJVMでサポートされている有効なバイトコードに変換できる限り、両方が同等である必要があります。しかし、私はJavaが独自のジェネリックを具体化することさえできなかったので、別のコンパイラーがこれをどのようにして取り除くことができるのでしょうか?)

31
Mirrana

「すべての」プログラミング言語はx86で実行されますが、それらはどのように互いに大きく異なるのでしょうか。

BrainfuckHaskell はどちらも Turing complete なので、どちらもまったく同じタスクを実行できます。

構文の変更、構文の砂糖、コンパイラーの魔法の間に少し余裕があります。そこではかなり多くのことができますが、常に限界があります。あなたの場合、それはJVMバイトコードです。

JavaがScalaと同じバイトコードを生成できる場合、それらは同等です。ただし、JVMの新機能がScalaのみで実装される場合があります。非常に可能性は低いですが、可能性があります。

32
Filip Haglund

あなたが提起する具体的な問題、具体化されたジェネリックの問題に対処するため。 。 。

多くのコンテキストでは、型パラメータareは実際にはクラスファイルに保存され、消去にもかかわらずリフレクションを介して悪用可能です。たとえば、次のプログラムは_class Java.lang.String_を出力します。

_import Java.lang.reflect.Field;
import Java.lang.reflect.ParameterizedType;
import Java.util.ArrayList;

public class ErasureWhatErasure {
    private final ArrayList<String> foo = null;

    public static void main(final String... args) throws Exception {
        final Field fooField = ErasureWhatErasure.class.getDeclaredField("foo");
        final ParameterizedType fooFieldType =
            (ParameterizedType) fooField.getGenericType();
        System.out.println(fooFieldType.getActualTypeArguments()[0]);
    }
}
_

すべての「消去」とは、パラメーター化された型のインスタンスを作成するときに、型パラメーターがインスタンスの一部として記録されないことを意味します。 new ArrayList<String>()およびnew ArrayList<Integer>()は、同一のインスタンスを作成します。ただし、Javaでも、次のようなよく知られた回避策がいくつかあります。

  1. new ArrayList<String>() { }のようなものは、実際には_ArrayList<String>_の匿名サブクラスの新しいインスタンスを作成し、サブクラス化関係doesは型パラメーターを記録します。したがって、Stringを反射的に取得できます。 (具体的には、_((ParameterizedType) new ArrayList<String>() { }.getClass().getGenericSuperclass()).getActualTypeArguments()[0]_は_String.class_です。)これは、GuiceやGsonなどのさまざまなフレームワークで利用されます。
  2. 独自のクラスを作成する場合は、型パラメーターをコンストラクターを介して渡してフィールドに格納するように要求できます。 (コンパイラーは、コンストラクター引数が型パラメーターと一致するように強制するのに役立ちます。型引数自体がジェネリックである場合、これを回避策#1と組み合わせて、型引数全体を確実に持つことができます。)

JVMに常駐する別の言語は、#2を暗黙的に発生させることにより、ジェネリックスを具体化できます。ジェネリッククラスを宣言するたびに、型パラメーターフィールドとコンストラクターパラメーターが暗黙的に追加され、インスタンス化または拡張するたびに、型引数が暗黙的にコンストラクター引数にコピーされますs)それらを入力します。 Javaは、暗黙のフィールドとコンストラクタパラメータ/引数として、同じことをすでに行っています。つまり、それらを含むメソッドでfinalローカル変数を参照するローカルクラスの場合。

主な制限は、JDKのジェネリッククラス(コレクションフレームワーク、_Java.lang.Class_など)にはまだこの設定がなく、JVMで実行されている代替言語で追加できないことです。そのため、そのような言語は、JDKクラスと同等の独自の言語を提供する必要があります。ただし、JVM自体は引き続き使用できます。

24
ruakh

JVMバイトコードは一種の一般的なマシンコードのふりをし、それは確かにそうです...それで、他の言語をサポートできないと思うのはなぜですか? JVMバイトコードはチューリング完全言語であるため、どの言語で書かれているかに関係なく、すべてのプログラムをバイトコードにコンパイル/変換できます。

すでにバイトコードコンパイラーを備えている言語はたくさんあり(例えば、Pythonの場合はJython、Rubyの場合はJRuby)、Javaとはかなり異なります。

技術的には、すべてのチューリング完全プログラミング言語を別の言語にコンパイルできることに注意してください。たとえば、JSをCにコンパイルしたり、Ruby= Pythonにコンパイルしたりできます。

7
Eneko

簡単に言えば、Scalaは、Scalaコンパイラがコード変換/生成のマスターであるため、これを実行できます。つまり、Java =もそれを行うことができます(多分既にそうです)トリックはバイトコードレベルではなくソースレベルで行われます。

このコードの出力を推測できますか?

def test[T](f : => Any) : T = {
  try { val x = f.asInstanceOf[T]
    println("f.asInstanceOf did not raise an error")
      x
  }
  catch { case e : Throwable =>
    println("f.asInstanceOf did raise an error")
    throw e
  }
}

val x = test[Int]("x")

(そうではない)驚いたことに出力する

f.asInstanceOf did not raise an error
Java.lang.ClassCastException: Java.lang.String cannot be cast to Java.lang.Integer at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.Java:105)

... 33省略

型消去のため、f.asInstanceOf[T]は失敗できませんが、文字列はIntではないため、どこかで失敗する必要があります。 Fortantely Scalaはこの問題の解決策をもたらします:

import shapeless.Typeable
import shapeless.syntax.typeable._

def test[T : Typeable](f : => Any) : T = {

    f.cast[T] match {
        case Some(x) => {
            println("f.cast[T] succeed")
            x
        }
        case None    => {
            println("f.cast[T] failed")
            throw new RuntimeException("cast failed!")
        }
    }
}

val x = test[Int]("x")

ここでの主な違いは、TTypeableとして宣言することです。 Scalaには、型のランタイム表現を提供するいくつかの方法があります。

3

具現化されたジェネリックは、JVMサポートを必要としません。はい。JVMサポートを使用すると、より簡単でパフォーマンスが向上しますが、JVMサポートは必要ありません。たとえば、Scalaコンパイラは、型変数を特徴とするすべてのクラスについて、対応するオブジェクトを格納するフィールドを追加できます。

class List<T> {

}

void test() {
    List<?> list = new List<String>();
    List<Integer> intList = (List<Integer>) list;
}

にコンパイルされます

class List<T> {
    final Class<T> tClass;
    public List(Class<T> tClass) {
        this.tClass = tClass;
    }

    public <O> List<O> castTo(Class<O> oClass) {
        if (tClass == oClass) {
            return (List<O>) this;
        } else {
            throw new ClassCastException("Incompatible type parameter: " + tClass);
        }
    }
}

void test() {
    List<?> list = new List<String>(String.class);
    List<Integer> intList = list.castTo(Integer.class);
}

もちろん、たとえば、同じジェネリック型の異なる型引数に対して別々のクラスを実際に用意することにより、Host型システムとよりシームレスに統合する、より複雑な変換戦略があります。

class List<T> {

}

class List#Integer extends List<Integer> { }

class List#String extends List<String> { }

void test() {
    List<?> list = new List#String();
    List<Integer> intList = (List#Integer) list;
}

(もちろん、この簡単な例では明らかではないいくつかの課題があります。たとえば、再帰型引数、異なるクラスローダーの適切な分離など)

Javaに具体化されたジェネリックがない理由は、具体化がJVMでは不可能であることではありませんが、具体化されたジェネリックは既存のJavaプログラム-何かScalaは心配する必要がありませんでした。

2
meriton