Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Monadic For-Yield

💡 お知らせ: このドキュメントはAIによって翻訳されています。表現に違和感がある場合は、原文(英語)を参照するか、翻訳にご協力ください。

Flix は、Scala の for 内包表記や Haskell の do 記法に似た、モナドの forM-yield 構文をサポートしています。forM 構文は、point と flatMap(これらは Monad トレイトによって提供されます)を使用するための糖衣構文です。forM 構文は、empty(これは MonadZero トレイトによって提供されます)を使用する guard 式もサポートしています。

たとえば、次のモナドの forM 式:

let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
forM (x <- l1; y <- l2)
    yield (x, y)

は、次のリストに評価されます:

(1, 1) :: (1, 2) :: (2, 1) :: (2, 2) :: Nil

ガード式を使用する

forM 式の中で ガード式 を使用することができます。たとえば、次のプログラム:

let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
forM (x <- l1; y <- l2; if x < y)
    yield (x, y)

は、次のリストに評価されます:

(1, 2) :: Nil

Option と Result を扱う

forM を使って Option データ型を扱うこともできます。たとえば:

def divide(x: Int32, y: Int32): Option[Int32] = 
    if (y == 0) None else Some(x / y)

def f(): Option[Int32] = 
    forM (
        x <- divide(5, 2);
        y <- divide(x, 8);
        z <- divide(9, y)
    ) yield x + y + z

ここで、関数 f は None を返します。なぜなら x = 5 / 2 = 2 であり、2 / 8 = 0 となるため、最後の除算が失敗するからです。

同様に、forM を使って Result[e, t] データ型を扱うことができます。たとえば:

use Sys.Console

def readLine(): Result[String, String] \ Console =
    Option.toOk("Unexpected end of input.", Console.readln())

def greet(): Result[String, Unit] \ Console =
    forM (
        fstName <- readLine();
        lstName <- readLine();
        ageLine <- readLine();
        ageNum  <- Int32.parse(10, ageLine)
    ) yield {
        Console.println("Hello ${lstName}, ${fstName}.");
        Console.println("You are ${ageNum} years old!")
    }

def main(): Unit \ Console =
    Console.println("Please enter your first name, last name, and age:");
    match greet() {
        case Ok(_)    => ()
        case Err(msg) => Console.eprintln(msg)
    }

ここで main は、ユーザーに名(first name)、姓(last name)、年齢の入力を促し、greet がそれらを読み取ります。Console.readln は Option[String] を返し、入力の終端では None になるため、readLine は Option.toOk を使ってこれを Result[String, String] に変換します。したがって、ローカル変数 fstName、lstName、ageLine は String です。Int32.parse を使用して ageLine を Int32 にパースします。これは Result[String, Int32] 値を返します。すべての操作が成功した場合、あいさつを表示して Ok(())(すなわち Unit の Ok)を返します。そうでなければ、最初の Err(msg) 値を返します。main は Unit を返さなければならないため、その結果をパターンマッチし、エラーメッセージがあれば出力します。

その他の Monad を扱う

forM は、Chain や Nel(空でないリスト)を含む、他の種類の Monad でも使用できます。たとえば、次のように書くことができます:

let l1 = Nel(1, 2 :: Nil);
let l2 = Nel(1, 2 :: Nil);
forM (x <- l1; y <- l2)
    yield (x, y)

これは、次の空でないリストに評価されます:

Nel((1, 1), (1, 2) :: (2, 1) :: (2, 2) :: Nil)

注意: 空でないリストでは if-ガードを使用することができません。なぜなら、そのような if-ガードは MonadZero トレイトのインスタンスを必要としますが、空でないリストはこれを実装していないからです(そのようなリストは空になることができないため)。

脱糖

forM 式は、Monad.flatMap、Applicative.point、MonadZero.empty を使用するための糖衣構文です。

たとえば、次の式:

let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
forM (x <- l1; y <- l2; if x < y)
    yield (x, y)

は、次のように脱糖されます:

Monad.flatMap(x -> 
    Monad.flatMap(y -> 
        if (x < y)
            Applicative.point((x, y))
        else 
            MonadZero.empty(), 
    l2), 
l1);