は は は ハ haskell

blog1.mammb.com
の続きです。

関数の定義

2 つの数字を加算する add 関数を定義してみます。

add x y = x + y

実行すると、以下のように 2 つの数字を加算した結果が得られていることが分かります。

Main> add 2 3
5


この関数の型について調べてみましょう。:type コマンドを使います。または :t としても同様です。

Main> :type add
add :: Num a => a -> a -> a

この型の定義は、-> が右結合であるので、以下と同じ意味となります。

add :: Num a => a -> ( a -> a )

この関数の型定義の意味は、

  • 「add ::」 add 関数の定義は、
  • 「Num a =>」 型クラス Num という型制約を持つ型変数 a に対して、
  • 「a -> 」 a の型を入力として、
  • 「( a -> a )」 a の型を a の型に変換する関数 を返す

となります。この a は Java で言う総称型のような感じで、以下の Java コードの T のようなものと考えると良いでしょう。

public <T extends Number> T add(T x, T y) {
    return x + y;
}

関数定義を追うと、

実際の関数定義「add x y = x + y」に対して「add 2 3」のように引数を与えた場合で考えると、

  • Num クラスである 2 を引数 x として与えると、
  • y を引数に取る 2 + y という関数が返却されて、
  • この関数 2 + y という関数に、3 を引数 y として与えると、
  • 2 + 3 となり、結果 5 となる

となります。


もう一度、型定義について見てみると、

Main> :type add
add :: Num a => a -> a -> a

a を ( a -> a ) という関数に変換する、つまり 引数を1つ与えると関数を返す こととなり、これをカリー化されていると呼びます。関数型言語では、入力として関数を取る関数 も定義でき、これらを含めて高階関数と言います。
普段は、a 型と a 型の引数を取って、結果として a 型を返すんだな、、のように考えます。


さてここで、 Num クラスとはなんでしょうか。まずは型について見ていき、その後でクラスについて見ていきます。

Haskellの型

基本型

Haskell が提供する基本的な型には以下があります。

  • Bool 真理値
    • 2つの真理値、False と True を持つ
  • Int 整数
    • 普通の整数型
  • Integer 多倍長正数
    • 上限と下限の制限がない整数型で Java で言う BigInteger
  • Float 単精度浮動小数点数
    • 単精度の浮動小数
  • Double 単精度浮動小数点数
    • 倍精度の浮動小数
  • Char 文字型
    • 文字を表し、'A' のようにシングルクオートで囲む
  • String 文字列型
    • 文字列を表し、"foo"のようにダブルクオートで囲む

Haskell の型名は先頭が大文字となります。


例えば、引数として Int を取り Int を返す関数 plusOne の型定義は以下のようになります。

plusOne :: Int  -> Int
plusOne x = x + 1

型定義を省略した場合には、Haskell の型推論により最も広い型が型推論されます。関数の型定義を記載することで、意図が明確になりエラーの早期発見につながるため、上の例のように極力型定義を記述するのが望ましいと思います。
定数を定義し、その定数の型を指定するには、以下の2通りの指定ができます。

pi :: Float
pi = 3.14

pi = 3.14 :: Float
リスト型

リストは[]で定義し、要素はカンマで区切ります。

['a', 'b', 'c'] ::[Char]  -- Char のリスト
["ab", "cd"] ::[String]  -- String のリスト

リストのリストも扱えます。また[]は要素がゼロの空リストとなります。

[["ab"], ["ab", "cd"]]  -- リストのリスト
[]  -- 空リスト
タプル型

タプルは()で定義し、要素はカンマで区切ります。複数の型を束ねて扱えます。

(False, True) :: (Bool, Bool)
("ab", True, 'z') :: (String, Bool, Char)

リストを要素に持つこともできます。

(['a', 'b'], [True, False]) :: ([Char], [Bool])
関数型

関数の型は、ある型の入力を ある型の出力にする、変換を定義することになります。変換は -> にて表されます。
ゼロから n までの整数のリストを返す zeroto 関数は以下のように定義できます。

zeroto :: Int -> [Int]
zeroto n = [0..n]

入力として Int 型を与えると、出力として Intのリスト型が返されます。


関数の型は、型変数にて多相的に定義できます。リストから先頭要素を取り出す関数 head の型は以下のように定義できます。

head :: [a] -> a

型変数は、a, b, c という名前が使われるのが一般的です。Java での List と同じ感じです。

型クラス

型クラスとは、共通のメソッドを持つ型をまとめて名前を付けたものです。
例えば あるデータ要素を並び替える場合に、並べ替えに必要な関数が定義されている型に Ord という名前をつけて扱います。Ord という型クラスは並び替えに必要な関数を持つ共通のインターフェースとして抽象的に扱うことができます。
例えば、Bool, Char, String, Int のような基本型はすべて Ord クラスのインスタンスとなります。

Eqクラスを例に

Eq クラスは同値比較可能な型の集合です。定義は以下のようになっています。

class Eq a where
    (==), (/=) :: a -> a -> Bool

    x == y      = not (x/=y)
    x /= y      = not (x==y)

class とは == と /= 関数が定義された型を表し、Java でいうとインターフェースに近いです。

このEqクラスのインスタンスとして、例えば Bool 型があり、以下のような定義で表されます。

data Bool    = False | True
	       deriving (Eq, Ord, Ix, Enum, Read, Show, Bounded)

Bool 型は、Eq, Ord, Ix, Enum, Read, Show, Bounded 型クラスのインスタンスとして定義されています。

代表的な型クラス
  • Eq クラス
    • 同値比較可能なクラスで、(==)、(/=) の定義を持つ
  • Ord クラス
    • 順序付け可能なクラスで、(<)、(>) など、比較関数の定義を持つ
  • Num クラス
    • 数値クラスで、(+)、(-) など、数値演算の定義を持つ
  • Integral クラス
    • 整数クラスで、div、mod の商と余り演算の定義を持つ
  • Show クラス
    • 表示可能クラスで、show にて文字列への変換関数の定義を持つ
  • Read クラス
    • 読込可能クラスで、read にて文字列から型への変換関数の定義を持つ

blog1.mammb.com
につづく