Open19

Haskell勉強メモ

Yuma ItoYuma Ito

HaskellでHello Worldをするまでの手順をメモ

環境

  • Haskell 9.0.1
  • WSL2
  • Docker 19.03.13
Yuma ItoYuma Ito

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のおかげで早く完了。)

Yuma ItoYuma Ito

GHCi上で、"Hello, World!"と打てば完了!!

ghci>  "Hello, World!"
"Hello, World!"
Yuma ItoYuma Ito

VSCodeのRemote Container拡張機能を利用する場合

以下のようなdevcontainer.jsonを作成し、VSCodeを立ち上げる。

.devcontainer/devcontainer.json
{
	"name": "HaskellDevContainer",
	"image": "haskell:9",
	"extensions": [
		"haskell.haskell"
	],
}

https://code.visualstudio.com/docs/remote/devcontainerjson-reference

Yuma ItoYuma Ito

HaskellのVSCode拡張機能を利用するためにはGHCをWSLにインストールする必要があるので、やはりRemote Containerを利用したほうが良さそう。

ということで、
コーディングはRemote Containerを使ってコンテナ内で行い、
Git pushはWSLのターミナルから行うことにした。(SSHキーはrootユーザーから権限がないため)

Yuma ItoYuma Ito

ファイルの作成&コンパイル&実行

ファイル拡張子は、.hsにする。

doubleMe x = x + x

GCHi上で:lコマンドでコンパイル→実行できる状態になる

ghci> :l <filename>

実行

ghci> doubleMe 10
20
Yuma ItoYuma Ito

Haskellの文法について

Yuma ItoYuma Ito

if文にはelseが必須

doubleSmalNumber x = if x > 100
                        then x
                        else x*2

すべての関数は何かしらの値を返却する必要があるため。
Haskellのifは値を返す式であり、文ではない。

Yuma ItoYuma Ito

リスト

同じ型の要素を複数個格納できる。

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"

※文字列は文字のリストとして表される

Yuma ItoYuma Ito

リストの要素へのアクセス

先頭からの位置で要素を取得したい際は !! を用いる

[リスト] !! インデックス(0スタート)

ghci> [0,1,2,3] !! 1
1

最大インデックスを超えるとエラーになる

ghci> [0,1,2,3] !! 4
*** Exception: Prelude.!!: index too large
Yuma ItoYuma Ito

リストの比較

<, <=, >=, >を使って2つのリストを比較することができる。
各要素を 辞書順 で比較される。

例えば、 [1,2,3] > [1,2,2]のように比較する場合、

  1. 0番目の要素(1と1)を比較する→等価なので次の要素へ
  2. 1番目の要素(2と2)を比較する→等価なので次の要素へ
  3. 2番めの要素(3と2)を比較する→3>2である
  4. よって [1,2,3] > [1,2,2]は Trueである
Yuma ItoYuma Ito

リストを扱う関数

  • 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'

文字は文字コードの順で判断されるようだ。

Yuma ItoYuma Ito

リスト内包表記

リストのフィルタリング、変換、組み合わせを行う方法。数学における集合の内包的記法の概念に近い。

例えば、

\lbrace 2 \cdot x | x \in N, x \leq 10 \rbrace

を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!"]
Yuma ItoYuma Ito

タプル

複数の違う型の要素を格納して、1つの値として扱うことができる。

リストとは異なる性質

  1. 複数の違う型の要素を格納できる。(例: (1,3), (1, 'a')
  2. サイズが固定されている。(例: [(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)]

最初に解の候補となる集合を生成し、解にたどり着くまで変換とフィルタリングを行う手法は、関数プログラミングでよく用いられるパターン、とのこと。

Yuma ItoYuma Ito

Haskellの型

基本的な型は以下。

  • Int: 整数
  • Float: 単精度浮動小数点数
  • Double: 倍精度浮動小数点数
  • Bool: 真理値型。TrueFalse
  • Char: Unicode文字

型変数

型の情報を表す変数。他の言語のジェネリクスに近い。

ghci> :t head
head :: [a] -> a

aが型変数。ここでは任意の型を表している。
→headという関数は、任意の型のリストを受け取って、任意の型を返却するということを表している。