Hello~! ミョウガです!
今回は、F#のカレンダープログラムの更なるショートコード化にチャレンジした時のお話です。
1. F#のカレンダープログラミングにおけるショートコード化カギはパイプライン演算子「|>」の活用にあり!?
「F#でカレンダープログラミングをやってみました(その3)」にある「Calendar5.fs(GitHub内: CalendarOld2.fs)」を以下に示します。
[<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
このプログラムの5~7行目の部分をもっと短くしてみたいなと思い、パイプライン演算子「|>」を使ってコードを工夫してみました。
open System
[<EntryPoint>]
let main argv =
// 現在の日付を取得し、当月1日の曜日と末日を求めます。
DateTime.Now |> fun now -> ( now.AddDays( float( 1 - now.Day ) ).DayOfWeek |> int, DateTime.DaysInMonth( now.Year, now.Month ) )
// 範囲を[( - 1日の曜日 + 1 )..末日]にし、要素が負の時に空白を、そうでなければ日付を文字列に変換します。
// こうすることで1日の曜日に合わせてオフセットすることができます。
// また、要素の位置が7の倍数なら、改行記号も付け足します。
|> fun( prePad, lastDay ) -> [ for curDay in -prePad + 1..lastDay -> if curDay < 1 then " " else sprintf ( if ( curDay + prePad ) % 7 = 0 || curDay = lastDay then "%3d\n" else "%3d" ) curDay ]
// イテレーターで各要素を出力します。
|> Seq.iter( fun cal -> printf "%s" cal )
0
※このプログラムはhttps://gist.github.com/Nia-TN1012/c3b6002fc9dba0af15c6611ba519e807でも公開されています(※GitHubアカウントの統合のため、ニア側のGistに移動しました)。
このカレンダープログラムにて必要なものは、「1日の曜日」と「当月末日」の2つです。ということで、現在の日付を表す「DateTime.Now」からパイプライン演算子を使って、1日の曜日と当月末日を求めるようにすればよいのです。
- DateTime.Nowから現在の日付を取得します。
- DateTime型のオブジェクトから1日の曜日と当月末日を返すラムダ式の引数に1.のDateTime.Nowを指定します。関数の戻り値は1つですが、タプルを活用することで複数の値を返すことができます。
- 1日の曜日は、DateTime.NowからAddDaysメソッドを使って「1 – 現在日」だけ引いた値だけ日付を進めた(すなわち、「現在日 – 1」だけ日付を過去に戻した)時のDateTime型のオブジェクトを取得し、そこからDayOfWeekプロパティで求めます。
- 当月末日は、DateTime.NowからDateTime.DaysInMonthメソッドを使って求めます。
そこで求めた1日の曜日と当月末日を使って、カレンダー用のリスト(シーケンス)を生成し、各要素を出力するとカレンダーが表示されます。
さらに「F#でカレンダープログラミングをやってみました(その1)」の「Calendar2.fs(GitHub内: Calendar.fs)」にある日付出力において、「for in文のinの後に指定するシーケンスの生成式で、範囲を[( – 1日の曜日 + 1 )..末日]にし、要素(ここではcurDay)が負の値や0の時、半角スペース3文字を出力する」というテクニックを生かし、カレンダー用リストの生成時にラムダ式を使って、各要素を半角スペース3文字(1日の曜日合わせのパディング)または日付の文字列(土曜日では改行記号も追加)で初期化しています。
「Calendar6.fs(GitHub内: Calendar.fs)」と同じ動作をするプログラムを以下に示します。
open System[<EntryPoint>]let m a=DateTime.Now|>fun n->(n.AddDays(float(1-n.Day)).DayOfWeek|>int,DateTime.DaysInMonth(n.Year,n.Month))|>fun(p,l)->[for d in -p+1..l->if d<1 then" "else sprintf(if(d+p)%7=0||d=l then"%3d\n"else"%3d")d]|>Seq.iter(fun d->printf"%s"d);0
このコードのサイズは265バイトです(可読性はお察しください・・・)。
2. F#のスクリプトでエントリーポイントを取っ払いました
実はというとF#には ソースファイル(拡張子「.fs」)の他、スクリプト(拡張子「.fsx」)があります。Visual Studioのドキュメントによると、「少量のコードを実行する」目的で使用するみたいです。
早速、「Calendar6.fs(GitHub内: Calendar.fs)」をF#のスクリプト向けに改造したコードを以下に示します。
open System
// 現在の日付を取得し、当月1日の曜日と末日を求めます。
DateTime.Now |> fun now -> ( now.AddDays( float( 1 - now.Day ) ).DayOfWeek |> int, DateTime.DaysInMonth( now.Year, now.Month ) )
// 範囲を[( - 1日の曜日 + 1 )..末日]にし、要素が負の時に空白を、そうでなければ日付を文字列に変換します。
// こうすることで1日の曜日に合わせてオフセットすることができます。
// また、要素の位置が7の倍数なら、改行記号も付け足します。
|> fun( prePad, lastDay ) -> [ for curDay in -prePad + 1..lastDay -> if curDay < 1 then " " else sprintf ( if ( curDay + prePad ) % 7 = 0 || curDay = lastDay then "%3d\n" else "%3d" ) curDay ]
// イテレーターで各要素を出力します。
|> Seq.iter( fun cal -> printf "%s" cal )
※このプログラムはhttps://gist.github.com/Nia-TN1012/c3b6002fc9dba0af15c6611ba519e807でも公開されています(※GitHubアカウントの統合のため、ニア側のGistに移動しました)。コンテストにはこのプログラムで応募しました。
スクリプトでは上から順に実行するため、エントリーポイント(「[<EntryPoint>]」と「main関数」)が不要になります。
これと同じ動作をするプログラムを以下に示します。
open System;DateTime.Now|>fun n->(n.AddDays(float(1-n.Day)).DayOfWeek|>int,DateTime.DaysInMonth(n.Year,n.Month))|>fun(p,l)->[for d in -p+1..l->if d<1 then" "else sprintf(if(d+p)%7=0||d=l then"%3d\n"else"%3d")d]|>Seq.iter(fun d->printf"%s"d);0
コードのサイズは244バイトと、さらに小さくなりました!
(くどいですが、可読性はお察しください・・・)
3. おわりに
F#のカレンダープログラミングの開発話(本編)は以上です。これをやる前はF#について少々かじった程度でしたが、これをきっかけにリストの使い方やパイプライン処理などF#ならではの機能を学ぶことができました(F#の機能自体は他にも色々ありますが)。
それともう1つ、昨年の12月28日にプログラミング生放送のブログにて、#カレンダープログラミング プチコンテストの選出コードが発表されました。
そこに「Calendar6.fsx(GitHub内: Calendar.fsx)」のコードが選ばれました!
(※まだ「仮」決定みたいです)
カレンダーが楽しみです!
[END]
コメント