kdnakt blog

hello there.

Haskell dojo第2回〜第5回のまとめ

前回はこちら。

kdnakt.hatenablog.com

[第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上で学んだ内容をまとめて発信してくれていたりしてよい(このブログに書く内容を思い出す的な意味で)。

各回の学習内容は以下の通り。

[学んだこと]

-- 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 TrueFalseになる。
    • あとは大体他の言語と似たような感じ。
    • x /= yで、xとyが違う値だったらTrueになる(2 /= 3はTrue)
  • 数値型は整数型と浮動小数点数型がある。
    • negate xで-xと同じ意味。
    • x `div` yで除算、x `mod` yで剰余
  • 文字型Charはユニコード文字、シングルクオートを使う
  • 文字列型Stringは実態は文字のリスト、[Char]の別名。
    • 文字列にはリストと同じ関数を利用できる
  • タプル型:(値1,...,値n)の形
    • 素数1のタプルはない。(123)はただのInt
    • 素数0のタプルはユニットと呼ぶ。()と書く。IO ()とかで出てきたのもユニット型。
  • リスト型:[値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さんありがとうございます。