Haskell

仕組みから学ぶHaskell入門 その1:基礎文法

このテキストはシリーズになっています。(なる予定です)

  • 仕組みから学ぶHaskell入門 その1:基礎文法 (本テキスト)
  • 仕組みから学ぶHaskell入門 その2:関数と評価戦略(制作中)

このテキストの対象読者

  • Haskellの名前くらいは聞いたことがある人
  • Haskellを実用で使いたいというより仕組みをよく知りたい人
  • 他のプログラミング言語を使ってプログラムを組んだことがある人

このテキストでは、実用的な内容にはあまり触れません。
普通にHaskellでプログラムを書きたいのであればもっと別の入門を参照するべきでしょう。

内容の正確性について

このテキストはHaskellを学び始めて間もない人間が書いているので間違いを含んでいる可能性が多分にあります。
内容に瑕疵等がありましたらコメント欄でお知らせください。

また、仕組みを説明するといってもコンパイラの仕組みを説明するわけではないので注意してください。
(あくまで、他の入門ではブラックボックスなまま話が進んでいくところをなるべく説明していくということです)

実行環境について

基本的にここのコードは「GHC」というコンパイラで動作を確認しています。
インストール方法は各自で調べてください。

手軽に実行してみたい場合には「Wandbox」を利用されるとよいでしょう。
(Wandboxのページの左上のドロップダウンボックスからHaskellを選べます)





以下、本文です。

まずは雰囲気をつかんでみよう

とりあえず、まずはHello,Worldを見てみましょう。
(基本的にこのテキストに出てくるプログラムはコンソール上で実行します。)

hello.hs
main = do
    print "Hello, World!"
結果
Hello, World!

Haskellでは「main = do」以下に書いた文が実行されます。
print [値]」という文は値をコンソールに出力する文です。

(厳密にいうとprint文を実行しているわけではないのです…実は。
 とりあえず「printの後ろに値を書くとその値が出力される」と捉えておいてください。
 今は仕組みを説明するには少々早すぎるのです…)

ちなみに、printは複数書くこともできます。
main = do」以下の文はインデントをそろえる必要があることに注意してください。

multiprint.hs
main = do
    print "First Line"
    print "Second Line"
結果
First Line
Second Line

しばらくは、この「main = do ~」以下のコードの詳しい仕組みは置いておいて、値を見るための道具として使っていくことにします。

変数と束縛

Haskellでは、変数を以下のように宣言します。

varbind.hs
a :: Int --型注釈:「a は Int型 である」
b :: Int --型注釈:「b は Int型 である」

a = 1     --束縛:「a は 1 である」
b = 1 + 2 --束縛:「b は 1+2 である」

main = do
    print a
    print b
結果
1
3

--はコメントです

まず、はじめの二行を見てみます。

a :: Int --型注釈:「a は Int型 である」
b :: Int --型注釈:「b は Int型 である」

一行目の「a :: Int」は、「aという変数はInt型である」ということを意味しています。
Int型というのは整数を表す型のことです。
このように「[変数名] :: [型]」と書いて変数の型を指定することを型注釈といいます。
変数を使うときはまず型注釈をします。

次に、変数に値を設定するところです

a = 1     --束縛:「a は 1 である」
b = 1 + 2 --束縛:「b は 1+2 である」

一行目の「a = 1」は、「aという変数は1という値を表す」という意味で、
二行目の「b = 1 + 2」は、「bという変数は1 + 2という式を表す」という意味です。
このように「[変数名] = [値or式]」と書いて変数に値や式を設定することを「変数値(or式)束縛する」といいます。
なぜ「束縛」かというと、Haskellでは変数の値を変更することができないからです。

※2017/11/15修正について(赤字部分)
「束縛」というのは「変数を値に結び付けること(変数は本来変化するはずだが、それを一つの値に固定してしまうこと)」を指すので、「変数を値に束縛する」、という言い回しが正しいです。「変数に値を束縛する」というのは正しくありません。

値を変更できないので、以下のようなコードはコンパイルエラーになります。

multibind.hs
a :: Int
a = 1 --束縛:「aは1である」
a = 2 --束縛:「aは2である」(束縛は一回しかできないのでエラー)

main = do
    print a
コンパイラ
[1 of 1] Compiling Main ( multibind.hs, example.o )

multibind.hs:3:1:
    Multiple declaration of ‘a’
    Declared at: multibind.hs 2:1
                 multibind.hs 3:1

このことから、Haskellにおける「変数」は値を入れる箱というより値や式に別名をつける機能である、というくらいに捉えておくとよいと思います。

いろいろな型

Haskellの値にはいろいろな型があります。代表的なものを見てみましょう。

整数型:Int,Integer

Haskellの整数型には、

  • Int型 : 格納できる値の大きさが限られている整数
  • Integer型 : 格納できる値の大きさに制限がない整数

の2つがあります。以下の例はそのことをよく示しています。

integers.hs
a :: Int
b :: Integer
c :: Int
d :: Integer

--小さな値ならIntもInteger同じだが
a = 128
b = 128

--パソコンの限界を超えるサイズの数値はIntegerしか格納できない
c = 123456789123456789123456789
d = 123456789123456789123456789

main = do
    print a
    print b
    print c
    print d
結果
128
128
-944716198279094507
123456789123456789123456789

Int型のほうは、大きい値を格納すると値が壊れてしまっていることがわかります。
(壊れた後の値は環境によって異なることがあります)

Int型は固定長の整数であるのに対し、Integer型は「多倍長整数」という仕組みを使っているので、大きさに制限なく値を格納できるのです。
ただし、実行時のパフォーマンスはInt型のほうが優れているので、特別な理由がない限りはInt型を用いるべきです。

浮動小数点数型 : Float,Double

Haskellの浮動小数点数型には、

  • Float型 … 単精度浮動小数点数
  • Double型 … 倍精度浮動小数点数

の2つがあります。整数型とは異なり小数計算もできます。
Double型のほうがより精度よく値を格納できます。

※精度だけでなくパフォーマンスの観点から見ても、Float型は使わないほうがいいようです。
(詳しい話はこちらをどうぞ。(英語)
 要するに、最近のコンピュータにはFloat型用の回路が付いていないので、
 Float->Doubleの変換が必要な分遅くなるということのようです。)

値の型を明示的に示す方法

今ここに、

1

という値があったとします。さて、この値の型は何でしょうか?

実際に考えてみると、これは「Int型」「Integer型」「Float型」「Double型」のどの型の値としても捉えることができることが分かります。

このような場合に、明示的に値の型を示す方法があります。
例えば、この値がInt型であることを明示するには、

1 :: Int

という風に書きます。変数の型注釈の構文とよく似ていて分かりやすいですね。
このように、「[値] :: [型]」とすることで値の型を明示することができます。

これを使うとさっきのIntとIntegerの例をコンパクトに書き直せます。

integers_short.hs
main = do
    print (128 :: Int)
    print (128 :: Integer)
    print (123456789123456789123456789 :: Int)
    print (123456789123456789123456789 :: Integer)
結果
128
128
-944716198279094507
123456789123456789123456789

頻繁に使うものではありませんが、頭の片隅に置いておいてください。

リスト型 : [型名]

他の言語で言うところの配列のようなものとして、Haskellには「リスト」というものがあります。
以下の例をご覧ください。

list.hs
a :: [Int] --型注釈:「aはInt型のリストである」
b :: [Double] -- 型注釈:「bはDouble型のリストである」

a = [1, 2, 3, 4] --要素は「,」で区切って「[]」で囲う
b = [1.1, 2.2, 3.3]
--c = [1.1::Double, 2::Int] -- エラー!一つのリストには同じ型の要素しか格納できない

main = do
    print a
    print b
結果
[1, 2, 3, 4]
[1.1, 2.2, 3.3]

では、まずは型注釈を見てみましょう。

a :: [Int] --型注釈:「aはInt型のリストである」
b :: [Double] -- 型注釈:「aはDouble型のリストである」

[Int]」は「『Int型のリスト』型」を表し、「[Double]」「『Double型のリスト』型」を表します。
同じように、「[型名]」で「『その型の値を格納するリスト』型」を表現することができます。

次に、値の設定です。

a = [1, 2, 3, 4] --要素は「,」で区切って「[]」で囲う
b = [1.1, 2.2, 3.3]
--c = [1.1::Double, 2::Int] -- エラー!一つのリストには同じ型の要素しか格納できない

リスト型の値は「[要素, 要素, 要素, …]」という風に[]の中に要素をカンマで区切って記述します。
ただし、異なる種類の値を一つのリストに格納することはできないので注意してください。

このように、リスト型は必ず他の型と組み合わせて使う必要があります。
リスト型のような、他の型と組み合わせて使う型を一般に「多相型」といいます。
(「多相」というのは要するに「たくさんの形態がある」というような意味で、
 例えばリスト型と一口に言っても[Int]型、[Integer]型、[Double]型…などのように
 他の型との組み合わせで無数の型を作れる、というイメージから「多相」と名付けられているようです。)

値の取り出しなどについてはまた後で説明します。

文字型と文字列型:Char,[Char]

文字を表すのはChar型です。

char.hs
a :: Char

a = 'a'

main = do
    print a
結果
'a'

一文字を表す値は「'字'」という風に表します。

文字列は、Char型のリストで表現されます。(さっきリスト型を紹介したのはこのためです)
以下の例がそのことをよく示しています。

string.hs
a :: [Char]
b :: [Char]

a = "Hello"
b = ['H', 'e', 'l', 'l', 'o']

main = do
    print a
    print b
結果
"Hello"
"Hello"

文字列を表す値は「"文字列"」で表せます。
ただ、上の例から分かるようにこれはリストの略記法に過ぎません。

(このような、見やすさや書きやすさを保つための略記法を「糖衣構文」と言ったりします。
 複雑だったり冗長だったりする構文に皮をかぶせて簡単に見せる、という感じです。
 Haskellにはプログラムを美しく書くための糖衣構文が他にも存在します。)

代表的な型の紹介は以上です。

まとめ

さて、今回の「仕組みから学ぶHaskell入門 その1:基礎文法」では、

  • 値の出力の仕方(main = do と print)
  • 変数の型注釈の付け方
  • 変数とは値や式に別名をつける機能であること
  • 様々な値の書き表し方

について学習しました。

次回「仕組みから学ぶHaskell入門 その2:関数と評価戦略」では、
Haskellの核をなす「関数」というものについて学習していきます。

それでは、またお会いしましょう。