Hello~! ミョウガです!
今回はF#のパイプライン処理を活用して、カレンダーを出力するプログラムを作成した時のお話です。
1. パイプライン処理とは?
ここでいうパイプライン処理とは、2つ以上の関数を連結して1つの繋がった処理にすることができます。
例えば、ある値に1を足してそれを2乗する処理はパイプライン処理(演算子は「|>」)を使って表すと以下のようになります。
let Add1 x = x + 1
let Square x = x ** 2
printfn "%d" Square ( Add1 11 )
↓
let Add1 x = x + 1
let Square x = x ** 2
11 |> Add1 |> Square |> printfn "%d"
メリットとしては処理の流れが見やすくなることかな。
2. パイプライン処理を活用してみました。
では早速、前回のプログラム「Calendar.fs(GitHUb内: CalendarOld.fs)」の14行目のSeq.iteri関数にある、カレンダー用リスト生成処理をパイプライン演算子を使って、関数の前に移動させてみます。
[<EntryPoint>]
let main argv =
// 現在の日付を取得し、当月1日の曜日と末日を求めます。
let now = System.DateTime.Today
let prePad = int( System.DateTime( now.Year, now.Month, 1 ).DayOfWeek )
let lastDay = System.DateTime.DaysInMonth( now.Year, now.Month )
// 1日のパディング部と日付(あらかじめ文字列に変換します)部のリストを連結し、カレンダー用リストを生成します。
[ for i in 1..prePad -> " " ] @ [ for d in 1..lastDay -> sprintf "%3d" d ]
// カレンダーを出力します。
// iはリストのインデックスであり、日曜始まりでです。dは日付もしくは空白スペースです。
// iの7の余剰が6、即ち土曜日であるもしくは月末日であれば、改行も出力します。
|> Seq.iteri ( fun i d -> printf ( if i % 7 = 6 || i + 1 = prePad + lastDay then "%s\n" else "%s" ) d )
0
こうすることで、「カレンダー用のリストを作成して、日付を出力する」という処理の流れがより掴みやすくなった他、Seq.iteri関数呼び出しの部分がちょっとスッキリしました。
また、日付の文字列への変換処理では、.NET FrameworkのString.Formatメソッドの代わりに、F#のsprintf関数を使いました。
3. コレクションの各要素に適用する処理を簡素にしてみました。
Seq.iteri関数呼び出しの部分をさらに簡素に(各要素をただ出力するだけ)しようと思い、週末・月末日での改行記号追加の処理をSeq.iteri内の関数から独立させてみました。
[<EntryPoint>]
let main argv =
// 現在の日付を取得し、当月1日の曜日と末日を求めます。
let now = System.DateTime.Today
let prePad = System.DateTime( now.Year, now.Month, 1 ).DayOfWeek |> int
let lastDay = System.DateTime.DaysInMonth( now.Year, now.Month )
// 1日のパディング部と日付(あらかじめ文字列に変換します)部のリストを連結し、カレンダー用リストを生成します。
[ for i in 1..prePad -> " " ] @ [ for d in 1..lastDay -> sprintf "%3d" d ]
// iはリストのインデックスであり、日曜始まりでです。dは日付もしくは空白スペースです。
// iの7の余剰が6、即ち土曜日であるもしくは月末日であれば、要素に改行を入れます。
// マッピングを終えたら、イテレーターで各要素を出力します。
|> Seq.mapi( fun i d -> if i % 7 = 6 || i + 1 = prePad + lastDay then d + "\n" else d ) |> Seq.iter( fun d -> printf "%s" d )
0
※このプログラムはhttps://gist.github.com/Nia-TN1012/c3b6002fc9dba0af15c6611ba519e807でも公開されています(※GitHubアカウントの統合のため、ニア側のGistに移動しました)。
17行目のSeq.mapi関数でカレンダー用リストの各要素に対し、週末・月末日に改行記号を追加します。
日付の出力処理では、各要素をそのまま出力するだけになりますので、Seq.iter関数の代わりにSeq.iter関数を使います。
4. 日付の文字列変換をショートコード化
前回までは日付の文字列変換処理にて、.NET FrameworkのString.Formatメソッドを使用していましたが、
プロ生ちゃんから「[ d.ToString().PadLeft( 3 ) ] の方が短くなる」というアドバイスをいただきました(※以下、「プロ生ちゃん提案法」とします)。
// dは日付の値です。
// 今までのコード
System.String.Format("{0,3}",d) // 31バイト
// ※open SystemでSystem名前空間をインポートした場合
String.Format("{0,3}",d) // 24バイト
↓
// プロ生ちゃん提案法
d.ToString().PadLeft(3) // 23バイト
String.Formatは静的メソッドのため、「System.String.Format」と完全な名前にするか、Systemをインポートする必要がありますが、ToStringとPadLeftメソッドはオブジェクトに対するメンバーなので、そのようなことは不要になります(しかも、変換処理だけを比較してみても、プロ生ちゃん提案法の方が短いです)。
さらに追及してみると・・・
もっとショートコード化できるかな・・・と、F#のリファレンスを巡回したところ、文字列への変換はF#のstring関数で出来ることに気付きました。
// dは日付の値です。
// プロ生ちゃん提案法
d.ToString().PadLeft(3) // 23バイト
↓
// 文字列変換をF#のstring関数にした場合
string(d).PadLeft(3) // 20バイト
さらに追求してみると、書式指定文字列への変換はF#のsprintf関数で出来ることに気付きました。
// dは日付の値です。
// 文字列変換をF#のstring関数にした場合
string(d).PadLeft(3) // 20バイト
↓
// sprintf関数を使った場合
sprintf"%3d"d // 13バイト
なんとString.Formatメソッドを使っていた時より約半分のコード量でできます!
5. おわりに
次回は、F#のカレンダープログラムの更なるショートコード化にチャレンジした時のお話をしていきたいと思います。
[END]
コメント