読者です 読者をやめる 読者になる 読者になる

俺です

クソザコなりにあがいてるRubyist

Rubyで構造体を使った型のあるプログラミングをしてみる

この記事はFUN Advent Calendar 12/13日の記事です.

www.adventar.org

書き手

はじめに

こんにちは. 今回はRubyで形のあるプログラミングをしてみようと思います.
しかし、Rubyは動的型付け言語であり型はありません. ですので今回はGemを使って一般的な静的型付けにあるような型を使ってみようとするものです.
※注意
この内容で登場してくる型はあくまでもGemを使って行うものであり、Ruby3で実装される予定のある型とは大きく異なります.

構造体とは

構造体(こうぞうたい、英: structure)はプログラミング言語におけるデータ型の一つで、1つもしくは複数の値をまとめて格納できる型。それぞれのメンバー(フィールド)は型が異なっていてもよい点が配列と異なる。

Wikipediaは申しています. 構造体っていうとC言語のイメージがとても強いのでCでちょっとした復習を.

Cでよくある構造体

#include <stdio.h>

struct grade{ /* Define structure */
    int id;
    int grade; 
    char name[30];
};
int main(void){
    int i;
    /* Define structure var and structure array */
    struct grade students[20];
     
    /* Input data*/
    for(i=0;i<20;i++){
        scanf("%d", &students[i].id);
        scanf("%s", students[i].name);
        scanf("%d", &students[i].grade);
    }
    /* Check input data */
    for(i=0;i<20;i++){
        printf("ID:%d NAME:%s GRADE:%f\n", students[i].id, students[i].name, students[i].grade);
    }
    return 0;
}

まぁ, よくあるC言語の入門書とかにありそうですよね. 復習なので.

RubyのStructクラス

やっとタイトルのRubyが出てきました.
僕は普段メインでRuby触ってるんですが, Rubyにも構造体としてStructクラスあるんです. 若干影薄い気もするけど.
Structクラスはサブクラスを新たに生成します. 構造体サブクラスではメンバに対するアクセスメソッドが定義されています.

第一引数がStringクラスの場合の使用例

human=Struct.new("Human", :name, :sex, :age, :address)
adam=human.new("adam", "man",15,"Hakodate,Japan")
adam.sex="woman"
puts adam

f:id:chikuwa_it:20161213131016p:plain

このように第一引数がStringクラスの場合はそれがクラス名になります. よって小文字から始まることはできません.

第一引数がSymbolクラスの場合の使用例

Hoge = Struct.new(:foo, :bar, :hogehoge)
p Hoge

f:id:chikuwa_it:20161213220909p:plain

第一引数がSymbolクラスの場合, 生成した構造体クラスは名前の無いクラスとなります. また最初に代入された定数名がそのままクラス名になります.

ブロック指定

human=Struct.new("Human", :name, :sex, :age, :address)do
    def hello
        "hey,#{name}"
    end
end
human.new("Andy","man",20,"London,UK").hello

f:id:chikuwa_it:20161213223904p:plain 上記のようにブロックを評価される事ができます. また, 定義したStructはブロックパラメータにも引き渡されます.

しかしながら, あくまで動的型付け言語での構造体です. やっぱり型はありません.
今回の目的はRubyで表題通り型のあるプログラミングをしてみることです. そこで今回はTypeStructというgemを導入してやってみることにします.

github.com

TypeStructを使う

以下のように定義することで型を付けれるようになります.

require "type_struct"
Human=TypeStruct.new(
    name: String,
    sex: String,
    age: Integer,
    address: String,
)

f:id:chikuwa_it:20161213233915p:plain

また、通常のStructとほぼ同じようなやり方でインスタンスを生成することができます.

require "type_struct"
Human=TypeStruct.new(
    name: String,
    sex: String,
    age: Integer,
    address: String,
)
michael=Human.new(
    name: "michael",
    sex: "man",
    age: 48,
    address: "California,USA",
)
puts michael

f:id:chikuwa_it:20161213234641p:plain

例外処理

require "type_struct"
Human=TypeStruct.new(
    name: String,
    sex: String,
    age: Integer,
    address: String,
)
asupe=Human.new(
    name: "michael",
    sex: 114514#TypeError
    age: 48,
    address: "California,USA",
)

f:id:chikuwa_it:20161214003207p:plain 型定義に則ってない値でインスタンス化をするとTypeStruct::MultiTypeErrorというエラーを返してくれます.
このGemの良いところは動的型付け言語ありながら, 型チェックをしてくれるということです.
これで想定外のエラーを検知することができます. いいね.

まとめ

動的型付け言語はバグを見つけることが難しいと言われてたりします.
こういう風に実行時に定義に則ってないって怒られることでちょっと幸せになります.
TypeStructにはよく出来たサブクラスであるArrayOf,HashOf,Interfaceなどがあります.
また、StructはHashより若干高速らしいです. もしStructを使う機会があれば是非お試しあれ.

明日の記事は 西崎芽依 (@Tkon_sec) | Twitter です.