Haskell概要
はじめに
この記事は2017年7月15日に開催したHaskell入門ハンズオン!のための事前資料です。同記事を2018年11月3日のHaskell入門ハンズオン! #4のための事前資料としても使わせていただきます。あつかう内容はHaskellの処理系の導入と、Haskellの概要の説明です。この資料を完全に理解する必要はありませんが、処理系の導入をして、Haskellがどのような言語であるかを、ざっと目を通し、実行例などは1度、実際に手を動かして打ち込んでみてください。
「Haskell入門ハンズオン!」事前資料の一覧
質問など
事前資料を読んでいくうえで生じた質問は、teratailでHaskellタグをつけて質問していただけると幸いです。あるいはfunpaala@gmail.comに、メールで質問していただくのでも、かまいません。また、Slackでも質問を受けつけています。
-
Slack: Haskell Dojoの登録用のアドレス
- Slack: Haskell Dojo 登録済みのかたはこちらから
事前資料に関する質問以外でもかまいません。どんなことであれ、質問は大歓迎ですので、よろしくおねがいします。
Haskellとは
汎用プログラミング言語。純粋関数型言語。
特徴
Haskellには、静的な型付けや、式の評価と動作の実行をわけることにより、コードの意味が明確に決まりやすいという特徴がある。また、遅延評価という特徴により、ある種のコードを見た目に直観的な書きかたで書けるようになっている。
Haskellを学ぶと、何かいいことあるの?
新しい手法を学ぶ道具として
Haskellでは、関数や動作を、簡潔かつ直接的に書くことができる。そのため、プログラミングにおける新しい手法を試すのが、簡単である。
他の言語を使うときも
Haskellでは、関数の作者の意図を明確に表現できる。また、状態の変化を式の評価から、くくりだすことによって、意図しない状態の変化を避けることができる。これらの特徴から、Haskellを学んだあとに、他の言語を書くときに、学ぶまえよりも安全なコードを書けるようになる。
現実の問題を解決するときに
もちろん、Haskellは汎用プログラミング言語なので、現実の問題解決に使うことができる。Haskellを実際の現場で使うことで、つぎのことが期待できる。ひとつは、はじめに書くときに、より深く問題について考えるということ。ふたつめには、実際に書いたコードをメンテナンスしていくことが、より容易になるということ。
DSL生成のために
Haskellは簡潔で直接的な表現ができ、また、型クラスによって一般的なわくぐみを共有できるため、DSL(ドメイン固有言語)を作ることも容易である。
文法の概要
ここでは、文法の概要を、いくつか説明する。初学者は、さきに事前資料 - 2/3 「処理系の導入と対話環境」と事前資料 - 3/3 「やってみよう」を、やってみてから、ここを読むほうがいいかもしれない。
値の定義
値を定義するには、つぎのようにする。
変数名 = 値
具体的には、つぎのようになる。
lucky = 7
型宣言
型宣言は任意だ。型宣言がないとき、型は推論される。ただし、モジュール内をスコープとする値すべてに、型宣言をつけるのは、良い習慣だ。型宣言は、つぎのように書く。
変数名 :: 型
具体的には、つぎのようになる。
lucky :: Integer
型注釈
変数を束縛しない値については、型宣言をつけられない。しかし、そのままでは型が特定できずエラーになる式もある。そのようなときには、型注釈をつける必要がある。
式 :: 型
具体的には、つぎのようになる。
123 :: Integer
多相的な型
Haskellでは、ふつうの型の名前には「大文字ではじまる識別子」を使う。「小文字ではじまる識別子」を型の名前として使うと、それは多相的な型の名前になる。たとえば、2つ引数を取り、そのうちのひとつめの値をかえす関数は、つぎのようになる。
ignoreSecond :: a -> b -> a
ignoreSecond x y = x
型の別名
予約語typeを使って、型の別名(型シノニム)を定義できる。
type 型の別名 = 型
たとえば、つぎのようにできる。
type TabaichouSeisuu = Integer
型引数
型シノニムを定義するときに、型変数を型引数として指定できる。
type 型の別名 型引数1 型引数2 ... = 型
たとえば、つぎのように書ける。
type Check a = (Bool, a)
関数定義
関数定義は、つぎのように書く。
関数名 仮引数1 仮引数2 ... = 式
具体的には、つぎのようになる。
sevenEleven x y = x * 7 + y * 11
関数リテラル
関数リテラルという記法がある。関数定義は、関数リテラルで作った関数を変数に束縛したと考えることができる。
\仮引数1 仮引数2 ... -> 式
具体的には、つぎのようになる。
\x y z -> x + y * z
演算子
演算子と関数のちがいは、見た目だけである。演算子の識別子には、記号列が使われる。また、演算子は中置記法となる。演算子と関数とは、``や()を使うことで、たがいに変換することができる。
(演算子名)
`関数名`
具体的には、つぎのようになる。
(+) 3 4
126 `mod` 5
演算子+を()でかこむことで、関数として使える。関数modを``でかこむことで、演算子として使える。
関数の型
関数も値だ。だから、型宣言も値と、おなじように書ける。ただし、関数の型を組み立てるには型構築子(->)を使う。つぎのように、書く。
関数名 :: 引数1の型 -> 引数2の型 -> ... -> 返り値の型
具体的には、つぎのようになる。
sevenEleven :: Integer -> Integer -> Integer
パターンマッチ
Haskellでは、分岐や複合的な値からの値の取り出しに、パターンマッチを使う。パターンマッチは、関数定義のときに、使うことができる。パターンマッチを使った関数の定義は、つぎのように書く。
関数名 (値構築子1 変数1-1 変数1-2 ...) = 式1
関数名 (値構築子2 変数2-1 変数2-2 ...) = 式2
具体的には、つぎのようになる。
nothingIsLucky (Just x) = x
nothingIsLucky Nothing = 7
Just値やNothing値は、Maybe型の値だ。値構築子Justは引数をひとつとる。値構築子Nothingは引数をとらない。Just 11のように作られた値は、Just xにマッチさせることで、なかみの11を変数xで受けとることができる。
束縛した変数を使わないことがある。そのようなときには、ワイルドカードが使える。
isNothing Nothing = True
isNOthing (Just _) = False
ワイルドカードはアンダーバーで示される。これは、変数束縛をしない以外では、パターンマッチに変数を使うのと、おなじ意味になる。
変数に束縛したうえで、さらに細かくパターンマッチをすることもできる。
変数@パターン
具体的には、つぎのようになる。
jx@(Just x)
たとえば、これにJust 8をマッチさせると、変数jxはJust 8に束縛され、変数xは8に束縛される。
Bool値に対する構文糖1: ガード
Bool値はFalseまたはTrue。これらは、値構築子Nothingと、おなじように引数をとらない。それぞれ偽と真とを意味する。これらも同様に、つぎのように書くことができる。
someFunc False = ..
someFunc True = ..
しかし、Bool値に対するパターンマッチは頻出なので、シンプルな構文糖がある。
関数名 仮引数1 仮引数2 ...
| 条件1 = 式1
| 条件2 = 式2
.
.
.
| otherwise = 式n
「条件」はBool値に評価される式だ。
Bool値に対する構文糖2: if式
パターンマッチにBool値を使う場合の構文糖には、もうひとつある。if式だ。つぎのようになる。
if Bool値 then Trueのときの値 else Falseのときの値
たとえば、つぎのようになる。
comment :: Integer -> String
comment n = if n >= 100 then "Great!" else "OK."
case式
関数定義の引数のところでのパターンマッチを、みてきた。これは、関数の引数だけしかマッチすることができない。もっと自由に、式のなかでパターンマッチしたいときにはcase式を使う。
case 式 of
パターン1 -> 式1
パターン2 -> 式2
.
.
.
具体的には、つぎのようになる。
case toLower c of
'y' -> Just True
'n' -> Just False
_ -> Nothing
代数的データ型
既存の型を組み合わせて、新しい型を作ることができる。
data 型構築子名
= 値構築子1 引数1-1の型 引数1-2の型 ...
| 値構築子2 引数2-1の型 引数2-2の型 ...
.
.
.
| ...
具体的には、つぎのようになる。
data Pet = Cat String | Dog String
また型構築子は型引数をとることができる。
data 型構築子名 型引数1 型引数2 ...
= (値構築子の引数の型の定義に型引数が使える)
前述のMaybe型の値やBool型の値は、つぎのように定義される。
data Maybe a = Nothing | Just a
data Bool = False | True
タプル
Haskellにはタプルという構造が用意されている。2要素タプルは、つぎのように書く。
変数名 :: (型1, 型2)
変数名 = (値1, 値2)
具体的には、つぎのようになる。
taro :: (String, Integer)
taro = ("Taro Yamada", 35)
つぎのように、パターンマッチでタプルの、それぞれの要素を受け取ることができる。
human :: (String, Integer) -> String
human (n, a) = n ++ "(" ++ show a ++ ")"
IOモナド
IOモナドを理解するには、本来、型クラスやモナドについて学ぶ必要がある。ここでは、中身を理解するよりも、とりあえず「使う」という観点から文法を解説する。
たとえば文字列"hello"を表示してから、"world"を表示する動作を組み立てるには、つぎのようにする。
putStrLn "hello" >> putStrLn "world"
キーボードからの入力を受け取り、それを表示するには、つぎのように書く。
getLine >>= putStrLn
do記法
手続き型言語に、なれた人にとって、より書きやすい、do記法という構文糖がある。うえの例は、それぞれ、つぎのように書ける。
do putStrLn "hello"
putStrLn "world"
do l <- getLine
putStrLn l
モジュールの導入
ソースファイルのなかでモジュールを導入するには、予約語importを使う。モジュールを導入するときには、導入リストを明示することができる。導入リストには、変数名や型名などをカンマでくぎって列挙する。
import モジュール名 (識別子1, 識別子2, ...)
具体的には、つぎのようになる。
import System.Environment (getArgs)
コメント
Haskellでは、コメントを書くのに、2種類の記法が使える。--から行の終わりまでは、コメントとなる。
x = 8 -- xは8です
あるいは{-と-}でかこまれた文字列も、コメントになる。
f x = x ^ x {- 関数fは引数xをとり、
xのx乗を計算する、
よ。-}