AdditiveArithmetic とは
AdditiveArithmetic
は Swift 5 で新しく導入された protocol です(SE-0233)。
Swift には数に関する protocol がいくつもありますが、そのうちのひとつに Numeric protocol があります(参考:Numeric 関連の階層)。Numeric
は、数の性質のうち、たし算、ひき算、かけ算の3つの演算を定義する protocol です。
AdditiveArithmetic は Numeric
よりも条件が弱い protocol で、たし算とひき算の2つの演算を定義する protocol です。
Swift 5 での AdditiveArithmetic
導入にともない、Numeric
は AdditiveArithmetic
を継承してかけ算を追加する形になりました。つまり、Numeric 関連の階層において、Equatable
と Numeric
の間に AdditiveArithmetic
が入ったことになります。
余談:数学用語になじみがある方向けに補足します(なじみがない方は飛ばしてください)。上記の AdditiveArithmetic
は 群(加法群) に、Numeric
は 環 に対応する、と言われれば分かりやすいかと思います。
AdditiveArithmetic の定義
AdditiveArithmetic
protocol の定義は以下のようなものです。単純なたし算とひき算を定義しています。
protocol AdditiveArithmetic {
static var zero: Self
static func + (Self, Self) -> Self
static func += (Self, Self)
static func - (Self, Self) -> Self
static func -= (Self, Self)
}
標準の数値型である Int
や Float
は AdditiveArithmetic
に準拠しています。また、独自の型で AdditiveArithmetic
に準拠するものを作ることもできます。
この protocol の応用例として、ドキュメントには以下の例が記載されています。AdditiveArithmetic
型の要素を持つ Sequence
にメソッドを宣言しています。
extension Sequence where Element: AdditiveArithmetic {
func sum() -> Element {
return reduce(.zero, +)
}
}
Int
や Float
などの具体的な型を使う代わりに AdditiveArithmetic
を使うことで、sum()
は AdditiveArithmetic
に準拠したあらゆる型に対応できる汎用メソッドとなっています。
なお、これにかけ算を追加したものが Numeric
となります。
protocol Numeric : AdditiveArithmetic {
static func * (Self, Self) -> Self
static func *= (Self, Self)
}
AdditiveArithmetic が有益な場合
従来の Swift で、すでに Numeric
が存在していますし、それで足りるようにも思えます。なぜわざわざ Swift 5 で AdditiveArithmetic
を導入したのでしょうか。
それは、AdditiveArithmetic
に準拠するのは簡単だが Numeric
に準拠するのは難しい、というたぐいの型があるからです。つまり、たし算やひき算は問題なくできるが、かけ算に相当する演算がない、という型です。
具体的な例としては、ベクトルがそうです。ベクトル同士のたし算やひき算はあるので AdditiveArithmetic
に準拠するのは簡単です。しかし、ベクトル同士のかけ算に相当する演算はないため Numeric
に準拠するのは難しいです。そのため、AdditiveArithmetic
が導入されたほうが都合が良いのです。
具体例 : 3次元ベクトル型
AdditiveArithmetic
の実例を見るために、3次元ベクトルの型 Vector3D
を作ってみます。
struct Vector3D {
typealias Scalar = Float
var x: Scalar
var y: Scalar
var z: Scalar
}
これを、AdditiveArithmetic
に準拠させます。
extension Vector3D : AdditiveArithmetic {
static var zero = Vector3D(x: 0, y: 0, z: 0)
static func + (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return Vector3D(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z)
}
static func - (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return Vector3D(x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z)
}
static func += (lhs: inout Vector3D, rhs: Vector3D) {
lhs = lhs + rhs
}
static func -= (lhs: inout Vector3D, rhs: Vector3D) {
lhs = lhs - rhs
}
}
これで、前述した sum()
メソッドが Vector3D
に対して使えるようになります。
extension Sequence where Element: AdditiveArithmetic {
func sum() -> Element {
return reduce(.zero, +)
}
}
let sum = [Vector3D(x: 1, y: 2, z: 3),
Vector3D(x: 4, y: 3, z: 2),
Vector3D(x: -3, y: -3, z: -3)].sum()
if sum == Vector3D(x: 2, y: 2, z: 2) {
print("😄")
}
なお、ベクトルの演算といえばたし算ひき算以外にもスカラー倍演算がありますが、AdditiveArithmetic
の説明には関係がないのでここでは省略します。それを含めたコードは GitHub に置いています(usami-k/AdditiveArithmeticSample)。
AdditiveArithmetic のメソッドを別のメソッドで実装するパターン
AdditiveArithmetic
に準拠させる際の実装ですが、いくつかのメソッドは別のメソッドから実装できます。
上記の例でも、+
を使って +=
を実装しましたし、-
を使って -=
を実装しました。
逆に、+=
を先に実装して +
を実装することもできます。
static func += (lhs: inout Vector3D, rhs: Vector3D) {
lhs.x += rhs.x ; lhs.y += rhs.y ; lhs.z += rhs.z
}
static func + (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
var lhs = lhs ; lhs += rhs ; return lhs
}
また、次のような単項演算子 -
を定義しておくと、+
を使って二項演算子 -
を実装することもできます。
// 単項演算子
prefix static func - (v: Vector3D) -> Vector3D {
return Vector3D(x: -v.x, y: -v.y, z: -v.z)
}
// 二項演算子
static func - (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return lhs + (-rhs)
}
逆に、二項演算子 -
を先に実装して単項演算子 -
を実装することもできます。
// 二項演算子
static func - (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return Vector3D(x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z)
}
// 単項演算子
prefix static func - (v: Vector3D) -> Vector3D {
return .zero - v
}
最後に
AdditiveArithmetic
は、通常はあまり触れる機会はないかもしれません。しかし、上述の例で見たようにベクトル型を扱いやすくなるなどの利点があり、それが活用できる分野では役に立つこともありそうです。今後の展開が気になるところです。