このセクションでは、PythonとScalaのプログラミング言語を比較します。 Pythonに詳しくて、Scalaについて学びたいと考えているプログラマーを対象としています。具体的には、Pythonの言語機能とScalaの比較例を挙げて説明します。

はじめに

例に入る前に、この最初のセクションでは、後に続くセクションの簡単な紹介と概要を提供します。 まず、2つの言語の概要を高レベルで比較し、その後、実践的なプログラミングでの比較を行います。

高レベルでの類似点

高レベルで見ると、ScalaはPythonと以下のような 類似点 を共有しています。

高レベルでの相違点

高レベルで見ると、PythonとScalaの間には以下のような 相違点 があります:

プログラミングレベルでの類似点

このセクションでは、実際に Python と Scala でコードを書く際に見られる類似点を紹介します。

プログラミングレベルでの違い

プログラミングレベルでも、コードを書く際に日常的に見られるいくつかの違いがあります:

機能の比較と例

この導入に基づき、以下のセクションではPythonとScalaのプログラミング言語機能を並べて比較します。

コメント

Pythonはコメントに # を使用しますが、Scalaのコメント構文はC、C++、Javaなどの言語と同じです。

# a comment
// a comment
/* ... */
/** ... */

変数の割り当て

これらの例は、PythonとScalaで変数を作成する方法を示しています。

整数変数,文字列変数

x = 1
x = "Hi"
y = """foo
       bar
       baz"""
val x = 1
val x = "Hi"
val y = """foo
           bar
           baz"""

リスト

x = [1,2,3]
val x = List(1,2,3)

辞書/マップ

x = {
  "Toy Story": 8.3,
  "Forrest Gump": 8.8,
  "Cloud Atlas": 7.4
}
val x = Map(
  "Toy Story" -> 8.3,
  "Forrest Gump" -> 8.8,
  "Cloud Atlas" -> 7.4
)

集合

x = {1,2,3}
val x = Set(1,2,3)

タプル

x = (11, "Eleven")
val x = (11, "Eleven")

Scalaのフィールドが可変になる場合は、変数定義に val の代わりに var を使います。

var x = 1
x += 1

しかし、Scalaの慣習として、特に変数を変更する必要がない限り、常にvalを使います。

関数型プログラミングスタイルのレコード

Scalaのケース・クラスはPythonのフローズン・データクラスに似ています。

構造体の定義

from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Person:
  name: str
  age: int
case class Person(name: String, age: Int)

インスタンスを作成して使用する

p = Person("Alice", 42)
p.name   # Alice
p2 = replace(p, age=43)
val p = Person("Alice", 42)
p.name   // Alice
val p2 = p.copy(age = 43)

オブジェクト指向プログラミングスタイルのクラスとメソッド

このセクションでは、オブジェクト指向プログラミングスタイルのクラスとメソッドに関する機能の比較を行います。

クラスとプライマリーコンストラクタ

class Person(object):
  def __init__(self, name):
    self.name = name

  def speak(self):
    print(f'Hello, my name is {self.name}')
class Person (var name: String):
  def speak() = println(s"Hello, my name is $name")

インスタンスを作成して使用する

p = Person("John")
p.name   # John
p.name = 'Fred'
p.name   # Fred
p.speak()
val p = Person("John")
p.name   // John
p.name = "Fred"
p.name   // Fred
p.speak()

1行メソッド

def add(a, b): return a + b
def add(a: Int, b: Int): Int = a + b

複数行のメソッド

def walkThenRun():
  print('walk')
  print('run')
def walkThenRun() =
  println("walk")
  println("run")

インターフェース、トレイト、継承

Java 8以降に慣れていれば、ScalaのtraitはJavaのインターフェースに良く似ていることに気づくと思います。 Pythonのインターフェース(プロトコル)や抽象クラスがあまり使われないのに対して、Scalaではトレイトが常に使われています。 したがって、この例では両者を比較するのではなく、Scalaのトレイトを使って数学のちょっとした問題を解く方法を紹介します:

trait Adder:
  def add(a: Int, b: Int) = a + b

trait Multiplier:
  def multiply(a: Int, b: Int) = a * b

// create a class from the traits
class SimpleMath extends Adder, Multiplier
val sm = new SimpleMath
sm.add(1,1)        // 2
sm.multiply(2,2)   // 4

クラスやオブジェクトでtraitを使う方法は他にもたくさんあります。 しかし、これは概念を論理的な動作のグループに整理して、完全な解答を作成するために必要に応じてそれらを統合するために、どのように使うことができるかのちょっとしたアイデアを与えてくれます。

制御構文

ここではPythonとScalaの制御構文を比較します。 どちらの言語にも if/else, while, for ループ、 try といった構文があります。 加えて、Scala には match 式があります。

if 文, 1行

if x == 1: print(x)
if x == 1 then println(x)

if 文, 複数行

if x == 1:
  print("x is 1, as you can see:")
  print(x)
if x == 1 then
  println("x is 1, as you can see:")
  println(x)

if, else if, else:

if x < 0:
  print("negative")
elif x == 0:
  print("zero")
else:
  print("positive")
if x < 0 then
  println("negative")
else if x == 0 then
  println("zero")
else
  println("positive")

if 文からの戻り値

min_val = a if a < b else b
val minValue = if a < b then a else b

メソッドの本体としてのif

def min(a, b):
  return a if a < b else b
def min(a: Int, b: Int): Int =
  if a < b then a else b

while ループ

i = 1
while i < 3:
  print(i)
  i += 1
var i = 1
while i < 3 do
  println(i)
  i += 1

rangeを指定したfor ループ

for i in range(0,3):
  print(i)
// preferred
for i <- 0 until 3 do println(i)

// also available
for (i <- 0 until 3) println(i)

// multiline syntax
for
  i <- 0 until 3
do
  println(i)

リスト範囲内のfor ループ

for i in ints: print(i)

for i in ints:
  print(i)
for i <- ints do println(i)

複数行でのfor ループ

for i in ints:
  x = i * 2
  print(f"i = {i}, x = {x}")
for
  i <- ints
do
  val x = i * 2
  println(s"i = $i, x = $x")

複数の “range” ジェネレータ

for i in range(1,3):
  for j in range(4,6):
    for k in range(1,10,3):
      print(f"i = {i}, j = {j}, k = {k}")
for
  i <- 1 to 2
  j <- 4 to 5
  k <- 1 until 10 by 3
do
  println(s"i = $i, j = $j, k = $k")

ガード付きジェネレータ (if 式)

for i in range(1,11):
  if i % 2 == 0:
    if i < 5:
      print(i)
for
  i <- 1 to 10
  if i % 2 == 0
  if i < 5
do
  println(i)

行ごとに複数のif条件

for i in range(1,11):
  if i % 2 == 0 and i < 5:
    print(i)
for
  i <- 1 to 10
  if i % 2 == 0 && i < 5
do
  println(i)

内包表記

xs = [i * 10 for i in range(1, 4)]
# xs: [10,20,30]
val xs = for i <- 1 to 3 yield i * 10
// xs: Vector(10, 20, 30)

match 条件式

# From 3.10, Python supports structural pattern matching
# You can also use dictionaries for basic “switch” functionality
match month:
  case 1:
    monthAsString = "January"
  case 2:
    monthAsString = "February"
  case _:
    monthAsString = "Other"
val monthAsString = month match
  case 1 => "January"
  case 2 => "February"
  _ => "Other"

switch/match

# Only from Python 3.10
match i:
  case 1 | 3 | 5 | 7 | 9:
    numAsString = "odd"
  case 2 | 4 | 6 | 8 | 10:
    numAsString = "even"
  case _:
    numAsString = "too big"
val numAsString = i match
  case 1 | 3 | 5 | 7 | 9 => "odd"
  case 2 | 4 | 6 | 8 | 10 => "even"
  case _ => "too big"

try, catch, finally

try:
  print(a)
except NameError:
  print("NameError")
except:
  print("Other")
finally:
  print("Finally")
try
  writeTextToFile(text)
catch
  case ioe: IOException =>
    println(ioe.getMessage)
  case fnf: FileNotFoundException =>
    println(fnf.getMessage)
finally
  println("Finally")

マッチ式とパターンマッチは、Scalaプログラミングの大きな要素ですが、ここで紹介しているのは、マッチ式の機能の一部だけです。より多くの例については、制御構造のページをご覧ください。

コレクションクラス

このセクションでは、PythonとScalaで利用可能なコレクションクラスcollections classesを比較します。リスト、辞書/マップ、セット、タプルなどです。

リスト

Pythonにはリストがあるように、Scalaにはニーズに応じて、可変および不可変な列(Seq)のクラスがいくつか用意されています。 Pythonのリストは変更可能であるため、Scalaの ArrayBuffer によく似ています。

Pythonリスト & Scalaの列(Seq)

a = [1,2,3]
// use different sequence classes
// as needed
val a = List(1,2,3)
val a = Vector(1,2,3)
val a = ArrayBuffer(1,2,3)

リストの要素へのアクセス

a[0]
a[1]
a(0)
a(1)
// just like all other method calls

リストの要素の更新

a[0] = 10
a[1] = 20
// ArrayBuffer is mutable
a(0) = 10
a(1) = 20

2つのリストの結合

c = a + b
val c = a ++ b

リストの反復処理

for i in ints: print(i)

for i in ints:
  print(i)
// preferred
for i <- ints do println(i)

// also available
for (i <- ints) println(i)

Scala の主な列(Seq)クラスは ListVectorArrayBuffer です。 ListVector は不変な列が必要なときに使うメインクラスで、 ArrayBuffer は可変な列が必要なときに使うメインクラスです。 (Scala における 「バッファ」 とは、大きくなったり小さくなったりする配列のことです。)

辞書/マップ

Python の辞書はScala の Map クラスのようなものです。 しかし、Scala のデフォルトのマップは immutable であり、新しいマップを簡単に作成するための変換メソッドを持っています。

辞書/マップ の作成

my_dict = {
  'a': 1,
  'b': 2,
  'c': 3
}
val myMap = Map(
  "a" -> 1,
  "b" -> 2,
  "c" -> 3
)

辞書/マップの要素へのアクセス

my_dict['a']   # 1
myMap("a")   // 1

for ループでの辞書/マップ

for key, value in my_dict.items():
  print(key)
  print(value)
for (key,value) <- myMap do
  println(key)
  println(value)

Scalaには、さまざまなニーズに対応する他の専門的な Map クラスがあります。

集合

Pythonの集合は、mutable ScalaのSetクラスに似ています。

集合の作成

set = {"a", "b", "c"}
val set = Set(1,2,3)

重複する要素

set = {1,2,1}
# set: {1,2}
val set = Set(1,2,1)
// set: Set(1,2)

Scalaには、他にも様々なニーズに特化したSetクラスがあります。

タプル

PythonとScalaのタプルも似ています。

タプルの作成

t = (11, 11.0, "Eleven")
val t = (11, 11.0, "Eleven")

タプルの要素へのアクセス

t[0]   # 11
t[1]   # 11.0
t(0)   // 11
t(1)   // 11.0

コレクションクラスでのメソッド

PythonとScalaには、同じ関数型メソッドがいくつかあります。

Pythonのラムダ式でこれらのメソッドを使うのに慣れていれば、Scalaがコレクション・クラスのメソッドで同じようなアプローチを持っていることがわかると思います。 この機能を実証するために、ここに2つのサンプルリストを示します。

numbers = [1,2,3]           // python
val numbers = List(1,2,3)   // scala

これらのリストは以下の表で使用され、マップ処理とフィルター処理のアルゴリズムを適用する方法を示しています。

マップ処理の内包表記

x = [i * 10 for i in numbers]
val x = for i <- numbers yield i * 10

フィルター処理の内包表記

evens = [i for i in numbers if i % 2 == 0]
val evens = numbers.filter(_ % 2 == 0)
// or
val evens = for i <- numbers if i % 2 == 0 yield i

マップ、フィルター処理の内包表記

x = [i * 10 for i in numbers if i % 2 == 0]
val x = numbers.filter(_ % 2 == 0).map(_ * 10)
// or
val x = for i <- numbers if i % 2 == 0 yield i * 10

マップ処理

x = map(lambda x: x * 10, numbers)
val x = numbers.map(_ * 10)

フィルター処理

f = lambda x: x > 1
x = filter(f, numbers)
val x = numbers.filter(_ > 1)

Scalaのコレクションメソッド

Scalaのコレクションクラスには100以上の関数メソッドがあり、コードを簡単にすることができます。 Python では、これらの関数の一部は itertools モジュールで利用できます。 mapfilterreduce に加えて、Scala でよく使われるメソッドを以下に示します。 これらのメソッドの例では

これらは利用可能なフィルタリング方法の一部です。

|メソッド|説明| | ————– | ————- | | c1.diff(c2) | c1c2 の要素の差分を返します。| | c.distinct | c の重複しない要素を返します。| | c.drop(n) | 最初の n 要素を除くコレクションのすべての要素を返します。| | c.filter(p) | コレクションから、その述語が true となるすべての要素を返します。| | c.head | コレクションの最初の要素を返します。 (コレクションが空の場合は NoSuchElementException をスローします。)| | c.tail | コレクションから最初の要素を除くすべての要素を返します。 (コレクションが空の場合は UnsupportedOperationException をスローします。)| | c.take(n) | コレクション c の最初の n 個の要素を返します。 以下に、いくつかのトランスフォーマメソッドを示します。| |メソッド| 説明 | | ————— | ————- | c.flatten | コレクションのコレクション(リストのリストなど)を単一のコレクション(単一のリスト)に変換します。| | c.flatMap(f) | コレクション c のすべての要素に f を適用し(map のような動作)、その結果のコレクションの要素を平坦化して、新しいコレクションを返します。| | c.map(f) | コレクション c のすべての要素に f を適用して、新しいコレクションを作成します。| | c.reduce(f) | 「リダクション」関数 fc の連続する要素に適用し、単一の値を生成します。| | c.sortWith(f) | 比較関数 f によってソートされた c のバージョンを返します。| よく使われるグループ化メソッド: | メソッド | 説明 | | —————- | ————- | c.groupBy(f) | コレクションを f に従って分割し、コレクションの Map を作成します。| | c.partition(p) | 述語 p に従って2つのコレクションを返します。| | c.span(p) | 2つのコレクションからなるコレクションを返します。1つ目は c.takeWhile(p) によって作成され、2つ目は c.dropWhile(p) によって作成されます。| | c.splitAt(n) | コレクション c を要素 n で分割して、2つのコレクションからなるコレクションを返します。| 情報および数学的なメソッド: | メソッド | 説明 | | ————– | ————- | | c1.containsSlice(c2) | c1 がシーケンス c2 を含む場合に true を返します。| | c.count(p) | ptrue である c の要素数を数えます。| | c.distinct | c の一意な要素を返します。| | c.exists(p) | コレクション内のいずれかの要素に対して ptrue であれば true を返します。| | c.find(p) | p に一致する最初の要素を返します。 要素は Option[A] として返されます。| | c.min | コレクションから最小の要素を返します。 (_java.lang.UnsupportedOperationException_例外が発生する場合があります。)| | c.max | コレクションから最大の要素を返します。 (_java.lang.UnsupportedOperationException_例外が発生する場合があります。)| | c slice(from, to) | 要素 from から始まり、要素 to で終わる要素の範囲を返します。| | c.sum | コレクション内のすべての要素の合計を返しますw。 (コレクション内の要素に対して Ordering を定義する必要があります。)|

以下に、これらのメソッドがリスト上でどのように機能するかを説明する例をいくつか示します。

val a = List(10, 20, 30, 40, 10)      // List(10, 20, 30, 40, 10)
a.distinct                            // List(10, 20, 30, 40)
a.drop(2)                             // List(30, 40, 10)
a.dropRight(2)                        // List(10, 20, 30)
a.dropWhile(_ < 25)                   // List(30, 40, 10)
a.filter(_ < 25)                      // List(10, 20, 10)
a.filter(_ > 100)                     // List()
a.find(_ > 20)                        // Some(30)
a.head                                // 10
a.headOption                          // Some(10)
a.init                                // List(10, 20, 30, 40)
a.intersect(List(19,20,21))           // List(20)
a.last                                // 10
a.lastOption                          // Some(10)
a.slice(2,4)                          // List(30, 40)
a.tail                                // List(20, 30, 40, 10)
a.take(3)                             // List(10, 20, 30)
a.takeRight(2)                        // List(40, 10)
a.takeWhile(_ < 30)                   // List(10, 20)

これらのメソッドは、Scalaにおける共通のパターンを示しています。オブジェクト上で利用可能な機能メソッドです。 これらの方法はいずれも、初期リスト a を変更しません。代わりに、コメントの後に示されているデータをすべて返します。 利用可能なメソッドは他にもたくさんありますが、これらの説明と例が、組み込みのコレクションメソッドの持つ力を実感する一助となれば幸いです。

列挙

このセクションでは、PythonとScala 3の列挙型を比較します。

列挙型の作成

from enum import Enum, auto
class Color(Enum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()
enum Color:
  case Red, Green, Blue

値とその比較

Color.RED == Color.BLUE  # False
Color.Red == Color.Blue  // false

パラメータ化された列挙型

N/A
enum Color(val rgb: Int):
  case Red   extends Color(0xFF0000)
  case Green extends Color(0x00FF00)
  case Blue  extends Color(0x0000FF)

ユーザー定義による列挙メンバー

N/A
enum Planet(
    mass: Double,
    radius: Double
  ):
  case Mercury extends
    Planet(3.303e+23, 2.4397e6)
  case Venus extends
    Planet(4.869e+24, 6.0518e6)
  case Earth extends
    Planet(5.976e+24, 6.37814e6)
  // more planets ...

  // fields and methods
  private final val G = 6.67300E-11
  def surfaceGravity = G * mass /
    (radius * radius)
  def surfaceWeight(otherMass: Double)
    = otherMass * surfaceGravity

Scala 独自の概念

Scalaには、Pythonには現在同等の機能がない概念が他にもあります。 詳細は以下のリンクを参照してください。

Scala と仮想環境

Scalaでは、Pythonの仮想環境に相当するものを明示的に設定する必要はありません。デフォルトでは、Scalaのビルドツールがプロジェクトの依存関係を管理するため、ユーザーは手動でパッケージをインストールする必要がありません。例えば、sbtビルドツールを使用する場合、build.sbtファイルのlibraryDependencies設定で依存関係を指定し、

cd myapp
sbt compile

以上のコマンドを実行することで、その特定のプロジェクトに必要なすべての依存関係が自動的に解決されます。 ダウンロードされた依存関係の場所は、主にビルドツールの実装の詳細であり、ユーザーはこれらのダウンロードされた依存関係と直接やりとりする必要はありません。 例えば、sbtの依存関係キャッシュ全体を削除した場合、プロジェクトの次のコンパイル時には、sbtが自動的に必要な依存関係をすべて解決し、ダウンロードし直します。 これはPythonとは異なります。Pythonではデフォルトで依存関係がシステム全体またはユーザー全体のディレクトリにインストールされるため、プロジェクトごとに独立した環境を取得するには、対応する仮想環境を作成する必要があります。 例えば、venvモジュールを使用して、特定のプロジェクト用に次のように仮想環境を作成できます。

cd myapp
python3 -m venv myapp-env
source myapp-env/bin/activate
pip install -r requirements.txt

これにより、プロジェクトの myapp/myapp-env ディレクトリにすべての依存関係がインストールされ、シェル環境変数 PATH が変更されて、依存関係が myapp-env から参照されるようになります。 Scalaでは、このような手動での作業は一切必要ありません。