Haskell 言語仕様ガイド

他言語の経験者が 2〜3 時間でざっと読み通せるリファレンスです。純粋関数型・遅延評価・型クラス・モナドなど Haskell 固有の概念を重点的に解説します。

GHC 9.x / Haskell 2010経験者向けコード例付き
01

値・式・不変性

基礎
定義とバインディング基礎

Haskell に変数はなく、すべては「定義」。一度バインドされた値は変わらない。let ... inwhere でローカル定義。

Haskell
x :: Int
x = 42

-- ローカル定義
result :: Double
result = let r = 5.0
             pi = 3.14159
         in pi * r * r

-- where 節
circleArea r = pi * r * r
  where pi = 3.14159
基本型基礎

Int(固定長整数)・Integer(任意精度整数)・DoubleBoolCharString(= [Char])。型注釈は :: で記述。

Haskell
x :: Int
x = 42
y :: Integer
y = 2 ^ 100           -- 任意精度
z :: Double
z = 3.14
b :: Bool
b = True
c :: Char
c = 'A'
s :: String
s = "hello"           -- [Char] と同じ
遅延評価基礎

Haskell はデフォルトで遅延評価(非正格)。式は必要になるまで評価されない(thunk)。無限リストも定義できる。

Haskell
nats :: [Int]
nats = [1..]          -- 無限リスト(遅延)

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

ones :: [Int]
ones = 1 : ones       -- 無限の 1 のリスト
演算子と優先順位基礎

演算子は関数。バッククォートで関数を中置演算子として使える(x div y)。infixlinfixr で優先順位を定義可能。

Haskell
div 10 3         -- 3(前置)
10 `div` 3      -- 3(中置)
(+) 1 2          -- 3(演算子を前置)
1 + 2 * 3        -- 7(* が高優先)
$                -- 最低優先度の関数適用
print $ 1 + 2   -- print (1+2)
02

制御フロー・パターンマッチ

基礎
パターンマッチ基礎

Haskell のパターンマッチは関数定義に直接書ける(複数節)。case ... of で式としても使える。

Haskell
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

describe :: Int -> String
describe x = case x of
    0 -> "zero"
    1 -> "one"
    _ -> "other"
ガード節基礎

| でガード節を書く。otherwise は常に真の条件(True と同じ)。パターンマッチとガードを組み合わせられる。

Haskell
bmi :: Double -> String
bmi b
    | b <= 18.5 = "Underweight"
    | b <= 25.0 = "Normal"
    | b <= 30.0 = "Overweight"
    | otherwise = "Obese"
if 式基礎

Haskell の if は式であり else 節は必須(両分岐の型が一致する必要がある)。

Haskell
abs' :: Int -> Int
abs' n = if n < 0 then -n else n

-- 多段は guard の方が読みやすい
max' a b = if a > b then a else b
where と let...in基礎

where は定義の後に書くローカル定義節(関数全体のスコープ)。let...in は式の中で使うローカル定義。

Haskell
hypotenuse :: Double -> Double -> Double
hypotenuse a b = sqrt (sq a + sq b)
  where sq x = x * x

-- let in
compute :: Int -> Int
compute n = let x = n * 2
                y = x + 1
            in x * y
03

関数・高階関数

基礎
カリー化と部分適用基礎

Haskell のすべての関数は自動でカリー化。f a b(f a) b。引数を一部渡して部分適用した関数を作れる。

Haskell
add :: Int -> Int -> Int
add x y = x + y

add5 :: Int -> Int
add5 = add 5        -- 部分適用

map (* 2) [1,2,3]  -- [2,4,6]
filter (> 3) [1..5] -- [4,5]
ラムダ式基礎

\x -> expr でラムダ式(無名関数)を定義。バックスラッシュが λ の代替。

Haskell
double :: [Int] -> [Int]
double = map (\ x -> x * 2)

-- セクション(演算子の部分適用)
map (* 2) [1,2,3]       -- [2,4,6]
map (subtract 1) [1,2,3] -- [0,1,2](1を引く)
関数合成 (.)基礎

.(ドット)で関数を合成。(f . g) x = f (g x)$ は最低優先度の関数適用で括弧を省略できる。

Haskell
import Data.Char (toUpper)
import Data.List (intercalate)

titleCase :: String -> String
titleCase = intercalate " "
          . map (\(h:t) -> toUpper h : t)
          . words

-- $の使用
print $ map (* 2) [1..5]   -- print (map (* 2) [1..5])
map・filter・fold基礎

Haskell の中核的な高階関数。foldr は右結合で遅延評価と相性が良い。foldl'(正格版)が大きなリストに推奨。

Haskell
map    (+ 1)   [1,2,3]   -- [2,3,4]
filter even    [1..10]   -- [2,4,6,8,10]
foldl  (+) 0   [1..5]    -- 15
foldr  (:) []  [1,2,3]   -- [1,2,3](リストの再構成)
scanl  (+) 0   [1..5]    -- [0,1,3,6,10,15]
04

リスト・タプル

基礎
リスト基礎

連結リスト。[] が空リスト、: が先頭追加(cons)。パターンマッチで (x:xs) と分割できる。

Haskell
[1, 2, 3]         -- リストリテラル
1 : [2, 3]         -- 先頭追加 → [1,2,3]
[1,2] ++ [3,4]     -- 結合 → [1,2,3,4]
head [1,2,3]       -- 1
tail [1,2,3]       -- [2,3]
null []            -- True(空判定)
length [1,2,3]     -- 3
リスト内包表記基礎

[ expr | x <- list, condition ] の形式でリストを生成。SQL の SELECT に相当。

Haskell
[ x^2 | x <- [1..10] ]
-- [1,4,9,16,25,36,49,64,81,100]

[ (x,y) | x <- [1..3], y <- [1..3], x /= y ]
-- [(1,2),(1,3),(2,1),(2,3),(3,1),(3,2)]

evens = [ x | x <- [1..20], even x ]
タプル基礎

固定長・異種型のデータ構造。fstsnd でペアの要素を取得。パターンマッチで分割。

Haskell
(1, "hello", True) :: (Int, String, Bool)
fst (1, 2)           -- 1
snd (1, 2)           -- 2
let (x, y) = (3, 4)  -- パターン分割
zip [1,2,3] "abc"    -- [(1,'a'),(2,'b'),(3,'c')]
文字列処理基礎

String = [Char] は便利だが遅い。本番では Data.Text(テキスト)・Data.ByteString(バイナリ)を使う。

Haskell
words "hello world"          -- ["hello","world"]
unwords ["hello","world"]    -- "hello world"
lines "a\nb\nc"             -- ["a","b","c"]
unlines ["a","b"]            -- "a\nb\n"

import Data.Char (toUpper)
map toUpper "hello"          -- "HELLO"
05

型定義

基礎
data 型(代数的データ型)基礎

data で代数的データ型を定義。直和型(sum type)と直積型(product type)の組み合わせ。

Haskell
-- 直和型(判別共用体)
data Color = Red | Green | Blue

-- 直積型(レコード)
data Point = Point { x :: Double, y :: Double }

-- 組み合わせ
data Shape
    = Circle { radius :: Double }
    | Rect   { width :: Double, height :: Double }

area :: Shape -> Double
area (Circle r)     = pi * r * r
area (Rect w h)     = w * h
newtype基礎

newtype は単一フィールドを持つラッパー型。実行時コストはゼロ(コンパイル後は内部型と同一)。型安全な区別が目的。

Haskell
newtype Name  = Name  String
newtype Email = Email String

createUser :: Name -> Email -> String
createUser (Name n) (Email e) = n ++ " <" ++ e ++ ">"

-- createUser (Email "x@y") (Name "Bob")  -- 型エラー
type エイリアス基礎

type は型の別名。String = [Char] が代表例。新しい型ではなく、同じ型を別の名前で呼ぶだけ。

Haskell
type Name   = String
type Age    = Int
type Person = (Name, Age)

greet :: Person -> String
greet (n, a) = n ++ " is " ++ show a ++ " years old"
Maybe と Either基礎

Maybe aJust a または Nothing(null の型安全な代替)。Either e aLeft e(エラー)または Right a(成功)。

Haskell
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv a b = Just (a `div` b)

safeDiv 10 2   -- Just 5
safeDiv 10 0   -- Nothing

-- Either でエラー情報を持つ
safeSqrt :: Double -> Either String Double
safeSqrt x
    | x < 0    = Left "Negative input"
    | otherwise = Right (sqrt x)
06

例外・IO

基礎
IO 型と do 記法基礎

副作用は IO a 型で管理する。do 記法で手続き的に書ける。<- で IO から値を取り出す。return は値を IO にラップ(Haskell の return は C の return とは違う)。

Haskell
main :: IO ()
main = do
    putStr "Name: "
    name <- getLine
    putStrLn ("Hello, " ++ name ++ "!")
    let greeting = "Welcome, " ++ name
    putStrLn greeting
例外処理基礎

Control.Exception で例外を扱う。catchtrythrowIOIOException はファイル I/O エラーに使う。

Haskell
import Control.Exception

main :: IO ()
main = do
    result <- try (readFile "missing.txt") :: IO (Either IOException String)
    case result of
        Right content -> putStrLn content
        Left e        -> putStrLn ("Error: " ++ show e)
interact と getContents基礎

interact は stdin 全体を受け取って変換する簡便関数。getContents で遅延読み込み可能。

Haskell
-- 行を逆順にするプログラム
main :: IO ()
main = interact (unlines . reverse . lines)
07

型クラス

基礎
型クラスの定義と実装基礎

型クラスは「ある型が持つべきインターフェース」を定義する。class で定義し、instance で実装。

Haskell
class Describable a where
    describe :: a -> String

data Color = Red | Green | Blue

instance Describable Color where
    describe Red   = "red color"
    describe Green = "green color"
    describe Blue  = "blue color"

describe Red   -- "red color"
主要な標準型クラス基礎

Eq(等値)・Ord(順序)・Show(文字列化)・Read(解析)・Num(数値演算)・Functor(fmap)など。deriving で自動導出。

Haskell
data Point = Point Double Double
    deriving (Show, Eq, Ord)

p = Point 1.0 2.0
show p           -- "Point 1.0 2.0"
p == Point 1.0 2.0  -- True

-- Functor の instance
instance Functor ((,) a) where
    fmap f (x, y) = (x, f y)
Functor と fmap基礎

Functor はコンテキストの中の値を変換する型クラス。fmap が核心メソッド。<$> は中置版の fmap

Haskell
fmap (+1) (Just 5)      -- Just 6
fmap (+1) Nothing       -- Nothing
fmap (*2) [1,2,3]       -- [2,4,6](map と同じ)
fmap length (Just "hi") -- Just 2

(+1) <$> Just 5        -- Just 6(中置)
deriving と Generic応用

derivingShowEqOrdGeneric を自動導出。GHC.Generics と組み合わせてシリアライズ(aeson など)を自動化できる。

Haskell
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics (Generic)
import Data.Aeson   (ToJSON, FromJSON)

data User = User { name :: String, age :: Int }
    deriving (Show, Eq, Generic)

instance ToJSON  User
instance FromJSON User
08

Applicative・Monad

応用
Applicative応用

Applicative は文脈の中の関数を文脈の中の値に適用する。<*>pure(値をコンテキストに入れる)が核心。

Haskell
pure (+3) <*> Just 5       -- Just 8
Just (+3) <*> Just 5       -- Just 8
Nothing   <*> Just 5       -- Nothing
[(+1),(* 2)] <*> [10,20]   -- [11,21,20,40]
Monad と >>= 応用

Monad はコンテキストの計算をチェーンする。>>=(bind)は左辺のコンテキストから値を取り出して関数に渡す。do 記法はシンタックスシュガー。

Haskell
-- Maybe モナド
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv a b = Just (a `div` b)

compute :: Maybe Int
compute = do
    x <- safeDiv 10 2   -- Just 5
    y <- safeDiv x  0   -- Nothing
    return (x + y)      -- Nothing(途中で失敗)
List モナド応用

リストはモナドとして非決定性計算を表す。>>=concatMap に相当。guard で条件フィルタリング。

Haskell
import Control.Monad (guard)

pythagorean :: Int -> [(Int,Int,Int)]
pythagorean n = do
    a <- [1..n]
    b <- [a..n]
    c <- [b..n]
    guard (a^2 + b^2 == c^2)
    return (a, b, c)

pythagorean 20  -- [(3,4,5),(5,12,13),(6,8,10),(8,15,17)]
State モナド応用

State s a は状態を引き回す計算をカプセル化。getputmodify で状態を操作。純粋関数型で可変状態をシミュレート。

Haskell
import Control.Monad.State

counter :: State Int Int
counter = do
    n <- get
    put (n + 1)
    return n

runState (do { counter; counter; counter }) 0
-- (2, 3)  ← 最後の戻り値=2, 最終状態=3
09

ジェネリクス・型レベルプログラミング

応用
パラメトリック多相基礎

型変数(ab)で任意の型に対して動作するジェネリック関数を定義。

Haskell
id :: a -> a
id x = x

const :: a -> b -> a
const x _ = x

flip :: (a -> b -> c) -> b -> a -> c
flip f y x = f x y
Kind(型の型)応用

型の型を「kind」と呼ぶ。Int の kind は *(= Type)、Maybe の kind は * -> *(型を受け取って型を返す)。

Haskell
-- :kind コマンドで確認(GHCi)
-- :k Int     → Type
-- :k Maybe   → Type -> Type
-- :k Either  → Type -> Type -> Type
-- :k Functor → (Type -> Type) -> Constraint
GADT応用

一般化代数データ型。コンストラクタごとに異なる型索引を付けられる。型安全な AST の表現などに使う。

Haskell
{-# LANGUAGE GADTs #-}
data Expr a where
    Lit  :: Int          -> Expr Int
    Bool :: Bool         -> Expr Bool
    Add  :: Expr Int -> Expr Int -> Expr Int
    If   :: Expr Bool -> Expr a -> Expr a -> Expr a

eval :: Expr a -> a
eval (Lit n)    = n
eval (Bool b)   = b
eval (Add x y)  = eval x + eval y
eval (If p t f) = if eval p then eval t else eval f
10

よく使うモナドとトランスフォーマー

応用
Reader モナド応用

Reader r a は読み取り専用の環境 r を引き回す計算。依存注入や設定の受け渡しに使う。

Haskell
import Control.Monad.Reader

data Config = Config { host :: String, port :: Int }

getUrl :: Reader Config String
getUrl = do
    h <- asks host
    p <- asks port
    return $ h ++ ":" ++ show p

runReader getUrl (Config "localhost" 8080)
-- "localhost:8080"
Writer モナド応用

Writer w a はログなどの出力を蓄積しながら計算を進める。tell でログを追記する。

Haskell
import Control.Monad.Writer

loggedAdd :: Int -> Int -> Writer [String] Int
loggedAdd x y = do
    tell ["Adding " ++ show x ++ " and " ++ show y]
    return (x + y)

runWriter (loggedAdd 3 4)
-- (7, ["Adding 3 and 4"])
モナドトランスフォーマー(mtl)応用

StateTReaderTExceptT でモナドをスタックし、複数の効果を組み合わせる。lift で下位のモナドの操作を持ち上げる。

Haskell
import Control.Monad.State
import Control.Monad.Except

type App a = ExceptT String (State Int) a

increment :: App ()
increment = do
    n <- lift get
    if n >= 10 then throwError "Limit reached"
    else lift $ put (n + 1)

runState (runExceptT increment) 0
-- (Right (), 1)
11

IO と副作用の管理

基礎
IORef・STRef(ミュータブル参照)基礎

IORef a は IO の中で使えるミュータブル参照。ST モナドは純粋計算内でのみ使えるミュータブル状態。

Haskell
import Data.IORef

main :: IO ()
main = do
    ref <- newIORef (0 :: Int)
    modifyIORef ref (+1)
    modifyIORef ref (+1)
    val <- readIORef ref
    print val   -- 2
並行処理(STM・async)応用

STM(Software Transactional Memory)は TVar を使ったデッドロックフリーの並行状態管理。async ライブラリで並行タスクを管理。

Haskell
import Control.Concurrent.STM
import Control.Concurrent.Async

main :: IO ()
main = do
    counter <- newTVarIO (0 :: Int)
    let increment = atomically $ modifyTVar' counter (+1)
    (a, b) <- concurrently (mapM_ (\_ -> increment) [1..1000])
                            (mapM_ (\_ -> increment) [1..1000])
    final <- readTVarIO counter
    print final  -- 2000
unsafePerformIO の危険性応用

unsafePerformIO は IO を純粋関数として実行する「脱出ハッチ」。型システムを破るため、誤用するとバグの原因になる。使用は極めて限定的にする。

Haskell
import System.IO.Unsafe
-- 注意: これは例示。基本的に使うべきではない
globalRef :: IORef Int
globalRef = unsafePerformIO (newIORef 0)
{-# NOINLINE globalRef #-}
unsafePerformIO は参照透過性を破壊する。FFI やグローバルな IORef の初期化など、安全性が保証できる場合のみ使用すること。
12

ファイル・テキスト処理

基礎
ファイル操作基礎

readFilewriteFileappendFile で基本的なファイル操作。System.IOHandle で詳細制御。

Haskell
import System.IO

main :: IO ()
main = do
    content <- readFile "input.txt"
    let processed = map toUpper content
    writeFile "output.txt" processed
    appendFile "log.txt" "done\n"
Data.Text と Data.ByteString基礎

Data.Text は Unicode テキスト処理に最適化。Data.ByteString はバイナリデータ用。本番コードでは String より推奨。

Haskell
import qualified Data.Text    as T
import qualified Data.Text.IO as TIO

main :: IO ()
main = do
    content <- TIO.readFile "input.txt"
    let upper = T.toUpper content
    TIO.putStrLn upper
JSON(aeson)基礎

aeson は Haskell の標準 JSON ライブラリ。Generic と組み合わせてボイラープレートなしで JSON 変換できる。

Haskell
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
import Data.Aeson

data User = User { name :: String, age :: Int }
    deriving (Show, Generic)
instance ToJSON  User
instance FromJSON User

-- encode (User "Alice" 30) → "{"name":"Alice","age":30}"
13

ビルドツール・エコシステム

基礎
cabal と Stack基礎

cabal(Haskell の標準ビルドツール)と stack(再現性を重視したビルドツール)の2つが主流。初学者には cabal が推奨(GHCup で GHC 管理)。

Haskell
# cabal
cabal init --non-interactive
cabal build
cabal run
cabal test

# stack
stack new my-project
stack build
stack run
HLS・GHCi基礎

HLS(Haskell Language Server)がほぼすべての IDE の LSP バックエンド。GHCi で対話的に試せる。:type:kind:info が特に便利。

Haskell
-- GHCi コマンド
ghci
> :type map          -- map :: (a -> b) -> [a] -> [b]
> :kind Maybe        -- Maybe :: Type -> Type
> :info Functor      -- 型クラスの定義と全 instance
> :set +s            -- 実行時間とメモリを表示
Hackage・Stackage基礎

Hackage は Haskell のパッケージリポジトリ。Stackage は検証済みのスナップショット(LTS・Nightly)を提供し、依存関係の整合性を保証。

Haskell
# cabal.project で依存追加
# build-depends: aeson >= 2.0, text, containers

# cabal で最新版を取得
cabal update
cabal install aeson
14

テスト・QuickCheck

基礎
HUnit基礎

HUnit は Haskell の xUnit スタイルテストフレームワーク。@?= で等値アサーション。

Haskell
import Test.HUnit

testAdd :: Test
testAdd = TestCase $ do
    assertEqual "1+1" 2 (1+1)
    assertEqual "head" 1 (head [1,2,3])

main :: IO ()
main = runTestTT testAdd >>= print
QuickCheck(プロパティテスト)基礎

プロパティ(性質)を定義し、ランダムな入力で自動テスト。Haskell 発祥で他言語にも移植された。反例が見つかると最小化して報告する。

Haskell
import Test.QuickCheck

prop_revRev :: [Int] -> Bool
prop_revRev xs = reverse (reverse xs) == xs

prop_sortOrdered :: [Int] -> Bool
prop_sortOrdered xs = isSorted (sort xs)
  where isSorted ys = all (uncurry (<=)) (zip ys (tail ys))

main :: IO ()
main = do
    quickCheck prop_revRev
    quickCheck prop_sortOrdered
Hspec基礎

Hspec は BDD スタイルのテストフレームワーク。describeitshouldBe で読みやすいテスト記述ができる。QuickCheck と統合可能。

Haskell
import Test.Hspec

spec :: Spec
spec = do
    describe "reverse" $ do
        it "reverses a list" $
            reverse [1,2,3] `shouldBe` [3,2,1]
        it "is involutory" $ property $
            \xs -> reverse (reverse xs) == (xs :: [Int])
15

高度な型システム機能

応用
TypeApplications応用

@Type で型変数に明示的に型を渡せる。型推論が曖昧な場合に型を明確に指定できる。

Haskell
{-# LANGUAGE TypeApplications #-}
read @Int "42"      -- 42 :: Int
show @Double 3.14   -- "3.14"
pure @Maybe 42      -- Just 42
DataKinds・型レベルプログラミング応用

DataKinds で値コンストラクタを型レベルに昇格。型レベルの自然数(Nat)やリスト('[])が使える。型安全な長さ付きベクターなどを実装できる。

Haskell
{-# LANGUAGE DataKinds, KindSignatures #-}
import GHC.TypeLits

data Vec (n :: Nat) a where
    VNil  :: Vec 0 a
    VCons :: a -> Vec n a -> Vec (n + 1) a

-- vHead :: Vec (n+1) a -> a(空ベクターは型エラー)
vHead :: Vec (1 + n) a -> a
vHead (VCons x _) = x
Profunctor・Lens応用

lens ライブラリでネストしたデータ構造の getter/setter を型安全に合成できる。viewoverset で操作。

Haskell
import Control.Lens

data Address = Address { _city :: String }
data Person  = Person  { _name :: String, _address :: Address }

makeLenses ''Address
makeLenses ''Person

alice = Person "Alice" (Address "Tokyo")
view (address . city) alice    -- "Tokyo"
over (address . city) (++ "!") alice  -- Address "Tokyo!"
16

パフォーマンス・実践

応用
正格評価(BangPatterns・seq)応用

遅延評価はサンクの蓄積でメモリを消費することがある。!(BangPatterns)・seqdeepseqfoldl' で正格評価を強制できる。

Haskell
{-# LANGUAGE BangPatterns #-}
import Control.DeepSeq

-- 正格な foldl'(サンク蓄積を防ぐ)
import Data.List (foldl')
sum' :: [Int] -> Int
sum' = foldl' (+) 0

-- BangPatterns で引数を正格評価
loop :: !Int -> Int -> Int
loop acc 0 = acc
loop acc n = loop (acc + n) (n - 1)
Criterion ベンチマーク応用

criterion は統計的に精密な Haskell のベンチマークライブラリ。WHNF(弱頭部正規形)と NF(正規形)の評価を区別して計測できる。

Haskell
import Criterion.Main

main :: IO ()
main = defaultMain
    [ bench "foldl'" $ whnf (foldl' (+) 0) [1..10000 :: Int]
    , bench "sum"    $ nf    sum             [1..10000 :: Int]
    ]
プロファイリング応用

GHC のプロファイリングフラグでメモリ・時間のコストを計測。+RTS -p -h でコスト集計とヒープグラフを生成。

Haskell
# プロファイリングビルド
cabal build --enable-profiling

# 実行時にプロファイリング
./my-program +RTS -p -h -RTS

# 生成ファイル
# my-program.prof  → コスト集計
# my-program.hp    → ヒープグラフ(hp2ps で可視化)
よく使う GHC 拡張応用

GHC 拡張で Haskell 標準の機能を拡張できる。OverloadedStringsLambdaCaseRecordWildCards は特に使用頻度が高い。

Haskell
{-# LANGUAGE OverloadedStrings  #-}  -- 文字列リテラルを多相的に
{-# LANGUAGE LambdaCase         #-}  -- \case で無名パターンマッチ
{-# LANGUAGE RecordWildCards    #-}  -- let {..} = record でフィールド展開
{-# LANGUAGE TupleSections      #-}  -- (,3) = \x -> (x,3)
{-# LANGUAGE ScopedTypeVariables #-} -- 型変数をスコープに持ち込む
🏠