1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Hello World あたたたた F#編

Last updated at Posted at 2025-12-25

この記事は Hello World あたたたた Advent Calendar 2025 の25日目の記事です。

概要

こちらを参照。

面白そうだったので、F#を使い、しかしレギュレーションから外れて「処理フロー」から逸脱したロジックで実装してみました。

実装

open System
let solve =
    let rec loop cnt flg (rnd : Random) =
        seq {
            match cnt with
            | 4 when flg -> yield ""
            | _ ->
                let (ata, nf, nc) = if rnd.Next(2) = 0 then ("あ", true, 0) else ("た", flg, cnt + 1)
                yield ata
                yield! loop nc nf rnd
        }

    loop 0 false (new Random())

solve
|> String.concat ""
|> printfn "%s"

printfn "お前はもう死んでいる"

実行結果

image.png

解説

open

まずは乱数生成に必要なRandomクラスを使うためにSystem名前空間をオープンします。

open System

関数solve

この関数では「あ」と「た」からなるシーケンスを作成しています。

関数の中に関数を定義していて、中身の関数は再帰関数になっています。この再帰関数がほぼ本体です。

繰り返し(再帰処理)

    let rec loop cnt flg (rnd : Random) =
        seq {
            match cnt with
            | 4 when flg -> yield ""
            | _ ->
                let (ata, nf, nc) = if rnd.Next(2) = 0 then ("あ", true, 0) else ("た", flg, cnt + 1)
                yield ata
                yield! loop nc nf rnd
        }

loopという関数名の通り、ループするために定義した関数です。今回whileでループせず再帰ループする点がレギュレーションから外れている点ですね。
この関数の引数はそれぞれ、cntが「た」の連続出現数、flgが「あ」が出現したか、rndが乱数生成用のインスタンスです。

そして戻り値としてシーケンスseqを返します。シーケンスはC#で言うとIEnumerableみたいなものですね。
シーケンスの中身ですが、match式によって処理を分岐しています。ifでもいいのですが、パターンマッチングというF#の機能をあえて使っています。

条件分岐

match cnt with
| 4 when flg -> yield ""
| _ ->
    let (ata, nf, nc) = if rnd.Next(2) = 0 then ("あ", true, 0) else ("た", flg, cnt + 1)
    yield ata
    yield! loop nc nf rnd

パターンは2パターン。cntの値が4でありflg = trueであるか、そうでないか。正直if elseで十分ですね。
前者の場合、シーケンス要素として""(空文字列)を返し、そこで処理が終わります(=シーケンスの終端)。

if rnd.Next(2) = 0 then ("あ", true, 0) else ("た", flg, cnt + 1)

後者の場合、まずはrnd.Next(2)で0か1の乱数を生成し、その結果によって後続処理で使う値を決めています。
F#の場合、ifは「if文」ではなく「if式」と呼ばれ、式になっていますので戻り値を持っていて、その結果を変数に入れることができます。これはC#でいう三項条件演算子? :に相当します。
乱数が0の場合、「あ」として処理します。そのため、出力用の「あ」と、出現フラグとしてtrue、そして「た」の連続数を0にリセットします。
1の場合は「た」として処理し、出力用に「た」、出現フラグは関数の引数をそのまま継承し、連続数に1を加算します。

文字の出力と再帰呼び出し

yield ata
yield! loop nc nf rnd

そして出力用の値を入れた変数atayieldで返します。

最後にyield!ですが、これは後続のシーケンスの中身をそれぞれリターンします。ここではloop関数を再帰呼び出しして次のシーケンスを生成していますので、その結果が順次リターンされます。

再帰関数を定義したら、最後にそれを呼び出します。

loop 0 false (new Random())

関数呼び出しとパイプライン

solve
|> String.concat ""
|> printfn "%s"

ここでは定義した関数solveを呼び出しています。
次の行にある|>はパイプライン演算子で、左側にある値を右側の関数の引数に渡す演算子です。読みやすさのために改行していますが、solve |> String.concat ""のように一行で書くこともできます。

String.concat関数は2つの引数を取ります。第1引数が「文字列同士を繋ぐセパレータ」で、第2引数が「string型のシーケンス」です。
基本的に|>演算子の右側に来る関数は「引数が1つしかない関数」です。よってString.concat ""というように、第1引数の""も含めた部分を1つの関数と捉えます。所謂カリー化というやつです。

そしてString.concat ""の戻り値はすべてのstringを連結した文字列になりますので、続くprintfn "%s"にその戻り値を渡して「あたたたた」を出力します。
printfn自体は1個以上の引数を取る関数で、第1引数に出力する文字列のフォーマットを指定します。%sは文字列を表す書式になっているので、第2引数として文字列を受け取るようになります。よってprintfn "$s"という関数は「文字列1つを受け取ってそれを出力する関数」になります。

最後

printfn "お前はもう死んでいる"

最後に決めゼリフ。

おわりに

というわけでwhileループしない実装をしてみました。
念のため言っておくと、F#にもwhile式はあります。ただその場合、変数を書き換えていくような実装になると思います。
F#の変数はミュータブルにもできるのですが、基本はイミュータブルなので、変数はイミュータブルにしたまま実装しようとした結果がこの形でした。

こんな感じの縛りプレイでコードを書いてみると、いつもと違った何かが見えてくるかもしれません。皆さんも是非。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?