前回はこちら。
[第2回〜第5回の様子]
2023/09/13に第2回、2023/09/20に第3回、2023/09/27に第4回、2023/10/04に第5回を開催した。
第2回は残念ながら自分は欠席してしまったので詳細はわからないが、Slackでの様子から、おそらく5人以上は参加していたはず。第3回は9人、第4回は6人、第5回は6人以上が参加していた。
第1回が異様に多いのはどの勉強会でもだいたいそうなのだけど、内容がやや難しいからか収束するのが早い気がする...。年末まで何人残るだろうか。
今回は、自分以外のメンバーもSlack上で学んだ内容をまとめて発信してくれていたりしてよい(このブログに書く内容を思い出す的な意味で)。
各回の学習内容は以下の通り。
- 第2回:「3. はじめの一歩」1. Hello World ~ 6. 関数
- 第3回:「3. はじめの一歩」7. 識別子 〜 8. 多相型
- 第4回:「3. はじめの一歩」8. 多相型 〜「4. 基本的なデータ型」7. ユニット型
- 第5回:「4. 基本的なデータ型」7. ユニット型 〜「5. 式と宣言」1. 変数の束縛
[学んだこと]
- 3. はじめの一歩
- hello worldはこんな感じ
-- hello.hs main = putStrLn "hello, world"
- 変数を使ったり、出力を複数回繰り返したりすると以下のようになる
-- コメントはこのように書く -- 変数の定義順は関係ない helloworld :: String helloworld = hello ++ world world :: String world = "World" -- Cにおけるmain関数と同様に -- Haskellでもmainという名前がプログラムのエントリポイントとなる main :: IO () main = do putStrLn hello putStrLn helloworld putStrLn "goodbye" -- 変数 -- 型は推論されるので書かなくてもよいが、書いておいた方がよい hello :: String hello = "Hello"
- ブロックコメントも以下のように書ける
main = putStrLn "hello, world" {- ブロックコメントは {- ネストさせてもよい -} -}
- 先ほどのメイン関数はブロックを使って書くこともできる
main = do { putStrLn "hello" ; putStrLn "world" ; putStrLn "goodbye" } -- 1行に書く場合は括弧を省略できる main = do putStrLn "hello" ; putStrLn "world" ; putStrLn "goodbye"
- 関数は
関数名 引数1 ... 引数n = 式
のように書く。呼び出す(適用する)場合も同じように書く。
main :: IO () main = do print $ add 10 20 -- 30が出力される -- 関数も型は推論されるので書かなくてもOK -- ちなみに型を書かないと Num a => a -> a -> a みたいになる -- add 10 20.0 とかはコンパイルエラーになる add :: Int -> Int -> Int add x y = x + y {- add :: _ add x y = x + y ↑こんな感じで定義して、 main = do print $ add 10 20 を実行すると、コンパイルエラーのメッセージでaddの型の推論結果を見れる -}
- 小文字で始まるのが変数識別子:関数、変数、型変数など。
- アンダースコア始まりも可。
- 大文字で始まるのが構成子識別子:型クラス、モジュール、データ構成子など。
- どちらも数字、シングルクオート、アンダースコアを利用可能。
- 多相型:polymorphic type。ジェネリクスみたいなやつ。
fst :: (a, b) -> a
のa, bが多相型の型変数- 同じ名前の型変数には同じ具体型が入る
-- 配列の先頭の要素を取り出すhead'関数を定義するとこんな感じ head' :: [a] -> Maybe a head' [] = Nothing head' (x:xs) = Just x -- xsは使わないので head' (x:_) = Just x でもOK -- haskellにはforやwhileがないので再帰を利用する -- 合計を求めるsum'関数はこんな感じ sum' :: [Int] -> Int sum' [] = 0 sum' (x:xs) = x + sum' xs -- n番目の要素を取得する関数 at :: Int -> [a] -> Maybe a at _ [] = Nothing at 0 (x:_) = Just x at n (x:xs) = at (n - 1) xs
- 4. 基本的なデータ型
- 論理型BoolはTrueかFalse
not True
でFalse
になる。- あとは大体他の言語と似たような感じ。
x /= y
で、xとyが違う値だったらTrueになる(2 /= 3
はTrue)
- 数値型は整数型と浮動小数点数型がある。
negate x
で-xと同じ意味。x `div` y
で除算、x `mod` y
で剰余
- 文字型Charはユニコード文字、シングルクオートを使う
- 文字列型Stringは実態は文字のリスト、[Char]の別名。
- 文字列にはリストと同じ関数を利用できる
- タプル型:(値1,...,値n)の形
- リスト型:[値1, ..., 値n]の形。空リストのリテラルは[]
- 連結リストであり、線形にアクセス時間がかかる
- [1] ++ [2, 3]とやると[1, 2, 3]のように連結できる
- [1, 2, 3] !! 1とやると1番目の要素2が返る
- Maybe型:値が存在しないかもしれない時に使う。NothingかJust x
- RustのResult(OkかErrになるやつ)と似てる
- Either型もあるLeftかRightになって、Rightに文字列でエラー詳細が返ってくるような時に使う
- 5. 式と宣言
- 変数を値に結びつけることをバインディング(束縛)という
- 変数は使う場所より前に書く必要はない
- 同名の変数の再定義は不可能
-- これはコンパイルエラー n = 123 n = 456
- ただし、letやwhereを用いてシャドウイングは可能
- letを使う場合は以下のように書く
m = 123 main :: IO () main = do -- 123を出力 print m -- シャドウイング let m = 789 -- 789を出力 print m
- whereを使う場合は以下のように書く
m = 1 main :: IO () main = do -- 2が出力される print m where m = 2
[まとめ]
インデントを揃えて書くPythonっぽさもありつつ、ブロックの書き方とか演算子が独特な感じもあり、慣れるのに時間がかかりそう...。
SlackにまとめてくれたSさんありがとうございます。