F#でカレンダープログラミングをやってみました(その4・FINAL)

Myoga-SB-SDF-A2

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日の曜日と当月末日を求めるようにすればよいのです。

  1. DateTime.Nowから現在の日付を取得します。
  2. DateTime型のオブジェクトから1日の曜日と当月末日を返すラムダ式の引数に1.のDateTime.Nowを指定します。関数の戻り値は1つですが、タプルを活用することで複数の値を返すことができます。
  3. 1日の曜日は、DateTime.NowからAddDaysメソッドを使って「1 – 現在日」だけ引いた値だけ日付を進めた(すなわち、「現在日 – 1」だけ日付を過去に戻した)時のDateTime型のオブジェクトを取得し、そこからDayOfWeekプロパティで求めます。
  4. 当月末日は、DateTime.NowからDateTime.DaysInMonthメソッドを使って求めます。

そこで求めた1日の曜日と当月末日を使って、カレンダー用のリスト(シーケンス)を生成し、各要素を出力するとカレンダーが表示されます。

さらに「F#でカレンダープログラミングをやってみました(その1)」の「Calendar2.fs(GitHub内: Calendar.fs)」にある日付出力において、「for in文のinの後に指定するシーケンスの生成式で、範囲を[( – 1日の曜日 + 1 )..末日]にし、要素(ここではcurDay)が負の値や0の時、半角スペース3文字を出力する」というテクニックを生かし、カレンダー用リストの生成時にラムダ式を使って、各要素を半角スペース3文字(1日の曜日合わせのパディング)または日付の文字列(土曜日では改行記号も追加)で初期化しています。

CalendarFSI4

「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日にプログラミング生放送のブログにて、#カレンダープログラミング プチコンテストの選出コードが発表されました。

「#カレンダープログラミング プチコンテスト 2014」選出コード発表! – プログラミング生放送
慧: 募集終了から2ヶ月……! 「カレンダープログラミング プチコンテスト」、もう忘れちゃったかな? 今年の企画は今年のうちに! ま、今年のうちに終わらないんだけどね。 カレンダ...

そこに「Calendar6.fsx(GitHub内: Calendar.fsx)」のコードが選ばれました!

(※まだ「仮」決定みたいです)

カレンダーが楽しみです!

[END]

コメント

タイトルとURLをコピーしました