LoginSignup
27
22

More than 5 years have passed since last update.

Haskell個人メモ :: 1.基本

Last updated at Posted at 2016-05-05

はじめに

Haskell学習中なのですが、アウトプットして理解を深めようと書いてみました。
まとめのベースは「すごいHaskell楽しく学ぼう」という本で、コード例も似ている(あるいは同じ)ものが多いです。

本の章ごとに個人メモという形で(リファレンス的な)記事を書いていこうと思います。

シリーズ :: [Qiita記事]

Haskell個人メモ :: 1.基本 ←いまここ
Haskell個人メモ :: 2.型
Haskell個人メモ :: 3.関数の構文
Haskell個人メモ :: 4.再帰

Haskellの特徴

  • 純粋関数型言語(副作用を持たない)
  • 静的型付け
  • 型推論がある
  • 遅延評価

論理演算 - && || not

True && True    -- True
False || True   -- True
not True        -- False

否定は!ではなくnot

等値比較 - == /=

5 == 5  -- True
5 /= 5  -- False

!=ではなく/=。数学の≠をイメージしたものらしい。

関数適用

min 9 10    -- 9
max 9 10    -- 10

関数適用は(スペース)
殆どの関数は前置記法で、演算子などの一部が中置記法。

10 `div` 2  -- 5
(+) 5 2     -- 7

``で囲むことで中置記法として記述できる。
逆に中置記法の演算子(正確には関数)を()で囲むことで前置記法ができる。

max 2 1 + 3   -- 5
(max 2 1) + 3

max 2 (1 + 3) -- 4

関数適用の結合優先度は最強。
順番を明示したい場合には、他の言語と同様に()で囲む。

関数定義(基本)

doubleMe  x = x + x
doubleMe' x = x * 2

基本形は関数名 引数 = 本体

Haskellの慣習的に、違ったバージョン(遅延しないとか)の時は末尾に'をつけたりする。
(数学でいうところのxx'みたいな)

if-then-else

isZero x = if x == 0
             then "Yes"
             else "No"

ifは式なので値を返す。
そのためelseは省略できない。

リスト

let lostNumbers = [4, 8, 15, 16, 23, 42]

[]で囲って,で区切って記述する。

[1, "Hello"] -- Compile Error

リスト内の要素はすべて同じ型である必要がある。

連結

[1, 2, 3] ++ [4, 5, 6] -- [1,2,3,4,5,6]

++でリスト同士を連結。
連結するときに1個目のリストの最後まで走査するのでパフォーマンスは(1個目のリストの要素数に対して)O(n)。

先頭への要素追加

0 : [1, 2, 3]       -- [0,1,2,3]

リストの先頭に要素を追加するときは:(cons演算子)を使う。
Haskellのリストはconsリストなので、先頭への要素追加は一瞬で終わる。

ちなみに「cons」は「construction(構築)」の略らしい。

シンタックスシュガー

0 : 1 : 2 : 3 : []  -- [0,1,2,3] 

前述してきたリスト表記は上記のシンタックスシュガー。
[]は空のリスト。

いわゆる単方向連結リストのイメージでOK。
[0] --> [1] --> [2] --> [3] --> []

末尾の[]は他の言語ではnullnilが格納されることが多い(印象)。

要素へのアクセス

"Hello" !! 1 -- 'e'
"Hello" !! 5 -- *** Exception: Prelude.!!: index too large

!! indexでアクセス。
範囲外にアクセスするとエラーになる。(ちなみにLisp派生のClojureだとnilを返す)

リスト in リスト

[[1, 2, 3], [4, 5, 6]]

リストの要素にリストを含めてもOK。
(リストの型が[Int][[Int]]になるようなもの)

リスト操作関数

head / tail / init / last

head [1, 2, 3, 4, 5] -- 1
tail [1, 2, 3, 4, 5] -- [2,3,4,5]
init [1, 2, 3, 4, 5] -- [1,2,3,4]
last [1, 2, 3, 4, 5] -- 5

リスト要素(範囲)を取り出す。

head [] -- *** Exception: Prelude.head: empty list

いずれも空のリストに対して適用するとエラーになるので注意。

length

length [1, 2, 3, 4, 5] -- 5

リストの長さを取得する。

null

null [1, 2, 3]
null []

リストが空かどうか調べる。

reverse

reverse [1, 2, 3, 4, 5] -- [5,4,3,2,1]

リストを反転させる。

take / drop

take 3 [1, 2, 3, 4, 5] -- [1,2,3]
drop 3 [1, 2, 3, 4, 5] -- [4,5]

takeは先頭から指定された数の要素を「取り出した(take)」リストを返す。
dropは先頭から指定された数の要素を「除外した(drop)」リストを返す。

take 10 [1, 2, 3, 4, 5] -- [1,2,3,4,5]
drop 10 [1, 2, 3, 4, 5] -- []

それぞれリストの要素数を超えても機能する。

maximum / minimum

maximum [1, 2, 3, 4, 5] -- 5
minimum [1, 2, 3, 4, 5] -- 1

最大値、最小値をそれぞれ取り出す。
ただし、リストの要素は「順序付け」されている必要がある。

sum / product

sum [1, 2, 3, 4, 5] -- 15
product [1, 2, 3, 4, 5] -- 120

和、積、をそれぞれ返す。

elem

4 `elem` [1, 2, 3, 4, 5] -- True
6 `elem` [1, 2, 3, 4, 5] -- False

リストに指定した要素が含まれているか判定する。
(他の言語ではcontainsという名前がよく使われる印象)

elem 4 [1, 2, 3, 4, 5] -- True

もちろん前置記法で書いてもOK。
(慣れてくるとこっちのほうが関数っぽくて読みやすく感じたりする)

レンジ(Range)

[1..10]    -- [1,2,3,4,5,6,7,8,9,10]
[2, 4..20] -- [2,4,6,8,10,12,14,16,18,20]

[開始値..終了値]でレンジを指定することができ、その範囲のリストが生成できる。
[開始値, 次の値..終了値]でステップ(増分値)を指定したレンジになる。

[10, 9..1] -- [10,9,8,7,6,5,4,3,2,1]

値が減少していくリストを作る場合は[開始値, 次の値..終了値]という指定にする必要がある。
(例えば[10..1]とやると、空のリスト[]が結果として得られてしまう)

無限リスト

[1..] -- [1, 2, 3, ...]

Haskellは遅延評価なので、無限リストを作ることが可能。
上記をREPLなどで評価すると止まらない。(計算機のリソースが尽きるまで)

take 5 [1..] -- [1,2,3,4,5]

takeなどを使って、(無限)リストの一部だけを取り出せる。

repeat / cycle / replicate

take 5 (repeat 3) -- [3,3,3,3,3]
take 5 (cycle [1, 2, 3]) -- [1,2,3,1,2]
replicate 3 10 -- [10,10,10]

repeatは指定された値、cycleは指定されたリスト、を繰り返す無限リストを生成。
replicateは指定された長さと要素で(無限ではない)リストを作成する。

リスト内包表記

基本

[ x * 2 | x <- [1..10] ] -- [2,4,6,8,10,12,14,16,18,20]

リストの「フィルタリング」「変換」「組み合わせ」を行う方法の1つ。
[ 出力値 | 束縛変数 <- リスト ]という形が基本形で、これだけであればmap関数に近い。

フィルタ

[ x * 2 | x <- [1..10], x > 5 ] -- [12,14,16,18,20]

,の後に述語を指定することでフィルタできる。
[ 出力値 | 束縛変数 <- リスト, フィルタ ]

組み合わせ

[ x + y | x <- [1, 2, 3], y <- [10, 100] ] -- [11,101,12,102,13,103]

2つのリストの要素を束縛して処理した場合、全組み合わせが計算される。
[ 出力値 | 束縛変数1 <- リスト1, 束縛変数2 <- リスト2 ]

[ x + y | x <- [1, 2, 3], y <- [10, 100], (x + y) > 100 ] -- [101,102,103]

前述したフィルタも一緒に使える。
[ 出力値 | 束縛変数1 <- リスト1, 束縛変数2 <- リスト2, フィルタ ]

ちなみにフィルタのことを「述語」と呼んだりする。(他の言語でも)

タプル

基本

(1, 2)
(1, 'A', "Foo")

値の組を表現するときに使う。
リストと違って異なる型でもOK。

2つ以上の組み合わせで、1つの値を表現したい時に使うイメージ。

fst / snd

fst (1, 'A') -- 1
snd (1, 'A') -- 'A'

要素数が2つのタプル(ペア)限定で、fstで1つ目、sndで2つ目、の要素が取得できる。

zip

zip [1, 2, 3] ['A', 'B', 'C'] -- [(1,'A'),(2,'B'),(3,'C')]

2つのリストを受け取って、それをタプル(ペア)のリストに結合する。

カバンとかジャケットとかの「ジッパー」を「閉める」ときの様子をイメージすると直感的。
(JOJO好きには「スティッキー・フィンガーズ」の逆バージョンと言えばOKかも)

文字列

"Hello"
'H' : 'e' : 'l' : 'l' : 'o' : []

文字列は""、文字は''で囲む。
Haskellにおいて「文字列」は「文字のリスト」なので、上記のコードは等価。

27
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
22