Scala vs Kotlin(翻訳)

私たちのチームは、記事の著者と同様に、Scalaから主要な言語としてKotlinにほぼ1年が経過しました。 私の意見は主に著者と一致するので、彼の興味深い記事の翻訳を提供します。







ブログを更新しなかったので、それはまともな時間でした。 今から一年、私はメインの言語であるScalaからKotlinに切り替えました。 この言語は、Scalaにある多くの落とし穴とあいまいさを回避できる一方で、Scalaで気に入っている多くの良いものを借りてきました。







以下に、ScalaとKotlinで気に入っている例と、両言語での実装方法の比較を示します。







宣言と型推論



両方の言語で特に気に入っているのは、どちらも静的に型推論で型付けされていることです。 これにより、コード内で面倒な宣言をすることなく、静的型付けの力を最大限に活用できます( 元:宣言型ボイラープレート )。 ほとんどの場合、これは両方の言語で機能します。 両方の言語で、名前の後に変数の型のオプションの宣言とともに、不変の型の設定も追跡されます。







サンプルコードは両方の言語で同じです。







ageおよびtype Intという名前の不変変数の宣言:







val age = 1
      
      





String型の可変変数の宣言:







 var greeting = "Hello"
      
      





どちらの言語も、ラムダ関数を最初のクラスのオブジェクトとしてサポートしています。これは、変数に割り当てるか、関数にパラメーターとして渡すことができます。







スカラ







 val double = (i: Int) => { i * 2 }
      
      





コトリン







 val double = {i: Int -> i * 2 }
      
      





データ / ケースクラス



ScalaとKotlinには、 データモデルオブジェクトの表現であるデータクラスに対して同様の概念があります







Scalaアプローチ


Scalaでは、これらは次のようなケースクラスです。







 case class Person(name: String, age: Int)
      
      







コトリンアプローチ


Kotlinはこれらのクラスをデータクラスと呼びます







 data class Person (val name: String, val age: Int)
      
      





主な機能:









Kotlinでは、クラスを初期化するために新しいキーワードが必要ないのと同じように、特別な適用メソッドは必要ありません。 したがって、これは他のクラスと同様に標準のコンストラクタ宣言です。







比較



基本的に、 ケースクラスとデータクラスは似ています。







以下の例は、両方の言語で同じように見えます。







 val jack = Person("jack", 1) val olderJack = jack.copy(age = 2)
      
      





一般に、 データケースクラスは日常使用で交換可能であることがわかりました。 Kotlinにはデータクラスの継承にいくつかの制限ありますが、落とし穴を避けるためのequalsおよびcomponentN関数の実装を考えれば、これは意図的に行われました。







Scalaの場合、 Kotlinが 'when'ブロックのデータクラスを操作する方法と比較してクラスはパターンマットにおいてより強力です。







Kotlinのアプローチは、既存のJavaフレームワークに対して次のように機能します。 通常のJava Beanのように見えます。







どちらの言語でも、名前でパラメーターを渡し、それらのデフォルト値を指定できます。







安全にヌル/オプション



Scalaアプローチ


Scalaでは、 nullはモナドオプション安全に使用することです。 簡単に言えば、 オプションは次の2つの特定の状態のいずれかになります 。Some (x)またはNone







 val anOptionInt: Option[Int] = Some(1)
      
      





または







 val anOptionInt: Option[Int] = None
      
      





isDefinedおよびgetOrElse関数(デフォルト値を示す)を使用してオプションで操作することは可能ですが、より頻繁に使用される状況は、モナドがmapforeach、またはfold演算子で使用される場合です。







たとえば、次のように2つのオプション変数の合計を計算できます。







 val n1Option: Option[Int] = Some(1) val n2Option: Option[Int] = Some(2) val sum = for (n1 <- n1Option; n2 <- n2Option) yield {n1 + n2 }
      
      





変数sumの値はSome(3)になります。 forキーワードは、 yieldキーワードの使用に応じてforeachまたはflatMapとして使用する方法の良い例です。







別の例:







 case class Person(name: String, age: Option[Int]) val person: Option[Person] = Some(Person("Jack", Some(1))) for (p <- person; age <- p.age)  {  println(s"The person is age $age") }
      
      





「The person is age 1」という行が印刷されます。







コトリンアプローチ


Kotlinは、日常使用で非常に実用的なgroovy構文を借用しています。 Kotlinでは、すべての型はnull不可であり、「?」で明示的にnull可能と宣言する必要があります nullを含めることができる場合







同じ例を次のように書き換えることができます。







 val n1: Int? = 1 val n2: Int? = 2 val sum = if (n1 != null && n2 != null) n1 + n2 else null
      
      





これは、Kotlinがコンパイル時チェックを強制してnullをチェックせずにnull許容変数が使用されないようにすることを除き、Java構文にはるかに近いため、NullPointerExceptionを恐れる必要はありません。 また、 nullを non-nullableとして宣言され変数に割り当てることはできません。 さらに、コンパイラーは、 nullの変数を再チェックする必要性を排除するのに十分スマートであり、Javaのような変数の複数のチェックを回避します。







2番目の例と同等のKotlinコードは次のようになります。







 data class Person(val name: String, val age: Int?) val person: Person? = Person("Jack", 1) if (person?.age != null) { printn("The person is age ${person?.age}") }
      
      





または、「let」を使用して、「if」ブロックを次のように置き換えます。







 person?.age?.let { person("The person is age $it") }
      
      





比較



Kotlinでのアプローチが好きです。 読みやすく理解しやすく、複数のネストされたレベルで何が起こるかを簡単に把握できます。 Scalaのアプローチはモナドの振る舞いに基づいており、一部の人々はもちろんそれを好んでいますが、私自身の経験から言えば、コードは少額の投資では過負荷になりつつあると言えます。 mapまたはflatMapを使用する際のこの複雑さには非常に多くの落とし穴があり、モナドマッシュで何か間違ったことをしている場合や、代替を探しずにパターンマッチを使用している場合、コンパイル時に警告も表示されません。明らかではない例外







Kotlinのアプローチは、Javaコードからの型がデフォルトでnull可能という事実により、Javaコードとの統合のギャップも減らします( ここで著者は完全に正しいわけではありません。Javaの型はnullableとnull Scalaは、 nullを安全に保護しない概念としてnullをサポートする必要があります







機能コレクション



もちろん、Scalaは機能的なアプローチをサポートしています。 Kotlinは少し少ないですが、主要なアイデアはサポートされています。







以下の例では、 foldおよびmap関数の操作に特別な違いはありません。







スカラ







 val numbers = 1 to 10 val doubles = numbers.map { _ * 2 } val sumOfSquares = doubles.fold(0) { _ + _ }
      
      





コトリン







 val numbers = 1..10 val doubles = numbers.map { it * 2 } val sumOfSquares = doubles.fold(0) {x,y -> x+y }
      
      





どちらの言語も、レイジーコンピューティングのチェーンという概念をサポートしています。 たとえば、10個の偶数の出力は次のようになります。







スカラ







 val numbers = Stream.from(1) val squares = numbers.map { x => x * x } val evenSquares = squares.filter { _%2 == 0 } println(evenSquares.take(10).toList)
      
      





コトリン







 val numbers = sequence(1) { it + 1 } val squares = numbers.map { it * it } val evenSquares = squares.filter { it%2 == 0 } println(evenSquares.take(10).toList())
      
      





暗黙的な変換と拡張メソッド



これは、ScalaとKotlinがわずかに分岐する領域です。







Scalaアプローチ


Scalaには暗黙的な変換の概念があり、必要に応じて別のクラスに自動的に変換することにより、クラスに高度な機能を追加できます。 広告の例:







 object Helpers { implicit class IntWithTimes(x: Int) {   def times[A](f: => A): Unit = {     for(i <- 1 to x) { f     }   } } }
      
      





次に、コードで次のように使用できます。







 import Helpers._ 5.times(println("Hello"))
      
      





これにより、「Hello」が5回出力されます。 これは、 "times"関数(実際にはIntには存在しない)が呼び出されると、変数がIntWithTimesオブジェクトに自動的にパックされ、関数が呼び出されるという事実により機能します。







コトリンアプローチ


Kotlinは同様の機能に拡張機能を使用します。 Kotlinでは、このような機能を実装するために、拡張が行われる型の形式のプレフィックスのみで、通常の関数を宣言する必要があります。







 fun Int.times(f: ()-> Unit) { for (i in 1..this) {   f() } }
      
      





 5.times { println("Hello")}
      
      





比較



Kotlinのアプローチは、私がScalaでこの機能を主に使用する方法と一致しますが、わずかに単純化された理解可能なレコードという形でわずかに利点があります。







KotlinにはないScalaの機能で、見逃せないもの



私にとってKotlinの最高の機能の1つは、その機能にさえありませんが、ScalaのKotlinにはない機能にあります。









ご清聴ありがとうございました。

オリジナルScala対Kotlin

PS翻訳のいくつかの場所では、特に翻訳なしの単語を残しました(null、null safe、infix、postfixなど)。








All Articles