2. Haskellの基本事項

本章ではHaskellの基礎事項を学びます.この章の内容は後の章でも掘り下げて学びます.プログラミング初学者はあまり深く考えず,まずはプログラミング用語に慣れてください.

2.1. 計算機として使ってみよう

四則演算は通常の計算と同じで,乗除の計算が和差より優先されます.優先度を変えたければ括弧を使います.

ghci> 1 + 2 * 5
11
ghci> (1 + 2) * 5
15

冪乗は^を使います.^は他の演算子よりも優先度が高いので先に計算されます.下の例では\(2^4\)が先に計算されてから,前後の値と掛け合わされます.演算子の間にスペースを入れても結果は変わりません.

ghci> 1 * 2^4 * 5
80
ghci> 1 * 2 ^ 4 * 5
80

指数部分が小数の冪乗は**を使います.

ghci> 2 ** 0.5
1.4142135623730951

基本的な数学関数も標準で用意されています.数学の関数は\(f(x)\)のように,関数fに渡す値xを括弧で囲みますが,Haskellでは括弧を付けずf xと書きます.関数に渡す値x引数(argument)と呼びます.

ghci> sqrt 5
2.23606797749979
ghci> log 10         (1)
2.302585092994046
ghci> logBase 2 8    (2)
3.0
ghci> cos pi         (3)
-1.0
1 log関数は自然対数を返します.対数の底は\(e\)です.
2 対数の底を指定する場合はlogBase関数を使います.logBase 2 8は\(\log_2 8\)です.
3 piは無理数\(\pi\)を表す定数です.

sqrt 5のように関数に引数を渡して関数を起動することを関数呼び出し(function call)といいます.関数と引数の間はスペースで区切ります.引数が2つ以上あるときは,上のlogBase 2 8のように引数をスペースで区切って並べます.

関数型プログラミングでは,関数を呼び出して実行することを,関数を引数に適用(apply)すると表現します.そして,関数呼び出しのことを関数適用(function application)といいます.

roundtruncateceilingfloor関数はそれぞれ,丸め,0方向への切り捨て,繰り上げ,繰り下げに使えます.以下はround関数の例のみ提示しています.負数を関数に渡すときは注意が必要で,以下のようにマイナス記号を含めて括弧で囲む必要があります.

ghci> round 3.9
4
ghci> round (-3.9)
-4

関数は計算の優先順位が最も高く左結合になります.もしround -3.9と入力するとHaskellの処理系はround-3.9に分解して読みます.マイナス記号と3.9の間にスペースは有りませんが,-は引き算の2項演算子として有効な記号なので独立した要素(トークン)として読み込まれます.もし-の優先度が高ければ「round引く3.9」という引き算に解釈されます.しかし,関数の優先度が高いので-roundと結合し,(round -) 3.9と解釈されます.

一般には-33``の「符号」は単項演算子です.しかしHaskellでは``-は和と差の「関数」です.したがって+3というプラス符号としての単項演算子もマイナス符号の演算子も有りません.ただしマイナス記号だけは符号を反転させるnegate関数のシンタックスシュガーとして提供されています.したがって-3negate 3と同じです.プラス記号は和の関数のみでシンタックシュシュガーではないので,以下の2行目と3行目は括弧を付けてもエラーになります.

2 * -3      --  正しくは 2 * (-3)
2 * +3      --  2 * (+3) でもエラー
round +3.9  --  round (+3.9)でもエラー

ちなみに,比較演算子は計算の優先順位が低いので,以下のように負数を括弧で囲まなくてもマイナス記号は数値と結合して評価されます.比較演算子はあとで真偽値と一緒に学びます.

ghci> 0 < -2     (1)
False
ghci> 2 == -2    (2)
False
1 括弧で囲まなくてもエラーにならない.
2 2 == (-2)と同じ.

2.2. 演算子と関数

Haskellでは演算子も関数です.関数名が記号の場合,関数呼び出しは中置記法になります.演算子を前置記法の関数呼び出しに変えるには括弧で囲みます.

(+) 2 3      --  2 + 3 と同じ
(-) 2 3      --  2 - 3 と同じ
(==) 2 (-3)  --  2 == -3 と同じ  (1)
1 前置記法の場合,関数は左結合になるため-3に括弧を付けないと(==) 2 -という関数呼び出しと解釈されエラーとなる.

Haskellでは関数名は必ず小文字か記号で始めます.関数名が記号でなければ関数呼び出しは前置記法になります.2つの引数を取る前置記法の関数はバッククォートで囲むことで中置記法で書くことができます.

ghci> 2 `logBase` 8    (1)
3.0
1 logBase 2 8と同じ.

関数の中置記法から前置記法への書き換え,前置記法から中置記法への書き換えは,今後色々な場面で遭遇します.

2.3. 割り算

これまで割り算には触れませんでした.割り算も冪乗と同じように整数と小数で演算子が異なります.整数の割り算はdiv関数を使い,小数の割り算は/演算子を使います.

ghci> 4.0 / 2.0
2.0
ghci> 4 / 2
2.0

4 / 2は整数の除算ですが結果が小数になっています./演算子は小数を引数にとる関数として定義されているので,処理系が引数を小数と解釈して計算します.

以下はdiv関数を使った整数の割り算です.関数を中置演算子として使うのでバッククォートで囲んでいます.

ghci> 4 `div` 2
2
ghci> div 4 2
2
ghci > 4.0 `div` 2.0
error

最後の式では,整数を引数にとるdivに小数を渡しているのでエラーになります.小数を整数に変換すると情報が失われるので,処理系が整数を小数として解釈することはありません.こうしたデータの「型」にまつわる話題はChapter 3で学びます.

割り算の余りはmod関数を使います.

ghci> 7 `div` 3
2
ghci> 7 `mod` 3
1

2.4. 関数定義

GHCには標準で多くの関数が用意されています.言語処理系が標準で用意する関数群を一般に標準ライブラリ(standard library)と呼びます.GHCに同梱される標準ライブラリのAPIはHaskell Hierarchical Librariesページを検索してください.標準ライブラリのPreludeモジュールで定義されている関数は,モジュールを読み込まなくても使えます.これまで使ってきた関数は全てPreludeに含まれます.

ここでは自分で関数を作ってみましょう.関数定義はとても簡単です.例えば,与えられた引数を2倍にするdouble関数は以下のように定義します.書式は,「関数名 パラメータ = 関数定義」です.

double x = 2 * x     --   関数名  パラメータ  =  関数定義
                     --   double    x     =   2 * x

引数を受け取る変数をパラメータparameter)と呼びます.上の定義ではxを使っています.この関数をghciに入力し,引数を与えて呼び出してみましょう.

ghci> double x = 2 * x
ghci> double 3
6
ghci> double 5
10

引数が複数ある場合は,パラメータを増やします.以下は2つの引数を2乗して和を返すsquareSum関数の定義と呼び出しです.

ghci> squareSum x y = x^2 + y^2
ghci> squareSum 5 10
125

2.5. 優先順位と括弧付け

Haskellの関数呼び出しには括弧が要らないのですが,複数の関数を入れ子にして呼び出すときには優先順位の関係で括弧をつける必要があります.

squareSum sqrt 5 10   -- 関数は左結合なので,(squareSum sqrt 5) 10 と解釈されエラー

squareSumは2つの引数を取る関数として定義しました.関数は最も優先度が高いので,上の関数呼び出しは(squareSum sqrt 5) 5と解釈されてしまいます.正しくは以下のように記述しなければなりません.

ghci> squareSum (sqrt 5) 10
105.0

関数呼び出しでは括弧が要りませんが,個別の引数が複数要素で構成されている場合は,その引数を括弧で囲む必要があります.

2.6. 論理式

ここまで数値演算ばかりを見てきましたが,条件判定や条件分岐で使う論理演算も見てみましょう.条件が真である状態を表す値はTrue,偽である状態を表す値はFalseです.

右辺と左辺が等しいかを判定する比較演算子は==です.

ghci> 4 == 4
True
ghci> 4 == 10
False

等しくない状態を判定するnot-equal演算子は/=です.

ghci> 4 /= 10
True
ghci> 4 /= 4
False

大小関係の比較演算子は数学記号とほぼ同じです.

ghici> 4 < 5
True
ghici> 4 > 4
False
ghici> 4 >= 4
True

論理積「〜かつ〜」は&&,論理和「〜または〜」は||演算子を使います.

ghci> True && True
True
ghci> True && False
False
ghci> True || False
True

以上の論理演算子は全て関数なので,括弧で囲むと前置記法で書けます.

ghci> (&&) True True
True
ghci> (&&) True False
False
ghci> (||) True False
True

論理否定はnot関数です.

ghci> not True
False
ghci> not False
True

2.7. 式と評価

Haskellのプログラムは全て式(expression)で構成されます.「式」は,言語処理系によって実行・評価されると必ず値を返します.値を返さないコードは「文(statement)」と呼びます.Haskellには「文」はありません.全てのコードは評価されると値を返します.

これまでコード中に書いてきた5やTrueといった値も式です.例えば,5は評価されると書かれた通り5と評価されます.このように書かれたままの値に評価されるものをリテラル(literal)と呼びます.

一般にプログラムを書く際には,コンパイラやインタプリタの気持ちになってソースコード内の式を評価しながら,プログラムの動作をイメージします.「式を評価する」というのは,式が実行されて返す値と式とを置き換えることを指します.処理系がどのようにソースコードを評価するかは実は奥が深いのですが,今のところは気にする必要はありません.式を見たら頭の中でどんどん式が返す値と置き換えていきましょう.

2.8. 本章のまとめ

  • 演算子を使った計算方法を学びました.

  • 関数呼び出しと関数定義の基本を学びました.

  • 演算子はすべて関数であることを学びました.

  • 除算と冪乗の演算子は,引数が整数の場合と小数の場合とで,演算子が異なることを学びました.

  • 2つの引数を取る関数はバッククォートで囲むことで中置演算子に変換できました.中置演算子は括弧で囲むことで前置記法で書けました.

  • 演算子や関数には計算の優先順位があり,関数は最も優先順位が高く左結合であることを学びました.また,必要であれば優先順位を変えるために括弧が必要です.

  • 「式」は必ず値を返します.Haskellのコードは全て式で構成されます.

  • 「式を評価する」という意味は,「式を,式が返した値で置き換える」ということです.

2.9. 練習問題

  1. インタプリタで\(\log_2 18446744073709551616\)と\(2^{64}\)を計算してください.

  2. 以下のコードの間違いを指摘してください.

    2 + 3 * -4  + 5^65
  3. \(\sqrt{2}\)を計算したくて以下を実行したところ1.0を得ました.なぜこのような結果になったか説明してください.また,sqrt関数を使わず以下のコードを修正して正しい計算結果を出してください.

    2^1/2
  4. 以下の計算式の演算子を全て前置記法で書き換えてください.

    4 + 5 * 6 - 1 `div` 2
  5. 3つの引数を取りその平均を計算するaverage3関数を定義してください.average3関数を使って1,5,10の平均を計算してください.

  6. 前問のaverage3関数を使って1,5,10の平均を計算する時,`average3`関数を中置表記で呼び出せますか?