Kuina-chan

くいなちゃん2018年10月19日


プログラミング言語Kuin」の逆引き辞典4、列挙型とクラスとエイリアスの扱い方についてです。

目次

列挙型を扱う

列挙型を作る

列挙型とは、いくつかの取り得る値を定めた型のことで、例えば「dog」「cat」「fox」のいずれかの値になる「Animal」型といったものを作ることができます。
列挙型を作成するには「enum 型名」から「end enum」の間に取り得る値の名前を書いていきます(列挙型)。
enum Animal
  dog
  cat
  fox
end enum
  
func main()
  var animal: @Animal
  do animal :: %dog
  var isCat: bool :: animal = %cat {false}
end func
列挙型
1~4行目で列挙型「@Animal」を宣言することで、8行目のように型として扱うことができます。
9~10行目のように「%値の名前」と書くと、列挙型の値として解釈されます。
どの列挙型の値なのかは型を推論して決定されますが、型が決定できないときにはコンパイルエラーとなり、その場合には明示的に「%値の名前 $ 列挙型の名前」とキャストして示す必要があります(型の明示)。
enum Animal
  dog
  cat
  fox
end enum
  
func main()
  var array: []@Animal :: [%dog $ @Animal, %fox] {[%dog, %fox]}
end func
型の明示

列挙型の値に数値を持たせる

列挙型の各値にint型の数値を持たせることができます。 「列挙型の値 :: int型の値」と書きます(列挙型の値の数値)。
enum Animal
  dog
  cat :: 5
  fox
end enum
  
func main()
  var animal: @Animal
  var n: int
  do animal :: %dog
  do n :: animal $ int {0}
  do animal :: %cat
  do n :: animal $ int {5}
  do animal :: %fox
  do n :: animal $ int {6}
end func
列挙型の値の数値
3行目で、列挙型の値「cat」にint型の値「5」を設定しています。 このように値を設定しなかった場合にも、自動でint型の値が割り当てられ、「0」から順に「1」「2」…と1ずつ増やしながら値が設定されます。
数値を明示した場合には、その次の要素はその数値から1ずつ増やしながら設定されるため、上記のプログラムでは結果的に「dog」は「0」、「cat」は「5」、「fox」は「6」と割り当てられます。
列挙型の中で割り当てられる数値が重複してはならず、コンパイルエラーとなります。 また割り当てた数値は11行目のようにint型にキャストすることで取り出せます。
列挙型の値は大小の比較が可能です。 大小関係は、割り当てられた数値の大小に基づいて比較されます(列挙型の大小)。
enum Animal
  dog
  cat :: 5
  fox
end enum
  
func main()
  var animal1: @Animal :: %dog
  var animal2: @Animal :: %fox
  var b: bool :: animal1 < animal2 {true}
end func
列挙型の大小

列挙型の数値でビット演算を行う

列挙型に割り当てられた数値は、bit64型と同様にビット演算を行うことができ、要素に包含関係を持たせることができます。 「.or」「.and」「.not」「.xor」メソッドが使えます(列挙型のビット演算)。
enum Color
  red :: 0x01
  green :: 0x02
  blue :: 0x04
end enum
  
func main()
  var red: @Color :: %red
  var yellow: @Color :: red.or(%green)
  var isGreen: bool :: yellow.and(%green) = %green {true}
end func
列挙型のビット演算
この例では、9行目で「%red」と「%green」の和集合(ビット和)を「%yellow」と定義し、10行目で「%yellow」に「%green」が含まれるかどうかを共通部分(ビット積)で判定しています。

クラスを扱う

クラスを作る

クラスとは、複数の変数や関数を1つにまとめて、新しく型を作ったものです。
クラスを作成するには「class 型名()」から「end class」の間に変数や関数などの要素を書いていきます(クラス)。
class Person()
  +var name: []char
  var age: int
  
  +func setAge(n: int)
    do me.age :: n
  end func
end class
  
func main()
  var person: @Person :: #@Person
  do person.name :: "Kuina"
  do person.setAge(6)
end func
クラス
1~8行目でクラス「@Person」を宣言することで、11行目のように型として扱うことができます。 11行目のように「#クラス名」と書くと、そのクラスのインスタンスが生成されます。
12~13行目のように「.メンバ名」と書くことで、クラスが持つ変数や関数にアクセスできます。
2行目や5行目のように、クラスの変数と関数の定義の先頭に「+」を書くと、その変数や関数は12~13行目のように外部からアクセスできます。 3行目の「age」のように「+」を書かなければ外部からアクセスできず、「person.age」とアクセスしてもコンパイルエラーが発生します。 必要最小限の要素を外部に公開することで、予期しない変数の書き換えを防ぐことができます。
クラスのメンバ関数内で自身のインスタンスを参照するには、6行目のように「me」と書きます。 13行目の「person.setAge」を呼び出すと、6行目の「me.age :: n」が実行され、結果的に「person.age」に値が代入されます。

クラスを継承する

あるクラスの変数や関数を引き継いで(継承)、新しいクラスを作成することができます。 継承してクラスを作成するには「class 型名(継承元クラス)」と書きます(クラスの継承)。
class Person()
  +var name: []char
  var age: int
  
  +func setAge(n: int)
    do me.age :: n
  end func
end class
  
class Student(@Person)
  +var mathScore: int
  
  +*func setAge(n: int)
    do super(me, n)
    do me.age :+ 1
  end func
end class
  
func main()
  var student: @Student :: #@Student
  do student.name :: "Kuina"
  do student.setAge(6)
  do student.mathScore :: 100
  var person: @Person :: student
  if(person =$ @Student) {true}
    var student2: @Student :: person $ @Student
  end if
end func
クラスの継承
10行目では、「@Person」クラスを継承して「@Student」クラスを作成しています。 「@Student」クラスは「@Person」のすべての変数や関数を引き継ぎ、21~22行目のようにアクセスできます。 また11行目で「@Student」に追加した変数にも23行目のようにアクセスできます。
また24行目のように、継承したクラスのインスタンス「@Student」は継承元クラスの型「@Person」とみなして扱うことができます。 逆に26行目のように、継承元クラスの型「@Person」から継承先クラスの型「@Student」に変換するときには明示的なキャストが必要で、本来のインスタンスではない型にキャストすると例外が発生します。
インスタンスがあるクラス型にキャスト可能かどうかは、25行目のように「=$」演算子で判定できます。
継承するときに、継承元の関数の処理を上書きすることができます(関数のオーバーライド)。 関数をオーバーライドするには13行目のようにfuncの直前に「*」を書き、オーバーライド元と同じ関数名、引数、戻り値にします。 関数内でオーバーライド元の関数を呼び出すこともでき、14行目のように「super(me, 第1引数, 第2引数, ...)」と書きます。 戻り値もあれば受け取れます。
継承元で「+」を付けなかった要素にも、継承先からはアクセスすることができます。 15行目のように「me.age」とアクセスしても問題ありません。

クラスのインスタンス生成時に処理を行う

クラスのインスタンスが生成されると、すべてのクラスの継承元クラスである「kuin@Class」に定義されている「ctor」という関数が呼ばれます。 これをオーバーライドすることで、インスタンス生成時に処理が行われるようにできます(コンストラクタ)。
class Person()
  *func ctor()
    do me.name :: "Kuina"
    do me.age :: 6
  end func
  
  +var name: []char
  var age: int
end class
  
func main()
  var person: @Person :: #@Person
end func
コンストラクタ
2~5行目で「ctor」関数をオーバーライドして、各変数に値を代入しています。 このように書くことで、12行目でインスタンスが生成されたときに変数の初期値を設定することができます。

クラスを比較可能にする

クラスを「=」「<>」「<」「>」「<=」「>=」演算子や、配列の「.sort」メソッドなどで扱えるように比較可能にするには、「kuin@Class」に定義されている「cmp」関数をオーバーライドします(クラスの比較)。
class Person()
  +*func cmp(t: @Person): int
    if(me.name = t.name)
      ret 0
    else
      ret me.name > t.name ?(1, -1)
    end if
  end func
  
  +var name: []char
end class
  
func main()
  var person1: @Person :: #@Person
  do person1.name :: "abc"
  var person2: @Person :: #@Person
  do person2.name :: "def"
  var b: bool :: person1 < person2 {true}
end func
クラスの比較
「cmp」関数は、自身のインスタンスと引数の「t」を比べ、自身が大きければ正、小さければ負、等しければ0の値を返すようにします。 こうすることで、18行目のように大小比較ができます。

型に別名を付ける

型に別名を付ける

型に別名を付けるには「alias 新しい型名: 」と書きます(エイリアス)。
func main()
  alias Items: dict<int, [][]char>
  
  var items: Items :: #Items
end func
エイリアス
2行目で「dict<int, [][]char>」型に対して「Items」という名前を付け、4行目で使っています。
このように、複雑な型に別名を付けることでソースコードを読みやすくできます。 ただし多用すると元の型が判りにくくなり、かえって読みづらくなるため、必要最小限の使用を推奨します。
1539953590jaf