F#のサンプルコードを動かしてみた

こんにちは。エンジニアの今井です。

業務ではC#がメインなのですが関数型言語にも興味があります。そのため、今回は関数型言語のプラクティスのひとつである鉄道指向プログラミングをF#で書いてみました。

この鉄道指向プログラミングは大雑把に言うと、成功と失敗がある処理ひとつひとつをブロック化し、その処理の出力の仕方に応じて後段の処理を分岐させる手法です。

鉄道指向プログラミングの概略図

1. Result型

鉄道指向プログラミングでは、処理の入出力にエラー結果と正常な結果を一つの型で表現したものを使います。

この記事では、その入出力の型としてResult型を利用します。

// TはOKとErrorを表す列挙体のようなもの、TErrorは正常な結果のオブジェクトもしくはエラー情報
// この定義は以下に記載
type Result<'T,'TError> =
    | Ok of ResultValue:'T
    | Error of ErrorValue:'TError

(コードのエリアの言語をF#で指定できなかったためC#で代用…)

2. 鉄道指向プログラミングのコード

上記のResult型を使ったサンプルは以下になります。

これはMicrosoftのサンプルを説明のために改変したものです。

(https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/results)

// リクエストの型
type Request =
    { Name: string
      Email: string }

// 名前の検証
let validateName req =
    match req.Name with
    | null -> Error "No name found."
    | "" -> Error "Name is empty."
    | "bananas" -> Error "Bananas is not a name."
    | _ -> Ok req

// e-Mailの検証
let validateEmail req =
    match req.Email with
    | null -> Error "No email found."
    | "" -> Error "Email is empty."
    | s when s.EndsWith("bananas.com") -> Error "No email from bananas.com is allowed."
    | _ -> Ok req

// 検証のパイプライン
let validateRequest reqResult =
    reqResult
    |> Result.bind validateName
    |> Result.bind validateEmail

// 処理実行関数
let test() =
    // リクエスト型の生成
    let req1 = { Name = "Phillip"; Email = "phillip@contoso.biz" }

    // 検証のパイプライン実行
    let res1 = validateRequest (Ok req1)
    match res1 with
    | Ok req -> printfn $"My request was valid! Name: {req.Name} Email {req.Email}"  
    | Error e -> printfn $"Error: {e}"
    // 成功した場合に表示される文: "My request was valid!  Name: Phillip Email: phillip@consoto.biz"

test()

さて、このコードでは鉄道指向プログラミングは以下の箇所で実現されています

// 検証のパイプライン
let validateRequest reqResult =
    reqResult
    |> Result.bind validateName
    |> Result.bind validateEmail

let test() =
    // 省略
    let res1 = validateRequest (Ok req1)
    // 省略

このコードでは最初にvalidateRequestに(Ok req1)を渡しています。そのため、validateRequestは以下のようになります。

let validateRequest reqResult =
    (Ok req1)
    |> Result.bind validateName
    |> Result.bind validateEmail

ここで疑問に思うのは、なぜreq1だけではダメなのかです。validateNameもvalidateEmailもRequest型以外に使いそうな記述はありません。

この理由は、Result.bindにあります。

Result.bindの概要は以下のようになります。ここからわかるとおり、Result.bindはResult型を受け取り、Errorであればスルー、Okであれば関数の実行をします。

bind f inp evaluates to match inp with Error e -> Error e | Ok x -> f x

https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-resultmodule.html

よって、validateRequestは以下のように読めます。

let validateRequest reqResult =
    (Ok req1)
    // 受け取ったResult型がエラーならスルー、OkならvalidateNameの実行
    |> Result.bind validateName
    // 受け取ったResult型がエラーならスルー、OkならvalidateEmailの実行
    |> Result.bind validateEmail

このようにして、Result型を使ってF#では鉄道指向プログラミングが実現できます。

おわりに

関数型言語では学習すべきことがたくさんありそうですが、今回の範囲だけでも今までにない有用性を感じました。そのため、引き続き学習を深めたいと思います。

また、本記事の内容は以下の記事がとても詳しいです。そのため、この記事の内容に興味が持たれた方がいたら、読んでみることをおすすめします。

https://github.com/yukitos/notes/blob/master/A_recipe_for_a_functional_app/Railway%20oriented%20programming.md

関連記事

プロジェクトストーリー

技術

コメント

この記事へのコメントはありません。

カテゴリー

TOP
TOP