Lecture

関数型プログラミング Ch1 イントロダクション

資料

はじめに

本講義では,関数型プログラミング言語Haskellの基礎,使用法,及び設計に関して扱います. 想定する履修者はPythonやJavaScriptなどの手続き型言語の使用経験はあるが,関数型言語を利用したことがない大学学部生です. 関数型言語の特徴を説明する際に手続き型言語の例としてPythonでの記述が出てきますが,Pythonの文法等に関しては既知のものとして扱います.(こちらはもとも官庁用の報告書として執筆したものを(大幅に)改変したものですので,もともとの資料ではVBAやJavaを事例として用いていました.)

また,本講義では代数学を利用したプログラミングの設計に関する方法論も扱います.集合論や代数学に関する知識は前提とせず,初歩から扱いますので,数学に関する前提知識は特に必要ありません. なお,本講義は集合論や代数学の習得を目的としているわけではないので,これらに関してはかなり簡略化した説明になります.専門的に数学を学びたい方向けの講義ではないことを理解したうえで受講してください.

こちらの資料では,授業に必要な技術的な内容に限定して掲載します. 授業概要,授業の注意点,成績等については講義中に別資料で説明します.

デザインについて

文章中で色の変わっているブロックはオレンジ色が注意(warn),青色が演習や強調(note)など独立した部分を表しています. 講義ではwarnに関しては,飛ばす場合があるので,興味のある人は自分で読み進めてください.

これは注意や発展的内容を示しています.

これは演習や強調したい箇所に利用されています.

リンクはGoogleのように下線で表示されます.クリックすることでリンクに飛ぶことが可能です. 右クリックして,新しいタブで開くことを推奨しています.

プログラムブロック

本講義ではプログラムブロックは以下のように黒い背景でシンタックスハイライトが適用されています.

import pandas as pd

print('Sample')

コピー&ペーストが可能なので, 自分のプログラムに利用してください.

Haskellとは

Haskellは,1987年に生まれた静的型付けの純粋関数型言語です. Haskellには,様々な特徴がありますが,本講義では,特に代数的データ型による,代数的なプログラミングに焦点をあてて,代数的な仕様記述とHaskellの関連を中心に議論します.

Haskellがどんな言語で,どのようなメリットがあるのか,という話は今後本講義でも扱いますが,ここでは深入りしません. 取り敢えず,どのような言語かを細かく説明する前に,関数型言語の雰囲気を掴んでもらおうと思います.

関数型言語の雰囲気

HaskellはLispやOCamlなどと同じ関数型言語です.関数型言語は関数によってプログラムを構築していく点にありますが,近年ではこのスタイルは関数型言語の専売特許というわけではなくなりつつあり,関数で書くことの特別さは,薄れつつあります. なので,ここでは,関数型言語の細かい機能について見る前に,関数型言語の考え方について,手続き型言語との違いという観点で見ていきましょう.

関数型言語でプログラミングををするとは,「それが何か」を分解して書いていくことです. 関数型プログラミングが宣言的であると言われる所以はそこにあります.手続き型言語が,「何をどうするのか」という手続きを書くのにたいして,「欲しいものはなにか」を宣言します.

こちらの(Haskell界隈では)有名なブログでは,関数型言語の考え方について以下のように説明しています.

Functional programmers have a peculiar way of approaching problems. They start by asking very Zen-like questions. For instance, when designing an interactive program, they would ask: What is interaction? When implementing Conway’s Game of Life, they would probably ponder about the meaning of life.

関数型プログラマーは問題への取り組み方が独特だ. 禅問答のような質問から始めるのだ.例えば,インタラクティブなプログラムを設計するとき,彼らは「インタラクションとは何か?コンウェイの「人生ゲーム」を実装するとき,彼らはおそらく人生の意味について熟考するだろう.

手続き型プログラミングと関数型プログラミングの違いは色々とありますが,取り敢えずここでは,この文章に習って

という観点に注目します. 例として以下の「ウサギの問題」について考えてみましょう.

ウサギの問題

  • 1つがいのウサギは,生まれてから2ヶ月後から毎月1つがいずつのウサギを産む

  • ウサギが死ぬことはない

  • この条件の下で,生まれたばかりの1つがいのウサギは1年の間に何つがいのウサギになるか

これについて,取り敢えず12ヶ月までのつがいの数をプログラムを用いて計算してみましょう.

まずは手続き型の考え方で数を数えてみます. 手続き型言語的には,「ウサギのつがいの数」を「どのように求めるのかという手続き」をプログラムに記述します.

note

学生にプログラミングを教えているとこれくらいのプログラムは,for文,if文,代入などの概念をちらっと読んだだけで簡単にできる人もいれば,数時間教えてもできない人もいます.これが何によって異なるのかというのは,長年の謎で,教育の難しいところです.

しかも,プログラムを教える人間は大抵前者なので,教師も学生も何が分からないのか分からないという事態によくなってしまいますね.

しかし,大抵の場合後者の人に話を聞いていくと,そもそもこの手続きを日本語であっても書けないという人が多いようです. なので,本当に苦労するタイプの人は,パワーポイントでウサギの絵を並べてルールにのっとってウサギが増えていく様子を小学生に教える日本語資料を作ってというような作業を一緒にすることになります.

これを書きながらこういった学生が実は関数型なら簡単だったりしないだろうか,と考えていますが,楽観的に過ぎるだろうなという予感がしています.

いろいろな方法がありますが(何が起きて,次に何が起きて,というふうに手続きを考える)「手続き型言語っぽい数え方」を一つ考えると,例えば

  • つがいは,新生ウサギ(0ヶ月)→子供ウサギ(1ヶ月)→大人うさぎ(2ヶ月)の順で変化する

  • 大人うさぎのつがいは毎月1つの新生うさぎのつがいを産む

  • 0ヶ月の新生うさぎの(こどもが産めない),子供ウサギ,大人うさぎの数を記録する

    • 新生 1
    • 子供 0
    • 大人 0
  • 1月たつと

    • 大人と同じ数だけ新生が生まれる
    • 子供が大人になる
    • 新生が子供になる
  • これを12ヶ月繰り返す

というように「何がどうなる」という「手順」を書いた説明になるかと思います. 授業では大抵,これをフローチャートに書き直させて,フローチャートをプログラムに直すという作業をさせますが,そこは省略します.

これをPythonのプログラムにすると以下のようになり,結果は233となります.

# 初期化
months = 12  # シミュレートする月数

#1ヶ月目の状態
new_born_pairs = 0 #新生のつがいの数
young_pairs = 1  # 子供のつがいの数
mature_pairs = 0  # 大人のつがいの数

# 各月におけるうさぎのつがいの数をシミュレート
for month in range(1, months + 1):
    # 大人と同じ数だけ新生が生まれる
    new_born_pairs = mature_pairs
    # 子供が大人になる
    mature_pairs += young_pairs
    # 新生が子供になる
    young_pairs = new_born_pairs


# 成熟したつがいと若いつがいの合計
total_pairs = mature_pairs + young_pairs

print(total_pairs)

こういった考え方が,いわゆる手続き型的な考え方とプログラミングの方法になります.

では,関数型の考え方とはどのようなものでしょうか. 先ほど引用したように,関数型では,それが何かを考えます.つまり,ここで問われている「つがいの数」を抽象化して,その特徴を記述するわけですね.

特定の数がなにかのルールに基づいて段々と増えていくというときに,それを並べてみて,法則性を探るということが一般的に行われます.これは,高校数学で扱う漸化式の考え方ですね.

月ごとのつがいの数を,並べてみると以下のようになります. そして,その増え方を計算してみると一定のルールに基づいていることが分かります.

つがいの数 計算
0 1
1 1
2 2 1 + 1
3 3 1 + 2
4 5 2 + 3
5 8 3 + 5
6 13 5 + 8
7 21 8 + 13
8 34 13 + 21
9 55 21 + 34
10 89 34 + 55
11 144 55 + 89
12 233 89 + 144

実はこのウサギのつがいの合計どの月でもは,1,1,2,3,5,8という風に前々月と前月のつがいの合計になることが知られています. このような,前の数字と前の前の数字の和によって次の数字を作る数をフィボナッチ数といいます.

※ 普通フィボナッチ数というと,0から始まりますが,ここではウサギの例で考えたいので1から始まることにします.

フィボナッチ数を漸化式として捉えると,第n月のフィボナッチ数の正体は以下のように得られます.

F_0 = 1 \\ F_1 = 1 \\ F_n = F_{n-1} + F_{n-2} (n >= 2)

したがって,上の条件での12ヶ月後のウサギの数はなにかという問題は,フィボナッチ数の第12番めの項F_{12}がなにかという問題であり,フィボナッチ数とはなにかといえば上の漸化式である,という風に考えることができます.

実際に計算手順を,一つひとつ追っていくのではなく,このように求めたい対象がなにかということを考えて,抽象化し記述するというのが,関数型言語の基本的な考え方になります.

ちなみに,これをHaskellで書くと以下のようになり,上の漸化式の書き方とかなり近い対応関係があることが分かります.

fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

※メモ化とかそういったことは,取り敢えずここでは置いておきます (この辺の数学的定義そのままだと,実用には向かない問題は,後ほど扱います.)

これを実行してみると確かに正しい数が求められています.

ghci> :{
ghci| fib :: Int -> Int
ghci| fib 0 = 1
ghci| fib 1 = 1
ghci| fib n = fib (n-1) + fib (n-2)
ghci| :}
ghci> fib 12
233

当然フィボナッチ数の漸化式は広く知られていますし, むしろ最初から漸化式として学習することが多いでしょう. したがって, Pythonでの実装もフィボナッチ数が漸化式であるという前提で,以下のように書くほうが一般的です.

def Fib(n):
    if n == 0:
        return 1
    elif n == 1:
        return 1
    else:
        return Fib(n-1) + Fib(n-2)

また,最近では,PythonやJavaScriptなどの手続き型の言語にも,関数型の考え方が導入され,内包表記,再帰,ラムダ式などの関数型のシンタックスも一般的に使われるようになっています(これらの詳細についてはこのあとやっていきます).逆にHaskell等の関数型言語においても,手続き型のほうが便利な場合には手続き型の記法を利用します.

したがって,現在では関数型的な考え方と,手続き型の考え方というのは,それほど明確に分かれるものではありません.

ここでは,手続き型の考え方と関数型の考え方の違いを説明するために,Pythonの事例をあえてあまり用いられない方法で書きましたが,大げさに書けば手続き型と関数型の考え方の違いとはこのような考え方,問題へのアプローチの仕方にあります.

関数型だと何が嬉しいのか

前節では,関数型の考え方に関して簡単な事例をしました. 関数型の考え方がしっくり来る人は,それが関数型を使う理由になるでしょうが,しっくり来るという抽象的な話ではなく,具体的な関数型言語のメリット/デメリットをこの節では紹介します. なお,関数型言語と一言でいっても,様々な言語がありますし,前述のように手続き型と関数型が明確に分かれる時代でもありません.

関数型言語の設計仕様は,関数型です. 手続き型言語の仕様定義にもいろいろな種類があります.

形式的証明可能性

関数型言語の大きなメリットの一つに,数式(仕様)とプログラムの対応関係が強い,という点があります. これにより,

という性質が得られます. 仕様書とプログラムのいずれかが残っていれば,もう一方も再現できる,ということです. 数理的な厳密性を必要とするプログラムでは,仕様とプログラムの一致が大きな資料価値をもちます.

具体的に事例を見てみましょう. 数式の表現に関しては後の章で内容を扱うのでわからない人は雰囲気のみの理解で問題ありません.

例1: データ集合

1 から 5 の自然数 x, y のうち x > y を満たす組 (x, y) の集合を考えます. 数式で表現すると以下のようになります.

S = \{(x, y) \mid x \in \{1,2,3,4,5\}, y \in \{1,2,3,4,5\}, x > y \}

Haskellではリスト内包表記を使って, 数式とほぼ一対一に書けます.

s = [(x, y) | x <- [1,2,3,4,5], y <- [1,2,3,4,5], x > y]

Pythonにも内包表記があるため,比較的数式に近い書き方が可能です.

s = [(x, y) for x in range(1, 6) for y in range(1, 6) if x > y]

一方,手続き型的にVBA (Excelで利用可能なプログラミング言語) で書くと,ループを積み重ねて手順として組み立てる形になります.

Dim result As Variant
Dim tuple(1) As Integer
Dim count As Integer
count = 0
For i = 1 To 5
    For j = 1 To 5
        If i > j Then
            ReDim Preserve result(count + 1)
            tuple(0) = i
            tuple(1) = j
            result(count) = tuple
            count = count + 1
        End If
    Next
Next

このVBAコードを見て,それが上の数式と一致していることをひと目で判断するのは難しいでしょう. 一方でHaskellのコードは数式をほぼそのまま書き写した形なので,対応関係を追うのは容易です.

例2: 再帰関数

階乗 n! を場合分けで定義すると以下のようになります.

n! = \begin{cases} 1 & (n = 0) \\ n \times (n-1)! & (n > 0) \end{cases}

Haskellではガードを用いて,この定義にほぼ対応する形で実装できます.

f 0 = 1
f n | n > 0 = n * f (n - 1)

Pythonでも,再帰を使えば数式の構造を保った書き方ができます.

def f(n):
    if n == 0:
        return 1
    return n * f(n - 1)

一方,VBAではforループと累積代入によって手順として書き下す形になります.

Function f(n As Integer) As Integer
    Dim result As Integer
    result = 1
    If n = 0 Then
        f = 1
    Else
        For i = 1 To n
            result = result * i
        Next
        f = result
    End If
End Function

このプログラムが n! の定義と一致していることを確認するには,for文が階乗の漸化式に対応していることを頭の中で復元する必要があります. 複雑な処理になるほど,この種の「手順への翻訳」の過程でバグが入り込みやすくなります.


このように,関数型言語では数式による仕様とプログラムの間に一対一に近い対応関係を持たせやすく,

のいずれの方向の作業も楽になります. これが,関数型言語における 形式的証明可能性 という性質です.

ただし,数式の世界には計算量,エラー処理,入出力や乱数などの副作用といった概念が存在しません. そのため,実際の複雑なプログラムを数式と完全に一致させることはできない場合があります. これらの点をどのように扱うかは,本講義の後半で設計の話題として改めて扱います.

厳密な証明などを必要とする学術,金融などの分野では計算を,集合論,代数的に記述する場合があります.そのような場合にはこの形式的証明可能性が強力な武器となります.

モジュラー性・結合性・等式推論

関数型言語では,プログラムを小さな関数の合成として書く文化が強く根付いています. これによって以下のような互いに関連した性質が得られます.

具体例として,「整数のリストに対して,各要素を2乗した合計を求める」処理を考えてみましょう.

Haskellで書くと以下のようになります.

square :: Int -> Int
square x = x * x

sumOfSquares :: [Int] -> Int
sumOfSquares xs = sum (map square xs)

ここで注目すべき点は次の通りです.

さらに, 関数合成演算子 . を使うと, 以下のようにも書けます.

sumOfSquares :: [Int] -> Int
sumOfSquares = sum . map square

関数を「データを加工するパイプライン」のように繋げて書けるわけです.

こうして組み立てられたプログラムは,数式のように書き換えることが可能です. 例えば,

\text{map}\ f \circ \text{map}\ g = \text{map}\ (f \circ g)

という法則(合成則)が成り立ち,関数型言語ではプログラムの意味を変えずに記述を変形できます. このような書き換えを 等式推論(equational reasoning) と呼びます. リファクタリングや最適化を行う際に,この等式推論が可能であるということは大きな安心材料になります.

一方,同じ処理を手続き型で書くとどうなるでしょうか. Pythonであれば内包表記で近い書き方もできますが,伝統的な手続き型スタイルで書くと,

def sum_of_squares(xs):
    total = 0
    for x in xs:
        total = total + x * x
    return total

となり,total という可変の状態ループが一体となって処理を構成します. この形式では x * x の部分を別関数として切り出すことはできても,「総和を取る」部分と「2乗する」部分を独立した部品として切り離して再利用することは難しく,等式的な書き換えも困難です.

参照透明性と純粋関数型言語

Haskellのような純粋関数型言語は,副作用を持たない関数を中心にプログラムを組み立てる言語です. 副作用とは,「入力から出力を計算する」以外の挙動,例えば,

といったものを指します. 純粋関数型言語では,このような副作用を持つ処理は型レベルで明示的に区別され,純粋な計算とは分離されます(具体的な方法は後の章の IO の説明で扱います).

この設計から得られる最大の性質が 参照透明性(referential transparency) です. これは,

という性質です.

具体例を見てみましょう. 手続き型のPythonでは,以下のようにグローバルな状態を使った関数を書けます.

counter = 0

def next_id():
    global counter
    counter += 1
    return counter

next_id()  # => 1
next_id()  # => 2
next_id()  # => 3

ここでは,next_id() という同じ呼び出しが,呼ぶタイミングによって毎回異なる値を返しています. これは内部状態 counter という副作用があるためです. この場合,next_id() という式を特定の値で置き換えることはできません.

一方,Haskellの純粋関数は入力にのみ依存します.

nextId :: Int -> Int
nextId n = n + 1

nextId 1 は,プログラムのどこで,いつ評価しても常に 2 を返します. したがって,

let y = nextId 1 in y + y

というプログラムは,

nextId 1 + nextId 1

とまったく同じ意味を持ちますし,さらに 2 + 2 で置き換えても結果は変わりません. この「式をその値で置き換えてよい」という性質が参照透明性であり,前述の 等式推論 を支える基礎となっています.

参照透明性は,副次的に以下のようなメリットももたらします.

  • テストが容易: 関数の振る舞いを確認するには,入力と期待する出力の組さえ用意すればよい. 外部状態の準備や後片付けが不要
  • デバッグが容易: 各関数の振る舞いは独立しており,他所の状態に依存しないため,バグの切り分けが局所化される
  • 並列化が容易: 副作用が無いため,計算の順序を変えたり並列実行しても結果が変わらない

一方で,現実のプログラムには必ずどこかに副作用(ユーザ入力,ファイル,乱数等)が必要です. 純粋関数型言語では,それらを型の中に閉じ込めて,純粋な部分と明確に分けて扱うという発想で設計されています. 詳細は後の章で扱います.

Haskellの特徴

前章までに,関数型言語としてのHaskellのメリット・デメリットを見てきました. 本章では視点を変えて,Haskellを広く使われている手続き型言語の代表であるPythonと比較しながら,言語としての特徴を整理していきます.

Haskellは以下の性質を備えた言語です.

対するPythonは以下の性質を持ちます.

両者を並べると以下のようになります.

項目 Haskell Python
実行方式 コンパイラ インタプリタ
パラダイム 純粋関数型 手続き型
型付け 静的 動的
評価戦略 遅延評価 正格評価
型推論 あり(強力) なし
副作用の扱い 型で区別 自由に記述可能

実行方式やパラダイムに関する違いは既に述べてきたので, ここでは特に「型付け」に注目します.

静的型付けと動的型付け

プログラミング言語における とは,データの種類(整数,文字列,リストなど)を区別する仕組みです. 型の検査をいつ行うかによって,言語は大きく二つに分類されます.

動的型付け (Pythonなど)

動的型付け言語では,型の検査は プログラムの実行時 に行われます. Pythonでは変数や関数の引数に型を明示する必要がなく,実行して初めて型の不整合が検出されます.

def add(x, y):
    return x + y

add(1, 2)      # => 3
add("a", "b")  # => "ab"
add(1, "b")    # 実行時に TypeError

静的型付け (Haskell, Java, Rustなど)

静的型付け言語では,型の検査は コンパイル時 に行われます. プログラムを実行する前に,型の不整合があればコンパイルエラーとして検出されます.

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

-- add 1 2       -- OK (結果は 3)
-- add "a" "b"   -- コンパイルエラー: Int を期待したが String

Haskellの型システムの特徴

Haskellの型システムは,静的型付け言語の中でも特に強力です.

-- 型注釈を省略してもコンパイラが型を推論する
double x = x * 2           -- Num a => a -> a と推論される
greet name = "Hello, " ++ name  -- [Char] -> [Char] と推論される

このように,Haskellでは「動的型付け言語のように気軽に書ける」面と「静的型付け言語としての強い安全性」を両立させる設計が図られています.

最近ではPythonにも typing モジュールや型ヒント(def f(x: int) -> int:)が導入されており, mypy などの外部ツールで静的型チェックを行うことも可能になっています. ただし, 型ヒントは実行時にチェックされるわけではなく, また既存コードへの普及も限定的なため, Haskellのコンパイル時型チェックとは安全性のレベルが異なります.

Haskellのメリットとデメリット及び, 比較としてHaskellがPythonに対して持つ特徴を,ここまでの議論を踏まえてまとめてみます.

メリット

  • 形式的証明可能性: 数式(仕様)とプログラムの対応関係が強く,仕様書とプログラムの相互変換が容易で,正しさを論じやすい
  • モジュラー性・結合性・等式推論: 小さな関数の合成でプログラムを組み立てられ,リファクタリングや最適化を等式的に行える
  • 参照透明性: 純粋関数は同じ入力に対して常に同じ出力を返し,テスト・デバッグ・並列化が容易になる
  • 静的型チェック: 実行前にバグを検出できる. 強力な型推論により型注釈の負担も軽減される
  • 高速性: コンパイラ方式のため一般に実行が速い. 副作用が無いぶん並列計算にも適する

デメリット

一方でHaskellには以下のようなデメリットもあり,常にあらゆる用途で最適とは限りません.

  • 学習コストが高い: モナドや型クラスなど,初学者にとって馴染みのない概念を習得する必要がある
  • エコシステムの規模: 機械学習・データ分析・Web開発などの領域では,Pythonの方がライブラリが充実している
  • 副作用の扱いが煩雑: Pythonなら素直に書けるI/Oやグローバル状態も,Haskellではモナドなどの仕組みを通す必要がある
  • 性能の予測が難しい: 遅延評価により,メモリ使用量や実行性能が直感と一致しない場合がある(スペースリーク等)
  • コミュニティ規模: 困ったときの情報量(Stack Overflow,日本語記事等)はPythonに比べて少ない

AIによるコーディング支援と静的型付け言語の再評価

近年は ChatGPT, Claude, GitHub Copilot, Cursor などの AI によるコーディング支援が急速に普及しています. これは, Haskell のような「学習コストが高い」とされてきた言語にとって二重の追い風になりつつあります.

一つ目は 学習ハードルの低下 です. 従来は日本語情報の少なさや独特の概念(モナド, 型クラス等)の理解が初学者の壁となっていましたが, AI に質問しながら進めることで, その障壁はかなり下がってきています. 分からないエラーメッセージを貼り付けて解説してもらう, 書きたい処理をまずAIに書かせて読み解く, といった学習スタイルが現実的になりました.

二つ目は 静的型付け言語としての安全性の再評価 です. AI が生成したコードは一見正しそうに見えても,実は細かい型の不整合や前提の崩れを含んでいることがあります. 動的型付け言語ではこれらが実行時まで発覚しないことが多いのに対し, 静的型付け言語ではコンパイラが自動でチェックしてくれるため, AIが書いたコードを安全に使うための第一関門 として機能します. この観点から, Haskellやその他の静的型付け言語が AI 時代に改めて注目されています.

実世界でのHaskell

Haskellは研究目的で設計された背景をもつため,「学術的で実用性に乏しい」という先入観を持たれがちですが,実際には企業や OSS プロジェクトで広く利用されています. ここでは代表的な事例を紹介します.

企業での利用

ブロックチェーン

OSSツール


Haskell は Python のような「どこでも使われる」汎用言語ではありません. しかし,強い型安全性や厳密性が要求される領域(金融,ブロックチェーン,コンパイラ基盤,大規模バックエンド)で着実にユーザを獲得しています. 本講義で扱う「代数的に考える」習慣は,まさにこれらの領域で価値を発揮するものです.

その他の関数型言語

関数型言語はHaskellだけではありません. それぞれ異なる思想・用途で発展してきた言語があり,用途や好みに応じて選択肢となります.

これらの言語はそれぞれ異なる特徴を持ちますが,関数による合成, 不変性, 代数的データ型, パターンマッチ といった関数型の基本的な道具は共通しています. Haskellで学んだ考え方は,これらの言語にも(程度の差はあれ)応用できます.

Rust の状況

関数型言語の周辺で,近年最も注目を集めているのが Rust です. Rustは厳密には「関数型言語」に分類される言語ではありませんが, 関数型言語の長所を強く取り入れたマルチパラダイム言語 として, 関数型コミュニティからも強い関心が寄せられています.

Rustの主な特徴は以下の通りです.

Rustは, 長年 Stack Overflow の「最も愛されているプログラミング言語」ランキングで首位を取り続けるなど, 開発者からの評価が非常に高い言語です. 近年では Linux カーネルへの採用や, Microsoft, Google, Amazon, Meta 等の大企業での採用拡大も進んでいます.

Rust が関数型言語として注目される理由は, 「Haskellのような強い型システムと代数的データ型の恩恵を, 実用的な速度と豊かなエコシステムで享受できる」 点にあります. 純粋関数型ではない, 遅延評価ではない, モナドを使わないといった違いはありますが, 関数型的な設計指向を持つ現場では Rust が有力な選択肢になっています.

一方で Rust は, 所有権・借用チェッカ(borrow checker)との戦いが学習曲線の大きな山となっており, Haskell 以上に「書き始めの難しさ」を訴える声もあります. これもまた, AI支援の普及によって徐々に緩和されつつある領域です.

本講義の主題はHaskellですが,Haskellで学んだ代数的な考え方や型システムの感覚は, Rust をはじめとするこれらの近縁言語を学ぶ際にも強い土台となります.

いずれの言語が優れているかは目的次第です. 試行錯誤しながらデータ分析を行うならPython,厳密性や保守性を重視するシステムならHaskell,というように,言語の特徴を理解して使い分けるのが現代的な姿勢といえます. また,これまでにHaskellなどの関数型言語のメリットは,Pythonなどの手続き型言語にも取り入れられており,それらの機能を理解する上でも異なるパラダイムの言語を学ぶ意義はあります. 本講義ではあえてHaskellを扱いますが,それはHaskellが常に最良の選択肢だからではなく,Haskellを学ぶことで プログラミングの捉え方そのものが広がる からです.

ce0f13b2-4a83-4c1c-b2b9-b6d18f4ee6d2