PHPとScalaにおける型システムの違いを理解する
直積型とは (Product Types)
直積型は複数の値を一つの組み合わせた型で表現します。
PHP
PHPの場合、主にクラスを利用することでこの概念を表現することが可能です。
class Coordinates {
public float $latitude;
public float $longitude;
public function __construct(float $latitude, float $longitude) {
$this->latitude = $latitude;
$this->longitude = $longitude;
}
}
$location = new Coordinates(40.7128, -74.0060);
PHPには、可変性の特性があります。
PHPのクラスのプロパティはデフォルトで変更可能なため、不変性を実現するには、アクセス修飾子を変更したり、readonly
を利用する必要があります。
class Coordinates {
public readonly float $latitude;
public readonly float $longitude;
public function __construct(float $latitude, float $longitude) {
$this->latitude = $latitude;
$this->longitude = $longitude;
}
}
$location = new Coordinates(40.7128, -74.0060);
// $location->latitude = 41.0; // エラー: readonlyプロパティは変更できない
Scala
Scalaでは、ケースクラスやタプルを使用して直積型を簡単に表現することができます。
case class Coordinates(latitude: Double, longitude: Double)
val location = Coordinates(40.7128, -74.0060)
Scalaには、不変性の特性があります。
一度インスタンスが生成されると、その状態を変更することができません。
case class Coordinates(latitude: Double, longitude: Double)
val location = Coordinates(40.7128, -74.0060)
location.latitude = 41.0 // コンパイルエラー: ケースクラスのプロパティは不変
直和型とは (Sum Types)
いくつかの異なる型のいずれか一つの値を取ることができる複合型です。
PHP
PHPの場合、クラスの継承やインターフェース、型チェックなどを利用して直和型のような構造を作ることが可能です。
// PHPの直和型の模倣例
interface Shape {
public function getArea(): float;
}
class Circle implements Shape {
public function __construct(private float $radius) {}
public function getArea(): float {
return pi() * $this->radius * $this->radius;
}
}
// Shape型のオブジェクトの面積を計算する関数
class Square implements Shape {
public function __construct(private float $sideLength) {}
public function getArea(): float {
return $this->sideLength * $this->sideLength;
}
}
function printArea(Shape $shape) {
echo "Area: " . $shape->getArea() . "\n";
}
// インスタンスの作成と関数の使用
$circle = new Circle(3);
$square = new Square(4);
printArea($circle);
printArea($square);
Scala
Scalaの場合は、sealed trait
とケースクラスを組み合わせることで同様の機能を実現できます。
// Scalaでの直和型の表現
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Square(sideLength: Double) extends Shape
// ケースクラスのインスタンス作成
val circle: Shape = Circle(10)
val square: Shape = Square(10)
直和型 + 直積型
直和型と直積型を組み合わせることで、異なる型を一つの型で表現し、それぞれの異なる型を直積型で定義することにより、より柔軟かつ複合的なデータ構造を実現できます。
この図は、Scalaのコード構造を視覚的に示しています。ShapeはCircleとSquareに分岐しており、それぞれが独自のプロパティを持っています。
パターンマッチングの違い
PHP
PHPは、if文やswitch文を使ってパターンマッチングの動作を実現できます。
インターフェースとクラスを使った先の例に条件分岐を追加すると、次のようになります。
// PHPでのパターンマッチングの例
interface Shape {
public function getArea(): float;
public function getType(): string;
}
class Circle implements Shape {
// 以前と同じ
public function getType(): string {
return 'circle';
}
}
class Square implements Shape {
// 以前と同じ
public function getType(): string {
return 'square';
}
}
class Rectangle implements Shape {
// 以前と同じ
public function getType(): string {
return 'rectangle';
}
}
function printArea(Shape $shape) {
switch ($shape->getType()) {
case 'circle':
// 円の面積の計算
break;
case 'square':
// 正方形の面積の計算
break;
case 'rectangle':
// 長方形の面積の計算
break;
}
echo "Area: " . $shape->getArea() . "\n";
}
$circle = new Circle(10);
printArea($circle);
Scala
Scalaでは、match
文を使ってパターンマッチングを行います。sealed trait
とケースクラスを使用した先の例にパターンマッチングを加えると次のようになります。
// Scalaでのパターンマッチングの例
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Square(sideLength: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
// シェイプの種類に応じて面積を計算する関数
def getArea(shape: Shape): Double = shape match {
case Circle(radius) => Math.PI * radius * radius
case Square(sideLength) => sideLength * sideLength
case Rectangle(width, height) => width * height
}
val circle = Circle(10)
println(s"Area: ${getArea(circle)}")
まとめ
PHPとScalaにおける直積型と直和型の実装方法には、顕著な違いがあります。
PHPはオブジェクト指向と動的型付けを中心にした言語であり、直積型はクラスを通じて、直和型はインターフェースとクラスの継承によって模倣されます。
一方、Scalaは関数型の特性と強力な型システムを持つ言語で、直積型はケースクラスを使って表現され、直和型はsealed traitとケースクラスによって実現されます。
Scalaの型システムは型の完全性と不変性を保証し、パターンマッチングを容易にする一方で、PHPはより柔軟な型の扱いとオブジェクト指向の特性に重点を置いています。
その結果、Scalaは型安全性と関数型プログラミングの利点を享受するのに対し、PHPはオブジェクト指向設計と動的型付けの利便性を提供していることが分かりました。
Discussion