Haskell勉強メモ
HaskellでHello Worldをするまでの手順をメモ
環境
- Haskell 9.0.1
- WSL2
- Docker 19.03.13
WSL2環境にインストールしようと思っていたが、Dockerイメージがあるので、そちらを利用することにした。
Dockerイメージをpull (今回のバージョンは9にする)
$ docker pull haskell:9
イメージが重い(300MBくらい)なので、しばらく待つ。
プルが完了したら、コンテナを起動。
$ docker run -it --rm haskell:9
GHCi, version 9.0.1: https://www.haskell.org/ghc/ :? for help
ghci>
(Dockerのおかげで早く完了。)
GHCiとは、HaskellのコンパイラGHCの対話環境のこと。
GHCi上で、"Hello, World!"と打てば完了!!
ghci> "Hello, World!"
"Hello, World!"
ghciを終了する場合 :q
を打つ
ghci> :q
Leaving GHCi.
VSCodeのRemote Container拡張機能を利用する場合
以下のようなdevcontainer.jsonを作成し、VSCodeを立ち上げる。
{
"name": "HaskellDevContainer",
"image": "haskell:9",
"extensions": [
"haskell.haskell"
],
}
ただし、コンテナ内で生成したファイルの権限に注意。WSL内でrootユーザーではないユーザーはファイルの所有権がない。
→面倒なので、Remote Containerは使わないことにした。
docker-composeを使ってコンテナを立ち上げ、WSLから直接コンテナ内に入ることにした。
HaskellのVSCode拡張機能を利用するためにはGHCをWSLにインストールする必要があるので、やはりRemote Containerを利用したほうが良さそう。
ということで、
コーディングはRemote Containerを使ってコンテナ内で行い、
Git pushはWSLのターミナルから行うことにした。(SSHキーはrootユーザーから権限がないため)
ファイルの作成&コンパイル&実行
ファイル拡張子は、.hs
にする。
doubleMe x = x + x
GCHi上で:l
コマンドでコンパイル→実行できる状態になる
ghci> :l <filename>
実行
ghci> doubleMe 10
20
Haskellの文法について
if文にはelseが必須
doubleSmalNumber x = if x > 100
then x
else x*2
すべての関数は何かしらの値を返却する必要があるため。
Haskellのif
は値を返す式であり、文ではない。
リスト
同じ型の要素を複数個格納できる。
list = [1,2,3,4]
++
)
リストの連結(ghci> [1,2,3,4] ++ [5,6,7,8]
[1,2,3,4,5,6,7,8]
ghci> "hello" ++ " " ++ "world"
"hello world"
※文字列は文字のリストとして表される
リストの要素へのアクセス
先頭からの位置で要素を取得したい際は !!
を用いる
[リスト] !! インデックス(0スタート)
ghci> [0,1,2,3] !! 1
1
最大インデックスを超えるとエラーになる
ghci> [0,1,2,3] !! 4
*** Exception: Prelude.!!: index too large
リストの比較
<, <=, >=, >
を使って2つのリストを比較することができる。
各要素を 辞書順 で比較される。
例えば、 [1,2,3] > [1,2,2]
のように比較する場合、
- 0番目の要素(1と1)を比較する→等価なので次の要素へ
- 1番目の要素(2と2)を比較する→等価なので次の要素へ
- 2番めの要素(3と2)を比較する→
3>2
である - よって
[1,2,3] > [1,2,2]
は Trueである
リストを扱う関数
- head: リストの先頭要素を返却する
- tail: 先頭を除く残りのリストを返却する
- last リストの最後の要素を返却する
- init 最後の要素を除く残りのリストを返却する
ghci> head [1,2,3]
1
ghci> tail [1,2,3]
[2,3]
ghci> last [1,2,3]
3
ghci> init [1,2,3]
[1,2]
headに空のリストを渡すとエラーになるので注意
ghci> head []
*** Exception: Prelude.head: empty list
- length: リストの長さを返却する
- null: リストが空であるかを返却する(空ならTrue)
- reverse: リストを逆順にする
- take: 数とリストを受け取る→先頭から指定した数の要素を取り出したリストを返却する
- drop: 数とリストを受け取る→先頭から指定した数の要素を削除したリストを返却する
ghci> reverse [1,2,3]
[3,2,1]
ghci> length [1,2,3]
3
ghci> null []
True
ghci> null [1]
False
ghci> reverse [1,2,3]
[3,2,1]
ghci> take 2 [1,2,3]
[1,2]
ghci> drop 1 [1,2,3]
[2,3]
takeとreverseを組み合わせてみる
ghci> take 2 (reverse [1,2,3])
[3,2]
- maximum: 最大の要素を返却する(何らかの順序が定義された要素からなるリストを受け取る)
- minimum: 最小の要素を返却する(何らかの順序が定義された要素からなるリストを受け取る)
- sum: 数のリストを受け取り、総和を返却する
- product: 数のリストを受け取り、積を返却する
ghci> maximum [1,2,3]
3
ghci> maximum "Hello, World!"
'r'
ghci> minimum [1,2,3]
1
ghci> minimum "Hello, World!"
' '
日本語が入る場合
ghci> maximum "Hello, World!あ"
'\12354'
文字は文字コードの順で判断されるようだ。
リスト内包表記
リストのフィルタリング、変換、組み合わせを行う方法。数学における集合の内包的記法の概念に近い。
例えば、
をHaskellで表すと、
ghci> [ x*2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]
となる。
他には、「10以上の奇数を"BANG!"に置き換え、10より小さいすべての奇数を"BOOM!"に置き換える内包表記を考える。関数を用いると、
boomBang xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]
となり、実行すると
ghci> boomBang [1..20]
["BOOM!","BOOM!","BOOM!","BOOM!","BOOM!","BANG!","BANG!","BANG!","BANG!","BANG!"]
タプル
複数の違う型の要素を格納して、1つの値として扱うことができる。
リストとは異なる性質
- 複数の違う型の要素を格納できる。(例:
(1,3), (1, 'a')
) - サイズが固定されている。(例:
[(1,2), (1,2,3), (1,2)]
はエラー)
直角三角形を見つける
条件
- 3辺の長さはすべて整数である。
- 各辺の長さは10以下である。
- 周囲の長さは24に等しい。
ghci> let rightTriangles' = [ (a,b,c) | c <- [1..10], a <- [1..c], b <- [1..b], a^2 + b^2 == c^2, a + b + c == 24]
ghci> rightTriangles'
[(8,6,10)]
最初に解の候補となる集合を生成し、解にたどり着くまで変換とフィルタリングを行う手法は、関数プログラミングでよく用いられるパターン、とのこと。
Haskellの型
基本的な型は以下。
- Int: 整数
- Float: 単精度浮動小数点数
- Double: 倍精度浮動小数点数
- Bool: 真理値型。
True
とFalse
- Char: Unicode文字
型変数
型の情報を表す変数。他の言語のジェネリクスに近い。
ghci> :t head
head :: [a] -> a
a
が型変数。ここでは任意の型を表している。
→headという関数は、任意の型のリストを受け取って、任意の型を返却するということを表している。