F#でコンピュテーション式を使ってOptionな値をうまく扱う
とりあえず、以下のような型とか関数があるイメージで。findCompanyとlocationはそれぞれCompanyとAddressのOptionを返すように実装される想定で、ここではとりあえず固定でSomeな値を入れている。
printCompanyはCompanyとAddressが揃ったときにのみ呼び出される関数。まあ、例なので適当に。
code: fsharp
type Company = { Name: string }
type Address = { Pref: string; City: string }
let findCompany (id: string) : Company option = Some { Name = "ACME Company" }
let location (company: Company) : Address option = Some { Pref = "Tokyo"; City = "Chofu" }
let printCompany (company: Company) (address: Address) =
printfn "%s, %s" address.City address.Pref
printfn "%s" company.Name
このときこれらをうまく組み合わせようとすると、こんな感じになってしまう。
code: fsharp
match findCompany "id" with
| Some company ->
match location company with
| Some address -> printCompany company address
| _ -> ()
| _ -> ()
パターンマッチがネストする形になってすこぶる微妙である。パターンマッチをネストさせないにするとこういう書き方ができる。
code: fsharp
let maybeCompany = findCompany "id"
let maybeAddress = maybeCompany |> Option.bind location
match maybeCompany, maybeAddress with
| Some company, Some address -> printCompany company address
| _ -> ()
Optionモジュールも使って良い感じに書けた気がするけど、そもそもmaybeCompanyがNoneだった時点で早期リターンしてしまいたい気持ちになる。残念ながらF#に早期リターンのような概念はない(たぶん、maybe)。しかしながら、Noneをあたかも無視しているように書くことはできる。やってみる。 code: fsharp
open FSharpPlus
monad {
let! company = findCompany "id"
let! address = location company
printCompany company address
}
|> ignore
だいぶすっきりしたと思う。FSharpPlusというライブラリにあるmonadコンピュテーション式を使うことでこういうことができるようになる。正直コンピュテーション式の理屈はよく分かっていないし、モナドもよく分かっていない。ただ、こう書けると便利なので、今はそれでいいかなという気がしている。便利ー。 参考