文字列から日付に

646 >> Name: デフォルトの名無しさん [sage] posted at: 2008/08/16(土) 17:28:38
以下はC#で書いた、"2003/4"みたいな文字列からDateTimeに変換する関数ですが。
これを関数型言語らしく書くと、どのように書けますか?
従来の思考が抜けなくて、どうやっても関数型言語で手続きしてしまいます。

DateTime? parse1(string s)
{
  var m = Regex.Match(s, @"^(\d+)/(\d+)$");
  if(!m.Success)
    return null;
  var year = int.Parse(m.Groups[1].Value);
  var month = int.Parse(m.Groups[2].Value);
  return new DateTime(year, month, 1);
}

関数型と手続き型

時刻用のデータ型は無いっぽいので、構造体でごまかす。今は (make-date :year 200X :month XX :day XX) できればいいやってことで。

(defstruct date
   year month day)

脱線するけどこんな感じのマクロをたまに書いてしまう。(date 2008 11 21) で (make-date :year 2008 :month 11 :day 21) を評価するマクロ。

(defmacro date (year month day)
  `(make-date :year ,year :month ,month :day ,day))

そして今勢いだけでこんなもの書いてしまった。上のような構造体のコンストラクタ(?)を (構造体名 引数1 引数2...) で評価するマクロを定義するマクロ。名前適当すぎるだろ・・・。

(defmacro define-constructor-sugar (struct &rest args)
  `(defmacro ,struct
     (,@args)
     (,(intern (concat "make-" (string struct)))
      ,@(mapcan (lambda (sym)
                  (list (intern (string sym) (find-package "keyword")) sym))
                args))))


話を戻して、とりあえず書いてみたらこーなった。let はいらないな。全部 parse-integer って書いてあるのがもにょもにょする。

(defun string->date (string)
  (let ((re "^\\([0-9]+\\)/\\([0-9]+\\)\\(?:/\\([0-9]+\\)\\)?$"))
    (if (string-match re string)
        (make-Date :year (parse-integer (match-string 1))
                   :month (parse-integer (match-string 2))
                   :day (parse-integer (or (match-string 3) "1"))))))

書き直し。長くなっちゃった。しかも day を省略できなくなっちゃった。マクロでキーワード不要にして mapcan 無くせば短くなると思ったけど、マクロに apply とかできなかった、しょんぼり。

(defun string->date (string)
  (if (string-match "^\\([0-9]+\\)/\\([0-9]+\\)\\(?:/\\([0-9]+\\)\\)?$" string)
      (apply 'make-date
             (mapcan 'list
                     '(:year :month :day)
                     (mapcar 'parse-integer
                             (mapcar 'match-string '(1 2 3)))))))

正規表現は使わないで書いてみる。やっぱり mapcan 邪魔いなぁ。

(defun string->date (string)
  (apply 'make-date
         (mapcan 'list
                 '(:year :month :day)
                 (split-string string "/"))))


関数型っぽいかどうかはよくわからんです。特にオチはないです。