Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active June 23, 2026 04:09
Show Gist options
  • Select an option

  • Save lagenorhynque/cac12828101d2b45f03100c75860d62a to your computer and use it in GitHub Desktop.

Select an option

Save lagenorhynque/cac12828101d2b45f03100c75860d62a to your computer and use it in GitHub Desktop.
速習FP in Scala with Flix: Flix言語で親しむ純粋関数型のコード設計
slides:
charset: utf-8
theme: night
highlight_theme: monokai-sublime
separator_vertical: ^\s*----\s*$
revealjs:
transition: convex
plugins:
- extra_css:
- https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css

速習 FP in Scala with Flix

Flix言語で親しむ純粋関数型のコード設計


x icon

  • 関数型プログラミング(言語)の愛好者/実践者
  • 関数型まつり2026の運営スタッフ(座長のひとり)
    • 主に(CfPを含む)メインコンテンツの準備を担当
  • 株式会社スマートラウンドのシニアエンジニア
    • スタートアップと投資家のやり取りを効率化するデータ管理プラットフォームを開発
    • ソフトウェア設計/開発とともに会計/財務/法務について探求するのも好き

  1. きっかけ

  2. 書籍 FP in Scala とは

  3. Flix言語の基本

  4. (純粋)関数型のコード設計

    1. 関数型プログラミング入門
    2. 関数型設計とコンビネータライブラリ
    3. 関数型設計に共通する構造
    4. エフェクトとI/O

1. きっかけ


関数型まつり2025での🐬の発表

Functional Language Tasting

関数型言語テイスティング: Haskell, Scala, Clojure, Elixirを比べて味わう関数型プログラミングの旨さ

書籍 Functional Programming in Scala
(FP in Scala)

  • 今年度は関数型プログラミングらしいコード設計/抽象化のパターンにより踏み込んで解説したい

  • 関数型言語について学び始めた頃に取り組んだ本のひとつに FP in Scala があった

    • 演習問題が膨大で途中まで進めて積んでいたが😇
  • 関数型まつり2026の「FP入門ハンズオン」の題材として FP in Scala の演習問題を利用することに


Flixとの出会い

Flix First Impression

Clojurianが出会ったsimple志向なJVM関数型言語Flix ——ファーストインプレッション

エフェクトシステムを備えた関数型言語Flix

  • エフェクトシステムと代数的エフェクトを探求する過程で、Flixという言語に出会った

  • simpleeasy を区別して simplicity を重視するという方向性もFlixの設計指針に含まれている

    • Rich Hickeyの有名なプレゼンSimple Made Easyに由来する
    • 🐬はClojurianなので大いに感激した😆

[参考] Shibuya.lisp lispmeetup #118での🐬の発表

Simple Made Easy Made Easier

"Simple Made Easy" Made Easier: Clojureに学ぶsimplicity

「"Simple Made Easy" Made Easier 」の要約

  • simple は客観的な構造の単純さ、 easy は主観的な(広義の)近さの問題(なのだが誤解されがち)

  • Clojureエコシステム/コミュニティには様々な simplicity 追求の例がある

  • 抽象度を上げて、客体/モノと主体/ヒトの区別、
    さらにはars/τέχνη (技芸)とvita/βίος (人生)の対比と捉えることもできる


2. 書籍 FP in Scala とは


Functional Programming in Scala, Second Edition

fp in scala (original)

Manningの書籍詳細ページより

『Scala関数型デザイン&プログラミング』
Functional Programming in Scala (第1版)の訳書

fp in scala (japanese)

インプレスブックスの書籍詳細ページより

FP in Scala, a.k.a. The Red Book 📕

  • オブジェクト指向/関数型言語Scalaで(純粋関数型言語Haskellのような) FP実践を目指す本

    • 2014年初版当時のScalaコミュニティでは良くも悪くも"better Java"扱いが珍しくなかった
    • ScalaでFP設計を深く学べる(おそらく)最初の本
  • 豊富な演習問題に取り組みながら理解を深め、抽象化の必要性/必然性を体感できる教育的な構成

    • ただし、独習では挫折しやすい本でもある🥹


3. Flix言語の基本


Flixとは

  • エフェクトシステムの組み込みサポートが特徴的な静的型付き関数型言語

    • 副作用を伴う関数には IO エフェクトが付く
    • Scalaと関係が深いが非オブジェクト指向言語
      (サブタイピング(部分型)/継承とも無縁)
  • JVM言語(Scala, Kotlin, Clojureなどと同じ)

  • 主にHaskell, Scala, OCaml, Rustの影響が見られる

    • 処理系の実装言語もScala
  • 🐬< 大雑把には「Scalaの皮を被ったHaskell」🐑


ℹ️ 他の関数型言語経験者向けの補足

  • Hindley-Milner型システムが採用されている

    • HaskellやML系言語と同様
    • ⚠️ トップレベルの定義のみ敢えて型注釈必須
  • 関数は常にカリー化される

    • HaskellやML系言語と同様
    • 構文的に多引数関数に見えても実体は1引数関数の連鎖
  • Haskellによく似た型クラス(type class)がある


コードの書き方(0): 事前準備

mkdir first-flix-project
cd first-flix-project
# プロジェクトを生成
flix init
# プロジェクトルートのモジュールファイルを追加
echo 'pub mod FirstFlixProject {}' > src/FirstFlixProject.flix

コードの書き方(1): モジュールとそのトップレベル関数

// src/FirstFlixProject/SimpleMath.flix
pub mod FirstFlixProject.SimpleMath {
    // パブリック関数
    pub def square(n: Int32): Int32 =
        n * n

    // プライベート関数
    // ⚠️ 未使用のままだとコンパイルエラー
    def double(n: Int32): Int32 =
        n * 2
}

コードの書き方(2): トップレベル関数による定数

// src/FirstFlixProject/SomeModule.flix
pub mod FirstFlixProject.SomeModule {
    // パブリックな0引数関数
    pub def a(): Int32 = 1

    // プライベートな0引数関数
    // ⚠️ 未使用のままだとコンパイルエラー
    def b(): Int32 = 2
}
  • トップレベル変数は存在しない

コードの書き方(3): モジュールの利用

flix> use FirstFlixProject.{SimpleMath => M}; \
>     M.square(3)
9

flix> use FirstFlixProject.{SimpleMath => M}; \
>     List.map(M.square, List.range(0, 11))
0 :: 1 :: 4 :: 9 :: 16 :: 25 :: 36 :: 49 :: 64 :: 81 :: 100
  :: Nil

flix> use FirstFlixProject.{SimpleMath => M}; \
>     List.range(0, 11) |> List.map(M.square)
0 :: 1 :: 4 :: 9 :: 16 :: 25 :: 36 :: 49 :: 64 :: 81 :: 100
  :: Nil
  • ; は式の区切り文字
  • \ はREPLでの行継続
  • |> はパイプ(ライン)演算子

コードの書き方(4): ローカル束縛

flix> \\
> use FirstFlixProject.{SimpleMath => M};
> let x = 5;
> let y = M.square(x);
> "square(${x}) = ${y}"
> \\
square(5) = 25
  • \\ はREPLでの複数行入力の開始/終了

コードの書き方(5): 無名関数(ラムダ式)

flix> (n -> n * n * n)(2)
8

flix> List.map(n -> n * n * n, List.range(0, 11))
0 :: 1 :: 8 :: 27 :: 64 :: 125 :: 216 :: 343 :: 512 :: 729 ::
  1000 :: Nil

4. (純粋)関数型のコード設計

  1. 関数型プログラミング入門
  2. 関数型設計とコンビネータライブラリ
  3. 関数型設計に共通する構造
  4. エフェクトとI/O

第1部 関数型プログラミング入門

  1. 関数型プログラミングとは
  2. Flixで始める関数型プログラミング
  3. 関数型データ構造
  4. 例外によらないエラーハンドリング
  5. 正格性と遅延性
  6. 純粋関数型の状態

第1章 関数型プログラミングとは


キーワード

  • 関数型プログラミング(functional programming)

  • 純粋関数(pure function)

  • 副作用(side effect)

  • 参照透過性(referential transparency)

  • 純粋性(purity)

  • 置換モデル(substitution model)


カフェでコーヒーを注文する過程を表す素朴なコード

// ⚠️ ScalaのOOPかつ非FPなコード
class Cafe:
  def buyCoffee(cc: CreditCard): Coffee =
    val cup = Coffee()
    cc.charge(cup.price)
    cup
class CreditCard:
  def charge(price: Double): Unit =
    println("charging " + price)  // 請求処理(簡単のため標準出力)
class Coffee:
  val price: Double = 2.0
// 利用例
scala> Cafe().buyCoffee(CreditCard())
charging 2.0  // 標準出力
val res0: Coffee = Coffee@353cb7e5  // 戻り値
  • クレジットカードへの請求処理という副作用が埋め込まれている

OOPで典型的な改善策: インターフェースを導入してDI

// ⚠️ ScalaのOOPかつ非FPなコード
class Cafe:
  def buyCoffee(cc: CreditCard, p: Payments): Coffee =
    val cup = Coffee()
    p.charge(cc, cup.price)
    cup
// インターフェース定義
trait Payments:
  def charge(cc: CreditCard, price: Double): Unit
// インターフェースに対する実装
class SimulatedPayments extends Payments:
  def charge(cc: CreditCard, price: Double): Unit =
    println("charging " + price + " to " + cc)
  • 副作用を含む実装を差し替えやすくなった
  • しかし依然として:
    • テストしやすいとまではいえない
    • buyCoffee の再利用性は低い

Flixでの対応A: (非OOPLなので)振る舞いを関数で渡す

mod Coffee {
    // priceフィールドを持つレコードによるCoffee型
    pub enum Coffee({price = Float64})
    // 文字列表現(ToString型クラスの実装)
    instance ToString[Coffee] {
        pub def toString(coffee: Coffee): String =
            let Coffee({price}) = coffee;
            "Coffee({price = ${price}})"
    }
    // コンストラクタ関数(ただの関数)
    pub def make(): Coffee = Coffee({price = 2.0})
}
mod CreditCard {
    // 金額を受け取って何も返さず、IOエフェクト(副作用)を伴う関数
    pub def charge(price: Float64): Unit \ IO =
        println("charging ${price}")
}

mod Cafe {
    use FpInFlix.Exercises.Introduction.FirstExample.Coffee
    // 請求関数を受け取ってコーヒーを返し、IOエフェクトを伴う関数
    pub def buyCoffee(charge: Float64 -> Unit \ IO):
      Coffee \ IO =
        let coffee = Coffee.make();
        let Coffee.Coffee({price}) = coffee;
        charge(price);
        coffee
}
// 利用例
flix> use FpInFlix.Exercises.Introduction.FirstExample.{Cafe,
  CreditCard}; \  // ℹ️ 以降はスペース節約のためuseを省略する
>     Cafe.buyCoffee(CreditCard.charge)
charging 2.0           // 標準出力
Coffee({price = 2.0})  // 戻り値
  • 高階関数によるDI
    • 高階関数は様々な設計パターンの基礎になる

Flixでの対応B: 振る舞いをエフェクトで表す

mod CreditCard {
    // 文字列(識別子)を持つCreditCard型(Eq, ToStringは自動導出)
    pub enum CreditCard(String) with Eq, ToString
}
// 決済エフェクト
pub eff Payment {
    // クレジットカードと金額を受け取って何かする関数
    def charge(cc: CreditCard, amount: Float64): Unit
}
mod Cafe {
    use FpInFlix.Exercises.Introduction.SecondExample.{
  Coffee, CreditCard, Payment}
    // 請求関数を受け取ってコーヒーを返し、決済エフェクトを伴う関数
    pub def buyCoffee(cc: CreditCard): Coffee \ Payment =
        let coffee = Coffee.make();
        let Coffee.Coffee({price}) = coffee;
        Payment.charge(cc, price);  // 決済エフェクトの関数を利用
        coffee
}

pub def example(): Unit \ IO =
    let cup = run {
        Cafe.buyCoffee(CreditCard.CreditCard("A"))
    } with handler Payment {  // 決済エフェクトに対するハンドラー
        def charge(cc, amount, resume) = {
            println("charging ${amount} to ${cc}");
            resume()  // エフェクト発生地点から続く処理を再開
        }             //   (継続を呼び出す)
    };
    println("a cup of ${cup}")
// 利用例
flix> example()
charging 2.0 to CreditCard(A)   // 標準出力(Payment.charge)
a cup of Coffee({price = 2.0})  // 標準出力(exampleの最後)
()  // 戻り値
  • 代数的エフェクトとエフェクトハンドラーによるDI
    • エフェクトシステムの活用例

Flixでの対応C: 副作用を完全に隔離する

mod Charge {
    use FpInFlix.Exercises.Introduction.ThirdExample.
      CreditCard
    // creditCard, amountフィールドを持つレコードによるCharge型
    pub enum Charge({
        creditCard = CreditCard,
        amount = Float64
    })
    // 文字列表現(ToString型クラスの実装)
    instance ToString[Charge] {
        pub def toString(charge: Charge): String =
            let Charge({creditCard, amount}) = charge;
            "Charge({creditCard = ${creditCard}, amount =
              ${amount}})"
    }
  • 「請求」をただのデータとして表す
    • 解釈は利用側に委ねる

    // クレジットカードが同一の場合に限って2つの請求をまとめる純粋関数
    pub def combine(a: Charge, b: Charge): Result[String,
      Charge] =
        let Charge({creditCard=cardA, amount=amountA}) = a;
        let Charge({creditCard=cardB, amount=amountB}) = b;
        if (cardA == cardB) Ok(Charge({creditCard = cardA,
          amount = amountA + amountB}))
        else Err("Can't combine charges to different cards:
          cardA=${cardA}, cardB=${cardB}")
    // 請求リストをクレジットカードごとにまとめる純粋関数
    pub def coalesce(charges: List[Charge]): Result[String,
      List[Charge]] =
        charges
            |> List.groupBy(charge ->  // カードでグループ化
                let Charge({creditCard | _}) = charge;
                creditCard
            )
            |> Traversable.traverse(group ->  // combineで集約
                Foldable.foldLeftM(combine, Nel.head(group),
                  Nel.tail(group)))
}

    mod Cafe {
        use FpInFlix.Exercises.Introduction.ThirdExample.
          {Charge, Coffee, CreditCard}
        // カードを受け取ってコーヒーと請求を返す純粋関数
        pub def buyCoffee(cc: CreditCard): (Coffee, Charge) =
            let coffee = Coffee.make();
            let Coffee.Coffee({price}) = coffee;
            let charge = Charge.Charge({creditCard = cc,
              amount = price});
            (coffee, charge)
        // カードと個数を受け取ってコーヒーリストと請求を返す純粋関数
        pub def buyCoffees(cc: CreditCard, n: Int32):
          Result[String, (List[Coffee], Charge)] =
            let (coffees, charges) = buyCoffee(cc)
              |> List.repeat(n) |> List.unzip;  // n個繰り返し
            charges  // コーヒーリストと請求のタプルに集約
                |> Foldable.foldLeftM(Charge.combine,
                  Charge.Charge({creditCard = cc,
                    amount = 0.0}))
                |> Functor.map(charge -> (coffees, charge))
    }

// 利用例
flix> Cafe.buyCoffee(CreditCard.CreditCard("A"))
(Coffee({price = 2.0}), Charge({creditCard = CreditCard(A),
  amount = 2.0}))  // 戻り値

flix> Cafe.buyCoffees(CreditCard.CreditCard("A"), 3)
Ok((Coffee({price = 2.0}) :: Coffee({price = 2.0}) ::
  Coffee({price = 2.0}) :: Nil, Charge({creditCard =
  CreditCard(A), amount = 6.0})))  // 戻り値
  • データを変換する純粋関数
  • 多様なデータを表す代数的データ型
    • List, Nel (non-empty list), Result, etc.
  • 関数のパターンを抽象化した型クラス
    • Traversable, Foldable, Functor, etc.

第2章 Flixで始める関数型
プログラミング


キーワード

  • 再帰関数(recursive function)

    • 末尾再帰関数(tail-recursive function)
      • 末尾呼び出し最適化/除去(tail-call optimization/elimination)
  • 高階関数(higher-order function)

  • 多相関数(polymorphic function)

    • ↔ 単相関数(monomorphic function)
    • パラメータ多相(parametric polymorphism)
  • 型駆動開発(type-driven development)


ループ(繰り返し処理)したい ⇒ 再帰関数

// (関数型言語のチュートリアルでお馴染みの)階乗関数
pub def factorial(n: Int32): Int32 =
    @Tailrec  // 末尾再帰であることを保証するアノテーション
    def go(x, acc) =  // 末尾再帰関数(ループ相当に最適化される)
        if (x <= 0) acc           // 基底部
        else go(x - 1, x * acc);  // 再帰部
    go(n, 1)
// 利用例
flix> List.map(factorial, List.range(0, 6))
1 :: 1 :: 2 :: 6 :: 24 :: 120 :: Nil

関数の振る舞いを切り替えたい ⇒ 高階関数

pub def formatAbs(x: Int32): String =
    "The absolute value of ${x} is ${abs(x)}."
pub def formatFactorial(n: Int32): String =
    "The factorial of ${n} is ${factorial(n)}."
// 整数を受け取って整数を返す関数をパラメータ化して高階関数に
pub def formatResult(name: String, n: Int32,
  f: Int32 -> Int32): String =
    "The ${name} of ${n} is ${f(n)}."
// 利用例
flix> formatResult("absolute value", -3, abs)
The absolute value of -3 is 3.

flix> formatResult("factorial", 5, factorial)
The factorial of 5 is 120.

任意の型で再利用したい ⇒ (パラメータ)多相関数

// 文字列のベクターをキーで線形探索してインデックスを返す関数
pub def findFirst(ss: Vector[String], key: String): Int32 =
    @Tailrec
    def loop(n) =
        if (n >= Vector.size(ss)) -1
        else if (Vector.get(n, ss) == key) n
        else loop(n + 1);
    loop(0)
// 任意の要素型のベクターを述語関数で線形探索してインデックスを返す関数
pub def findFirst(xs: Vector[a], p: a -> Bool): Int32 =
    @Tailrec
    def loop(n) =
        if (n >= Vector.size(xs)) -1
        else if (xs |> Vector.get(n) |> p) n
        else loop(n + 1);
    loop(0)

第3章 関数型データ構造


キーワード

  • 代数的データ型(algebraic data type, ADT)

    • 直積型(product type)
    • 直和型(sum type)
  • 再帰データ型(recursive data type)

  • パターンマッチング(pattern matching)

    • 網羅性(exhaustiveness)
  • 純粋関数型データ型(purely functional data type), 永続データ型(persistent data type)

    • 構造共有(structural sharing)

不変コレクションがほしい ⇒ 代数的データ型

// リスト(単方向連結リスト)
pub enum List[a] with Eq, ToString {  // 等価判定、文字列表現付き
    case Nil               // 空の場合(基底部)
    case Cons(a, List[a])  // 空でない場合(再帰部)
}
// ツリー(二分木)
pub enum Tree[a] with Eq, ToString {
    case Leaf(a)                   // 葉の場合(基底部)
    case Branch(Tree[a], Tree[a])  // 枝の場合(再帰部)
}

データを構造に沿って扱いたい ⇒ パターンマッチング

// リストの長さ
pub def length(l: List[a]): Int32 =
    match l {
        case Nil => 0
        case Cons(_, xs) => 1 + length(xs)
    }
// ツリーの要素数
pub def size(t: Tree[a]): Int32 =
    match t {
        case Leaf(_) => 1
        case Branch(left, right) => 1 + size(left) +
          size(right)
    }
// 利用例
flix> length(Cons("a", Cons("b", Cons("c", Nil))))
3

flix> size(Branch(Branch(Leaf("a"), Leaf("b")), Leaf("c")))
5

データの操作パターンを抽象化したい ⇒ 高階関数

// 右から畳み込む関数(再帰構造に沿った集約関数)
// ⚠️ 愚直な実装では末尾再帰/スタックセーフにならない(工夫が必要)
pub def foldRight(f: (a, b) -> b \ ef, acc: b, l: List[a]):
  b \ ef =
    match l {
        case Nil => acc
        case Cons(x, xs) => f(x, foldRight(f, acc, xs))
    }
// 利用例
flix> foldRight((-), 0, Cons(1, Cons(2, Cons(3, Nil))))
2
//   (1 - (2 - (3 - 0)))
// = (1 - (2 - 3))
// = (1 - (-1))
// = 2

// 左から畳み込む関数(末尾再帰の集約関数)
@Tailrec  // 末尾再帰
pub def foldLeft(f: (b, a) -> b \ ef, acc: b, l: List[a]):
  b \ ef =
    match l {
        case Nil => acc
        case Cons(x, xs) => foldLeft(f, f(acc, x), xs)
    }
// 利用例
flix> foldLeft((-), 0, Cons(1, Cons(2, Cons(3, Nil))))
-6
//   (((0 - 1) - 2) - 3)
// = (((-1) - 2) - 3)
// = ((-3) - 3)
// = -6
  • ℹ️ 結合法則を満たさない(+ 単位元を持たない)演算では foldRightfoldLeft の結果が異なる

// リストを返す関数を各要素に適用して得られるリストを平坦化して返す関数
pub def flatMap(f: a -> List[b] \ ef, l: List[a]):
  List[b] \ ef =
    // ℹ️ append: (List[a], List[a]) -> List[a] は定義済みとする
    foldRight((x, acc) -> append(f(x), acc), Nil, l)
// 利用例
flix> \\
> flatMap(
>     x -> Cons(x, Cons(x + 1, Nil)),
>     Cons(1, Cons(2, Cons(3, Nil)))
> )
> \\
Cons(1, Cons(2, Cons(2, Cons(3, Cons(3, Cons(4, Nil))))))

// 関数を各要素に適用して得られるリストを返す関数
pub def map(f: a -> b \ ef, l: List[a]): List[b] \ ef =
    flatMap(x -> Cons(f(x), Nil), l)
// 利用例
flix> \\
> map(
>     x -> x * x * x,
>     Cons(1, Cons(2, Cons(3, Nil)))
> )
> \\
Cons(1, Cons(8, Cons(27, Nil)))

// 述語を満たす要素のみのリストを返す関数
pub def filter(p: a -> Bool \ ef, l: List[a]): List[a] \ ef =
    flatMap(x -> if (p(x)) Cons(x, Nil) else Nil, l)
// 利用例
flix> \\
> filter(
>     // ℹ️ バッククォートで括ると中置記法に
>     x -> x `Int32.remainder` 2 != 0,
>     Cons(1, Cons(2, Cons(3, Nil)))
> )
> \\
Cons(1, Cons(3, Nil))
  • 他にも多様な高階関数を定義できる

  • 他のデータ構造についても同様に整備できる


第4章 例外によらないエラー
ハンドリング


値の有無を区別したい ⇒ Option/Maybe

pub enum Option[a] with Eq, ToString {
    case None     // 値がない場合
    case Some(a)  // 値がある場合
}
  • 例外は参照透過性を損なう副作用なので避ける

    • 代わりに代数的データ型で表す
    • ⇒ コレクションと同様に豊かな操作の対象に
  • Flix標準ライブラリ: Option


処理の成否を区別したい ⇒ Either/Result

pub enum Either[e, a] with Eq, ToString {
    case Left(e)   // 失敗の場合: エラー値を持つ
    case Right(a)  // 成功の場合: 成功値を持つ
}
  • Flix標準ライブラリ: Result

データの操作パターンを抽象化したい ⇒ 高階関数

pub def flatMap(f: a -> Option[b] \ ef, o: Option[a]):
  Option[b] \ ef =
    match o {
        case None => None
        case Some(x) => f(x)
    }
pub def flatMap(f: a -> Either[e, b] \ ef, et: Either[e, a]):
  Either[e, b] \ ef =
    match et {
        case Left(x) => Left(x)
        case Right(y) => f(y)
    }

pub def map(f: a -> b \ ef, o: Option[a]):
  Option[b] \ ef = ???

pub def filter(p: a -> Bool \ ef, o: Option[a]):
  Option[a] \ ef = ???
// 利用例
flix> map(x -> x / 2, Some(42))
Some(21)
flix> map(x -> x / 2, None)
None
flix> filter(x -> x `Int32.remainder` 2 != 0, Some(42))
None
flix> filter(x -> x `Int32.remainder` 2 != 0, None)
None

pub def map(f: a -> b \ ef, et: Either[e, a]):
  Either[e, b] \ ef = ???

// ⚠️ Eitherのfilterは無条件には定義できない
//    (述語を満たさない場合に採用するエラー値が定まらないため)
// 利用例
flix> map(x -> x / 2, (Right(42): Either[String, Int32]))
Right(21)
flix> map(x -> x / 2, Left("falsity"))
Left(falsity)

// 2引数関数を2個のOption値に適用してOptionを返す関数
pub def map2(f: (a, b) -> c \ ef, o1: Option[a],
  o2: Option[b]): Option[c] \ ef =
    flatMap(x -> map(y -> f(x, y), o2), o1)
// 利用例
flix> map2((+), Some(1), Some(2))
Some(3)
flix> map2((+), None, Some(2))
None
flix> map2((+), Some(1), None)
None
flix> map2(((+): (Int32, Int32) -> Int32), None, None)
None
  • Either についても同様に定義できる

// Optionを返す関数をリストの各要素に適用してリストのOptionを返す関数
pub def traverse(f: a -> Option[b] \ ef, l: List[a]):
  Option[List[b]] \ ef =
    List.foldRight(
        (x, acc) -> map2(List.cons, f(x), acc),
        Some(Nil),
        l
    )
// OptionのリストをリストのOptionにする関数
pub def sequence(l: List[Option[a]]): Option[List[a]] =
    traverse(identity, l)
// 利用例: ℹ️ 標準ライブラリのListを利用
flix>  traverse(x -> if (x `Int32.remainder` 2 != 0) Some(x)
  else None, List#{1, 2, 3})
None
flix>  traverse(x -> if (x `Int32.remainder` 2 != 0) Some(x)
  else None, List#{1, 3, 5})
Some(1 :: 3 :: 5 :: Nil)
  • Either についても同様に定義できる

エラー値を蓄積したい ⇒ Validated/Validation

pub enum Validated[e, a] with Eq, ToString {
    case Valid(a)         // 有効な場合: 有効値を持つ
    case Invalid(Nel[e])  // 無効な場合: エラー値の非空リストを持つ
}
  • Flix標準ライブラリ: Validation
    • ℹ️ 性能特性から Nel (non-empty list)ではなく Nec (non-empty chain)が採用されている

第5章 正格性と遅延性


キーワード

  • 非正格性(nonstrictness)、非正格評価(nonstrict evaluation)

    • ↔ 正格性(strictness)、正格評価(strict evaluation)/先行評価(eager evaluation)
    • 遅延性(laziness)、遅延評価(lazy evaluation)
  • 計算の記述(description)と評価(evaluation)の分離

  • 余再帰関数(corecursive function)


必要になるまで実体化されないデータ構造がほしい
⇒ 代数的データ型 + 評価を制御する工夫

pub enum LazyList[a] {
    case Empty
    // ℹ️ Lazyは遅延評価される式を表すFlixの組み込み型
    case Cons(Lazy[a], Lazy[LazyList[a]])
}
pub def take(n: Int32, ll: LazyList[a]): LazyList[a] =
    match ll {
        case Cons(head, tail) if n > 1 =>
            // ℹ️ lazyは直後の式を遅延評価されるようにし、
            //    forceは直後の遅延評価式を実体化する
            Cons(head, lazy take(n - 1, force tail))
        case Cons(head, _) if n == 1 =>
            Cons(head, lazy Empty)
        case _ => Empty
    }

// nからの整数列
pub def intsFrom(n: Int32): LazyList[Int32] =
    Cons(lazy n, lazy intsFrom(n + 1))
// フィボナッチ数列
pub def fibs(): LazyList[BigInt] =
    def go(current, next) =
        Cons(lazy current, lazy go(next, current + next));
    go(0ii, 1ii)
// 利用例
flix> LazyList.intsFrom(0) |> LazyList.map(x -> x * x)
  |> LazyList.take(5) |> LazyList.toList
0 :: 1 :: 4 :: 9 :: 16 :: Nil

flix> LazyList.fibs() |> LazyList.take(10) |> LazyList.toList
0 :: 1 :: 1 :: 2 :: 3 :: 5 :: 8 :: 13 :: 21 :: 34 :: Nil

第6章 純粋関数型の状態


擬似乱数を生成したい ⇒ 状態を受け渡す純粋関数

// シード値を持つRng (random number generator, 乱数生成器)型
pub enum Rng(Int64) with Eq, ToString
// Rngを受け取って整数と新たなRngを返す関数
pub def nextInt(rng: Rng): (Int32, Rng) =
    let Rng(seed) = rng;
    // 線形合同法による乱数生成
    let newSeed = Int64.bitwiseAnd(
      seed * 25214903917i64 + 11i64, 281474976710655i64);
    let nextRng = Rng(newSeed);
    let n = Int64.rightShift(newSeed, 16)
        |> Int64.truncateToInt32;
    (n, nextRng)
// Rngを受け取って任意のランダム生成値と新たなRngを返す関数の型エイリアス
pub type alias Rand[a] = Rng -> (a, Rng)
  • Flix標準ライブラリ: Math.Random (エフェクト)

// 自然に定義できるユーティリティ関数の例
pub def unit(a: a): Rand[a] = ???
pub def map(f: a -> b, ra: Rand[a]): Rand[b] = ???
pub def map2(f: (a, b) -> c, ra: Rand[a], rb: Rand[b]):
  Rand[c] = ???
pub def flatMap(f: a -> Rand[b], ra: Rand[a]): Rand[b] = ???
// ランダム関数の例
pub def int(): Rand[Int32] = rng -> nextInt(rng)
pub def boolean(): Rand[Bool] =
    rng ->
        let (i, rng2) = nextInt(rng);
        (i `Int32.remainder` 2 == 0, rng2)
// 利用例
flix> map2((i, b) -> "int: ${i}, boolean: ${b}", int(),
  boolean())(Rng(42i64))
(int: 16159453, boolean: false, Rng(197491923327988))

flix> map2((i, b) -> "int: ${i}, boolean: ${b}", int(),
  boolean())(Rng(197491923327988i64))
(int: -340305902, boolean: true, Rng(149370390209998))

第2部 関数型設計とコンビネータ
ライブラリ

  1. 純粋関数型の並列処理
  2. プロパティベーステスト
  3. パーサーコンビネータ

第7章 純粋関数型の並列処理


並列/並行処理したい ⇒ 処理の記述と実行の分離

// マルチスレッド実行管理オブジェクトを受け取り非同期処理の結果を返す関数
enum Par[a](ExecutorService -> Future[a] \ IO)

pub def unit(a: a): Par[a] =
    Par(_es -> /* ExecutorServiceを利用せず直ちにaを返す */)
pub def fork(pa: Lazy[Par[a]]): Par[a] =
    Par(es -> /* ExecutorServiceを利用して非同期的にタスク実行 */)
pub def runPar(es: ExecutorService, pa: Par[a]):
  Future[a] \ IO =
    let Par(f) = pa;
    f(es)

// 自然に定義できるユーティリティ関数の例
pub def flatMap(f: a -> Par[b], pa: Par[a]): Par[b] = ???
pub def map2(f: (a, b) -> c, pa: Par[a], pb: Par[b]):
  Par[c] = ???
pub def map(f: a -> b, pa: Par[a]): Par[b] = ???
// 並列処理を実行する関数の例
pub def runWithMap2(f: (a, b) -> c, pa: Par[a], pb: Par[b]):
  c \ IO =
    let es = Executors.newFixedThreadPool(4);
    let p = map2(f, pa, pb);
    let v = runPar(es, p).get();
    es.shutdown();
    v
// 利用例
flix> runWithMap2((a, b) -> a + b, unit(2), unit(3))
5

flix> runWithMap2((a, b) -> a ++ b, unit(List#{1, 2}),
  unit(List#{3, 4, 5}))
1 :: 2 :: 3 :: 4 :: 5 :: Nil

第8章 プロパティベーステスト


;; TODO: 実装に立ち入らず、コアのモデル(型定義)とできることを手短に紹介


第9章 パーサーコンビネータ


;; TODO: 実装に立ち入らず、コアのモデル(型定義)とできることを手短に紹介


第3部 関数型設計に共通する構造

  1. モノイド
  2. モナド
  3. アプリカティブ/トラバーサブルファンクター

第10章 モノイド


キーワード

  • ???

;; TODO


第11章 モナド


キーワード

  • ???

;; TODO


第12章 アプリカティブ/
トラバーサブルファンクター


キーワード

  • ???

;; TODO


第4部 エフェクトとI/O

  1. 外部エフェクトとI/O
  2. 局所エフェクトと可変状態
  3. ストリーム処理とインクリメンタルI/O

第13章 外部エフェクトとI/O


;; TODO: 実装に立ち入らず、コアのモデル(型定義)とできることを手短に紹介 + Flixでのエフェクトベースのアプローチを紹介


第14章 局所エフェクトと可変状態


;; TODO: 実装に立ち入らず、コアのモデル(型定義)とできることを手短に紹介 + Flixでのエフェクトベースのアプローチを紹介


第15章 ストリーム処理と
インクリメンタルI/O


;; TODO: 実装に立ち入らず、コアのモデル(型定義)とできることを手短に紹介


おわりに: 関数型プログラミングの抽象化過程

書籍 FP in Scala から見えてくる、また、Flix言語でも推奨されている段階的な習熟/活用のステップ:

  1. 再帰代数的データ型とパターンマッチに親しむ
  2. よくある操作パターンを高階関数として定義/利用できるようになる
  3. 様々な型で繰り返し現れる(高階)関数を型クラスとして抽象化できるようになる

🐬< FP in Scala もFlixも(純粋)関数型プログラミングの深淵への入口として非常にオススメ😈


Further Reading

書籍 FP in Scala


Flix

関数型プログラミング(言語)

#!/usr/bin/env bash
# pip install mkslides
open http://localhost:8000 \
&& mkslides serve *.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment