Kotlinでも代数学 (1)

はじめに

この記事はSwiftで代数学入門シリーズに影響を受けて書かれました。

元記事のその1-その3くらいの内容相当、つまり有理数体を構成するまで、になっております。

前提となる知識

群だの環だのが少し分かっていれば大丈夫です。わたしもそれほどよくわかっているわけではありませんし気にせずに行きましょう。

元記事では丁寧に解説されていますので、不安なら目を通しておくのもいいかもしれません。

この記事には曖昧だったり不正確だったり、あるいは完全に間違った記述があるかもしれませんきっとある。見つけたらこっそり教えてください。

kotlin?

ここのところ人気の出始めている(たぶん)プログラミング言語です。

Kotlin Programming Language

AndroidデベロッパにはおなじみのAndroid Studioや、人気IDEであるところのIntelliJ IDEAだったりPhpStormだったりでおなじみのJetBrainsが開発しています。

Androidでの開発に利用できることもあって、iOS開発におけるObjective-Cに対するSwiftのような立場とも言えるでしょう。

実装しよう

では早速コードを書いていきましょう。

モノイド(Monoid)

集合\(S\)とその上の演算\(\circ: S \times S \mapsto S\)の組\(\left(S, \circ\right)\)について、

  1. 結合律をみたす: \( \left(a \circ b \right) \circ c = a \circ \left(b \circ c\right)\)となる
  2. 単位元が存在する: ある元\(e \in S \)が存在して、任意の\(a \in S\)に対して\(a \circ e = e \circ a = a\)となる

とき、この\(S\)をモノイドというのでした。

コードで書くとこう。

interface Monoid<T> {
    operator fun times(other: T): T
    val identity: T
}

演算をここではtimes() つまり\( \times \)で定義し、単位元を表すプロパティとしてidentityを持たせるようにしました。

他はMonoid<T> とgenericにしているのがミソですね。この型パラメータを持たせない場合、operator fun times(other: Monoid): Monoidなどとなって継承側でキャストが必要になり煩雑です(とはいえスマートキャストのおかげでそれほど大変ではないけど気持ちは悪い)。

結合律をみたすことを保証できないのですが、思い切ってオミットします(それをモノイドと呼んでいいのか……)。

この先もこういう感じの雑なアレが続きます

群 (Group)

モノイド\( G \)にさらに

  1. 逆元が存在する: 任意の元\( a \in G \)に対して\( a \circ b = b \circ a = e \)となる\( b \in G \)が存在する

とき、この\( G \) をと呼ぶのでした。ちなみに\( a \)の逆元は\( a^{-1} \)と書かれることが多いです。

コードで書くとこうです。ドン。

interface Group<T>: Monoid<T> {
  val inverse: T
}

Group はMonoid を継承し、新たなプロパティinverse を持たせただけです。ちょう簡単ですね。

加法群(Additive Group)

加法群は当然ながらです。この後に出てくるには演算が2つ出てくるのですが、一方を他方と区別するためにわざわざ(?)名前がつけられています。

群に対して、演算\( \circ \)を\( + \)に、ある元\(a\)の逆元\(a^{-1}\)を\(-a\)と表すことにしましょう。さらに単位元\(e\)を\(0\)で表します(これは零元と呼ばれます)。そうして構成された群を加法群と呼びましょう。

コードで書くとこう。

interface AdditiveGroup<T> {
  operator fun plus(other: T): T
  operator fun unaryMinus(): T
  val zero: T
}

operator fun<T: AdditiveGroup<T>> T.minus(other: T) = this + (-other)

Group の各演算、プロパティを対応することばで書き換えただけですね。

加算plus() と(加算についての)逆元unaryMinus() から減算minus() が自然と導かれますね。

環(Ring)

集合\( R \)とその上の演算\(+: R \times R \mapsto R \)(加法という)および\(\times: R \times R \mapsto R\)(乗法という)の組\(\left(R, +, \times \right)\)について

  1. \(R\)は加法について可換群をなす
  2. \(R\)は乗法についてモノイドをなす
  3. 分配法則をみたす:
    1. \(\left(a+b\right)\times c = a\times c + b\times c\)
    2. \(a\times\left(b+c\right) = a\times b + a\times c\)

ときこの\(R\)をというのでした。

 

コードで書くと……

interface Ring<T>: Monoid<T>, AdditiveGroup<T> {}

加法はAdditiveGroup から、乗法はMonoid からそれぞれ引き継ぐことで、もう必要なものは揃いました。(えっ分配法則?)

 

さて、これで整数環\( \mathbb{Z} \)が構成できるようになりました。

こうです。

typealias Z = Int
val Z.zero: Z
  get() = 0

このようにKotlinでもInt を乗っ取って整数環を構成できます(この場合、分配法則はKotlinがその成立を保証してくれます)。

 

……たしかに見た目だけならSwift版のそれとほぼ同じですが、実態はかなり異なるでしょう。例えば、val Z.zero: Z と書くとそれはインスタンスのプロパティなので、

val x: Z = 42
x.zero // ナニコレ???

などというコードが書けてしまいます(extensionでコンパニオンオブジェクトをぶっこんだりできるんでしたっけ?)。

 

しかしながら、今回は整数環さんの出番は(もう)ありませんから、忘れます。

体(Field)

ある集合\(K\)とその上の演算\(+: K \times K \mapsto K\)、および\(\times: K \times K \mapsto K\)の組\(\left(K, +, \times\right)\)について

  1. \(K\)は加法、乗法について環をなす
  2. \(K – \{ 0 \}\)は乗法について群をなす

とき\(K\)を体というのでした。\(K – \{0\}\)とは\(K\)から零元を取り除いた残りの集合を意味します。

 

難しいことを言ってるようですが、コードを見ればなんとなくわかるでしょうか。

たったのこれだけなんですけど。

interface Field<T>: Group<T>, Ring<T> {}
operator fun<T: Field<T>> T.div(other: T) = this * other.inverse

群から引き継いだ乗法と逆元から除算div() が導出できます。

有理数体

さて、ここまでで体を構成するのに必要なinterface が出来上がりました。これを使って有理数体を作りましょう。

有理数というのはつまり分数のことで、2つの整数\(p, q \in \mathbb{Z}\)をもって\(\frac{p}{q}\)と表すことができるのでした。ただし\(q=0\)となることはできません。

1つの有理数を表すのに2つの整数が必要なことから、有理数型Q はこんな風に書けそうです。

class Q(val p: Z, val q: Z = 1) {
//...
}

これにさらにさきほど作ったField を組み合わせることで演算やプロパティを要請し、有理数体\(\mathbb{Q}\)へ仕立て上げよう!というわけです。

 

では(細かいことは省略して)有理数体くんの登場です。
はいドン。

class Q(val p: Z, val q: Z = 1): Field<Q> {
  override fun equals(other: Any?) = when(other) {
    is Q -> (this.p * other.q) == (this.q * other.p)
    else -> false
  }

  override operator fun plus(other: Q) = Q(this.p * other.q + this.q * other.p, this.q * other.q)
  override operator fun unaryMinus() = Q(-this.p, this.q)
  override operator fun times(other: Q) = Q(this.p * other.p, this.q * other.q)

  override val inverse
	 	 get() = Q(this.q, this.p)

  override val identity
		get() = Q(1)

  override val zero
	 	get() = Q(0, 1)

  override fun toString() = when(this.q) {
    1 -> "${this.p}"
    else -> "${this.p} / ${this.q}"
  }
}

Field<Q> を継承することで、要請されるメソッドの引数や戻り値の型をQ で扱うことができるようになります。ここには加算plus() 、単項マイナスunaryMinus() 、乗算times() しかありませんが、減算、除算はそれぞれAdditiveGroup<Q> 、Field<Q> に定義されたものが使えますね。

ところでKotlinにはSwiftのIntegerLiteralConvertible 的なものはないのでしょうか……何かいい方法がないか、考えておきます。

 

さて、それではうまくいっているか確認してみましょう。

  val a = Q(2, 3) // 2/3のつもり
  val b = Q(4, 5) // 以下同
  val c = Q(6, 5)

  println(a+b) //=> 22 / 15
  println(a-b) //=> -2 / 15
  println(a*b) //=> 8 / 15
  println(a/b) //=> 10 / 12
  println(a==b) //=> false
  println(a!=b) //=> true
  println(a*c==b) //=> true
  println(a+b*c) //=> 122 / 75

よさそうですね(約分?そんなものはない)

次回予告

  • 有理数体くんのq に0を食わせるフトドキモノが現れてもいいように、\(p \in \mathbb{Z} \)、\(q \in \mathbb{Z}^{+} \)とかしよう!
  • 有理数体くん、ちょっと頭弱いみたいになってるので約分してほしい!
  • それが終わったらいよいよ有限体、そして多項式環、代数拡大までいこう!

では次回!