型とは何か?

kotlinでは型が重要。けれど型とは何か、というのはなかなか初心者にわかりやすく説明されていない事が多い。 そこでここでは、プログラム初心者向けに型とはなにかという説明をしてみたい。

けれど、型とは何か、というのはなかなか説明が難しい。 そういった難しい説明を理解して習得するよりも、ある程度使っていって慣れてきて、なんとなく「こういうものか」と思うようになるのが型の正しい学び方だと思う。

そこでここではグダグダと説明を書くけれど、ある程度型を使うようになって「こういうものか」と思えるようになったら、ここに書いた説明は忘れてOKです。 あくまでそう思えるようになるまでの自転車の補助輪のようなものという事で。

以下いくつか、型とは何か、みたいな事を、比喩も交えて書いてみます。

空手の型よりはクッキーの型に近い

型、というと空手の型を思い浮かべる人も多いと思うけれど、それよりはクッキーの型とか型番の型に近い。

クッキーの型というのは星とかハートとかの型で、あれでくり抜いてその形のクッキーをどんどん作っていくようなもの。

型番というのは自転車とか車とかの型番とかの事で、あれもプログラムの型に近い。

プログラムとはデータとそれに対する処理である

プログラムを、以下の2つに分けて考える、という考え方がある。

  1. データ
  2. データに対する処理

プログラムとは「データとそれに対する処理である」と考える事が出来る。

データとは、1とか2とか”Hello World”とかそういったもの。

処理とは足したり引いたり連結したり空白で2つに分けたり、といった事。 このように考えた時のデータについての何かが型と言える。 以下見ていきましょう。

ゲームに例えると「対戦格闘」が型、「ストIIダッシュ」や「真サム」がデータ

ゲームの例を考えてみる。例えば「対戦格闘」などは型と言える。 一方で「ストIIダッシュ」とか「スパIIX」とか「ガロスペ」とか「真サム」などはデータと言える。

「RPG」なども型と言える。一方ドラクエIIIとかFF4などはデータと言える。

RPGとかシミュレーションとか対戦格闘とか、そういうのが型。

また、「カプコンのゲーム」と「SNKのゲーム」なども型と言える。 分類の仕方はいろいろありえて、解こうとしている問題に合わせて自分で分類を考えて作っていくのがプログラムの重要な一部だ。

型とはネジ穴とドライバーにおけるネジ穴の形のようなもの

ドライバーにプラスとマイナスがある。 これは、プラスのネジにマイナスのドライバーを使ってしまわないため、わざと形を変えてある。 型もこれに似ている。

プログラムの部品と部品を組み合わせる時に、形があっているもの同士しか組み合わせられないようにするための何かが型。 型があっていると部品同士はつながる。 型が違っているとつながらない。

そしてつながってはいけない部品同士が間違ってつながらないように、型をいろいろ作っていく。

先ほどのゲームの例に戻ると、「対戦格闘」なら「ダイアグラム」を作る事が出来る。 一方で「RPG」でダイアグラムを作る事は出来ない。

「RPG」なら「ダンジョンのマップ」を作る事が出来る。けれど「対戦格闘」の「ダンジョンのマップ」を作る事は出来ない。

こうした型ごとに出来る処理をいろいろ作る事が出来て、 それは型が同じデータに対してだけ行える。

以下のような処理を考えてみる。

fun main() { val a = 1 val b = 2 val c = a-b println("a-bは$c") val d = "abc" val e = "def" val f = d+e println("d+eは$f") }

aとbは数字なので、引き算が出来る。

一方、dとeは文字列なので足す事は出来るが、数字の足すとはちょっと意味が違う(文字をつなげる事になる)。 なにより、引く事は出来ない。d-eとは書けない(書いてみてエラーになるのを確かめよう!)。

型とはこのように、ある「処理」とつなげられるかどうかを表すための、間違ったものがつながらないようにちょっと特別な形をつける事である。 星型ドライバー、プラスドライバー、マイナスドライバーなどのようにわざと形を変える事で、ドライバーとネジの組み合わせがあっているものだけが回せるようにする。

変数には(ほとんど)すべてに型がある

変数には、(genericsの例外を除いて)全てに型があります。 genericsの話はこのシリーズでは多分しないので、全てに型がある、と思っていてよろしい。

変数を最初に定義するところで型が決まる。

以下のa, bを考えてみましょう。

val a = 1
val b = "abc"

このように最初にvalで変数を定義するところで、イコールの右の値から型が推測して決まります。

この場合aはIntという型、bはStringという型になります。 とりあえずIntとかStringが何か、というのは後述するとして、ここでは全ての変数に型というのが勝手についている、 という事が大切です。

なお、推測に任せずに自分で指定する事も出来ます。 これは以下のようにコロン(:)で変数の名前の後ろに書きます。

val a: Int = 1
val b: String = "abc"

この時点では「何を言っているんだお前は?」という感じだと思いますが、 関数が出てくるあたりでこの辺は意味が出てくるので、 今は「意味わからんな」、と思いながら進んでください。

型があった変数にしかデータは入れられない

「カプコンのゲーム」にストIIやエイプレは入れられるが、ガロスペや真サムは入れられない。 同様に、数字の型にしか数字は入れられず、文字列の型に数字を入れようとするとコンパイルエラーとなる。

変更する変数の場合はvarで定義するという話でした。 そこで以下のように書くと、これはコンパイルエラーになります。

fun main() { var a = 1 a = "abc" // コンパイルエラー! println(a) }

データにも型がある(と思って良い)

1と言ったらIntです。123もIntです。 "abc"と言ったら文字列です。

このように、データにも型があります。

上級者にとっては数字の即値の型というのは結構ややこしい話題なのだけれど、このシリーズで扱うレベルならデータには型がある、と思ってしまってOK。

最初に覚えておくべき型

ここらへんで、実際の型をいくつか見てみよう。 最初にプログラムをしてく段階で覚えていた方がいい型というのがいくつかある。

他の言語を知っている人は、Basic types - Kotlin Documentationなどで一気に学んでもいいのだけれど、 型というのを初めて見た人にはこれは多すぎる。 だいたいほとんど使わない。

という事で、最初の段階で覚えた方がいい型だけをとりあげて、それを簡単に説明します。

型の名前 説明
Int 数値のうち、整数の型
Double 数値のうち、小数も使える型
String 文字列
Boolean trueとfalseの2つのどちらかだけの型
TextView, Button, EditText, CheckBox 最初にやった奴

IntとDouble

数値には二種類あります。整数だけのIntと、小数も使えるDoubleです。 Doubleという名前にはほとんど意味は無くて、歴史的な事情でこういう名前になっている。

整数は全然数学やってないわ〜って人は忘れてるかもしれないので簡単に説明しておくと、 整数は整数を理解するよりも、小数から見る方がいい。

小数とは1.5とか2.7とかの小数点がある奴。 整数とは、小数では無い数。つまり1とか3とか2とかそういうの。 基本的にはこの2つに別々の型がある。

小数も使えるDoubleと小数は使えないIntがあるなら、全部Doubleでいいのでは? と思うかもしれないけれど、 CPUというのは小数を扱うのがめっちゃ下手なので、プログラムは出来るだけIntを使う方がいい。 という事で不要ならIntを使おう。

けれど、みんなで割り勘する時のお金を計算する、 とか、一週間の体重の平均を計算する、とか、 消費税の計算をする、とか、そういう時は小数も使いたくなるので、 Doubleも知っておいた方がいい。

なお、1とか123はIntだけど、1.5とか1.0とか123.0などはDoubleになります。 11.0が別の型になる、というのは慣れてない人には驚くかもしれないけれど、そういうものです。

以下、この違いが分かる例を見てみよう。

fun main() { println(5/2) // これはIntなので2.5じゃなくて切り捨てて2になる println(5.0/2.0) // これはDoubleなので2.5になる }

Stringと文字列

プログラム初心者が割とつまづく概念に文字列がある。 文字列とはようするに文字の列、つまり単なるテキストなのだけれど、 こればっかりは慣れるしか無いので、以下見ていく。

"abc"とか"ほげほげ"とか、ダブルクオートで囲んだテキストはString、という型になる。 Stringはprintlnで表示する事が出来る。

fun main() { val mojiretu = "くにへかえるんだな おまえにもかぞくがいるだろう" println(mojiretu) }

上のコードで、mojiretu変数はString型となる。

Boolean型

Booleanとはtrueとfalseだけの型です。 そんな型なんて大して使わ無さそうに思うけれど、比較などの条件などの結果が全部これになります。

fun main() { val result = ("ほげ" == "いか") println(result) }

このresultはBoolean型となります。 ==の他に>とかもBoolean型になります、 辞書の中に要素があるか、などを調べる関数もBoolean型となります。

関数の話はあとでするので良く分からなくてもいいですが、とにかくif文の条件になるようなものは全部Boolean型になる、という事から、 Boolean型は結構出てきます。

TextView, Button, EditTextなどの型

findViewByIdで渡していたこれらは、全て型です。 現時点では「これらも型だ」という事だけ頭の片隅に入れておけばOKです。

とりあえず型を使うシチュエーション

型は至るところで使われているけれど、このシリーズをやっている初心者が初期の段階で意識しないといけない場所はそんなに多くない。

現時点では、以下の三つを意識しておけば十分じゃないか。

  1. 変数の宣言
  2. コレクション(MapとかList)を使う時
  3. findViewById

これにプラスして関数が出てきたら関数のところでも型を使う必要が出てくるが、それは関数が出てきた時に考えればいい。

ということで上の三つを簡単に見ていく。

変数の宣言で型を使う

前述の「変数には(ほとんど)すべてに型がある」でも言ったように、変数を作る時には型が決まる。 以下のaはIntで、bはStringの型になるのだった。

val a = 1
val b = "abc"

このように変数を作った時に値も入れるケースでは、型を書く必要は基本的には無い。 けれど、変数を作ったけどその場では値を入れない、というケースが(慣れてくるとほとんど無いのだが)勉強の過程ではちょくちょくある。

例えば以下のようなコードを考える。

fun main() { // 変数を作るだけで値を入れない val d: Int if ("ほげ"=="いか") { d = 1 } else { d = 2 } println(d) }

このように、dという変数は作るが値は後で入れる、という場合には、作る時に型を指定する必要がある。

なお、このdは一回しか値をセットしていないのでvalで良い(varにしなくて良い)。 これはややこしいところなので、最初のうちは気にしなくて良い。

ここでの問題は、こういう風に変数を作る時に値がわからないケースでは型を書く必要がある、ということだ。 あんまり無いけどたまにあるので軽く見てみた。

コレクション(MapとかList)を使う時

これも先ほどと同様に、変数を作る時に値をセットしているとあまり見る機会は無い。

fun main() { val items = listOf("ひとつ!", "ふたーつ!", "みっつ!") println(items[1]) }

これでitemsはList<String>型になる。

ただ、からっぽのリストを作って、後からアイテムを追加していくようなケースでは最初に型を指定する必要が出てくる。

fun main() { // 右辺ではなんのリストかわからないので<String>と指定してやる必要がある val mlist = mutableListOf<String>() // 追加する、これがユーザーの入力とかで後からくるケースを想像してくれ。 mlist.add("ひとつ!") mlist.add("ふたつ!") mlist.add("みっつ!") println(mlist) }

mutableListってなんなのか、とかは後で説明するけれど、とにかくこのように最初に何かからっぽのコレクションを作って、後から要素を追加していく、 という場合には、最初はからっぽなので型がわからない。だからプログラマが指定してやらないといけない。

これも後で説明するので、この時点では、とりあえずコレクションを使う時は型を使う場合があるんだな、と思っておいてくれ。

findViewByIdの時

これはもう散々やってきた。 例えば以下みたいなコード。

findViewById<Button>(R.id.okButton).setOnClickListener {}

この<Button>のところのButtonが型になる。 idと型を指定するのは最初のうちはなんだか無駄に二回同じようなことをやらされていると思うけれど、 もう少し慣れてくるとこの二つの意味合いはわかってくると思う。

そしてこのButtonとかが型だ、ということが分かってくると、ドキュメントとかの調べ方が分かってくるので、将来的にはわかった方がいい。

この時点では<>の中には型を指定しているらしいぞ、くらいの理解でいいです。

文字列と数字をいったりきたりする

突然ですがクイズです。 "1"+"2"はいくつでしょうか? 以下を実行する前に答えを考えてから実行してみてください。

fun main() { val s = "1"+"2" println(s) }

予想通りでしたか?正解は”12”です。”3”じゃありません。

では1+2はいくつでしょうか?前とどこが違うか分かりますか?

fun main() { val s = 1+2 println(s) }

今度は3になりました。違いは何か、というと、前者はString、後者はIntという事になります。

数字の「+」は足し算を意味しますが、文字列の「+」は連結、つまり二つの文字列をつなげるという意味になります。 これはJS入門の方でも少し話したとは思いますが、この間を行ったり来たりするのはJavascriptよりkotlinの方が意識的に行う必要があるので、 ここで少し練習しておきましょう。

文字列からIntにするのは.toInt()

String型の変数からIntが欲しい事があります。 そういう時は.toIntというのを呼びます。

例えばさきほどの文字列の「+」の例で、「+」をしているところで.toInt()を呼ぶと以下のようになります。

fun main() { val s1 = "1" val s2 = "2" println(s1.toInt()+s2.toInt()) }

つまり数字として足す訳ですね。 以下のようにも出来ます。

fun main() { val s1 = "1" val s2 = "2" // 新しい変数にする val n1 = s1.toInt() val n2 = s2.toInt() println(n1+n2) }
fun main() { // ダブルクオートの後に直接.toInt()してs1とs2をIntにしてしまう val s1 = "1".toInt() val s2 = "2".toInt() println(s1+s2) }

以下、少し自分でも同じように書いてみましょう。

課題: 以下のTODO以下の二行を変更して3になるようにせよ

fun main() { val s1 = "1" val s2 = "2" // TODO:以下の二行を変更 val n1 = s1 val n2 = s2 println(n1+n2) }

課題: 以下のTODO以下の二行を変更して3になるようにせよ

fun main() { val hoge = "1" val ika = "2" // TODO:以下の二行を変更 val n1 = hoge val n2 = ika println(n1+n2) }

課題: 以下のTODO以下の二行を変更して3になるようにせよ

ダブルクオートを消す、とかはしないでtoIntを使ってください。

fun main() { // TODO:以下の二行を変更 val n1 = "1" val n2 = "2" println(n1+n2) }

Intから文字列にするのは.toString()

逆方向、つまりIntから文字列にする方は、あとで説明するstring templateという機能があるのであまり出番は無いかもしれませんが、 一応.toString()で出来ます。

fun main() { val n1 = 1 val n2 = 2 println(n1.toString()+n2.toString()) }

TextViewなどのtextはほとんどStringだけど微妙に違う

初期の頃にやっていた、以下のようなコードなどで、

findViewById<TextView>(R.id.label1).text = "Hello"

textはStringを入れる事は出来るのだけれど、微妙にString型と違う。 だから、以下のsはString型では無い。

val s = findViewById<TextView>(R.id.label1).text

CharSequenceという型になるんですが、この名前は覚えなくていいです。 それよりも、String型を作るにはtoString()しないといけない、というのが今後重要になります。

val s = findViewById<TextView>(R.id.label1).text.toString()

たとえばlabel1の内容をIntにしたいなら、こうしないといけない。

val s = findViewById<TextView>(R.id.label1).text.toString()
val n = s.toInt()

慣れてくれば1行でこう書いてもいい。

val n = findViewById<TextView>(R.id.label1).text.toString().toInt()

.toString().toInt() ってなんか間違ってそうですがこれで正解です。

この辺の話はあとで「簡単な電卓を作ろう」のあたりまで行くと何言っているか分かると思うので、 この時点では「ふーん」と思って先に進んでしまってOKです。

まとめ

型ってのがあって、コレクションのところで必要になるらしい。

静的型付けいいかどうか論争
kotlinには型がある、より正しくは静的型付けを行う言語である、となります。 一方でJavaScriptやPythonなどは型が無い言語、より正確には静的型付けを行わない言語となります。

この二つのどっちがいいか、というのは、 プログラムの素人でも知ったかぶりをしやすいトピックであり、 またどちらかが正しいという訳でも無いので何も分かってない奴が好きな方を持ち上げても間違いじゃない事が多いので、 好きな事を言っても分かってる人に間違いを指摘されて恥をかくリスクも少なく、 素人がしったかぶりするのにとても適したトピックと言えます。

そういう訳で、定期的に何も分かってない人同士が、 型がある方がいいか無い方がいいかについてtwitterなどで論争をしています(そしてベテランもたまに釣られている…お前ら何回同じ話ししてるの……)。

本当に20年くらいずっと同じ話をしてます。やばい。