common-lispで関数のスタイルについて認識してみた

# common-lispで関数のスタイルについて認識してみた

## 改訂履歴

1. @nfunato 様よりご指摘を頂きました。LOOPマクロを利用した関数記述スタイルをconsしました。

## はじめに

[末尾再帰，継続，アクタ，コルーチン : セマンティックウェブ・ダイアリー](http://blog.livedoor.jp/s-koide/archives/1888679.html)

現時点で関数の書き方のスタイルとして、ざっくり4種類を認識しています。LOOPマクロでの関数記述スタイルを追加して、5種類の認識となります。

0. LOOPマクロ利用の関数 
1. 末尾再帰ではない再帰関数
2. 末尾再帰の再帰関数
3. 継続渡し
4. map系関数の利用

2つの簡単な関数をそれぞれのスタイルで記述してみました。

1. iota関数 : pythonのrange関数のようなもの。
2. 2bai関数 : リストの要素を2倍します。ただしリストの要素が3の倍数の場合は3倍します。

## LOOPマクロ利用の関数

LOOPマクロは難解ですが、慣れてしまえばかなり強力なのではないでしょうか。

```common-lisp
(defun iota-loop (start stop &optional (step 1))
  (loop for i from start below stop by step
        collect i))                     ; => IOTA-LOOP

(iota-loop 1 5)                         ; => (1 2 3 4)
(iota-loop 1 10 2)                      ; => (1 3 5 7 9)


(defun 2bai-loop (list)
  (loop as x in list
        if (= (mod x 3) 0) collect (* 3 x)
        else collect (* 2 x)))          ; => 2BAI-LOOP

(2bai-loop (iota-loop 1 5))             ; => (2 4 9 8)
```

## 末尾再帰ではない再帰関数

まず関数を書くときはこれ。とにかくわかりやすい。ただ、すぐに末尾再帰に変換してしまいますので、短命です。

```common-lisp
(defun iota (start stop &optional (step 1))
  (cond ((>= start stop) nil)
        (t (cons start (iota (+ start step)
                             stop
                             step)))))
                                        ; => IOTA

(iota 1 5)                              ; => (1 2 3 4)
(iota 1 5 2)                            ; => (1 3)

(defun 2bai (list)
  (cond ((null list) nil)
        ((= (mod (car list) 3) 0) (cons (* 3 (car list))
                                        (2bai (cdr list))))
        (t (cons (* 2 (car list))
                 (2bai (cdr list))))))
                                        ; => 2BAI

(2bai (iota 1 5))                       ; => (2 4 9 8)
```

## 末尾再帰の再帰関数

末尾再帰最適化の恩恵を享受するため、デフォルトこのスタイルになる。 (個人的には一番好き。)

```common-lisp
(defun iota-tail (start stop &optional (step 1) (acc nil))
  (cond ((>= start stop) acc)
        (t (iota-tail (+ start step)
                      stop
                      step
                      (append acc (list start))))))
                                        ; => IOTA-TAIL

(iota-tail 1 5)                         ; => (1 2 3 4)
(iota-tail 1 5 2)                       ; => (1 3)

(defun 2bai-tail (list &optional (acc nil))
  (cond ((null list) acc)
        ((= (mod (car list) 3) 0)
         (2bai-tail (cdr list)
                    (append acc
                            (list (* (car list) 3)))))
        (t (2bai-tail (cdr list)
                      (append acc
                              (list (* (car list) 2)))))))
                                        ; => 2BAI-TAIL

(2bai-tail (iota-tail 1 5))             ; => (2 4 9 8)

(trace 2bai-tail)                       ; => (2BAI-TAIL)
```

## 継続渡し (Continuation-passing style)

継続渡しの挙動を強調するため、
1. 関数の中で継続を実行せず、継続自体を返しています。
2. 関数の呼び出し元はfuncallで、継続を実行します。

```common-lisp
(defun iota-cps (cont start stop &optional (step 1))
  (cond ((>= start stop) cont)
        (t (iota-cps (lambda (x)
                       (funcall cont (cons start x)))
                     (+ start step)
                     stop
                     step))))
                                        ; => IOTA-CPS

(iota-cps #'values 1 5)
;; => #<CLOSURE (LAMBDA (X) :IN IOTA-CPS) {1004896FCB}>

(funcall (iota-cps #'values 1 5) nil)   ;; => (1 2 3 4)


(iota-cps #'values 1 5 2)
;; => #<CLOSURE (LAMBDA (X) :IN IOTA-CPS) {1004A5D16B}>

(funcall (iota-cps #'values 1 5 2) nil) ; => (1 3)


(defun 2bai-cps (cont list)
  (cond ((null list) cont)
        ((= (mod (car list) 3) 0)
         (2bai-cps (lambda (x)
                     (funcall cont
                              (cons (* 3 (car list))
                                    x)))
                   (cdr list)))
        (t (2bai-cps (lambda (x)
                       (funcall cont
                                (cons (* 2 (car list))
                                      x)))
                      (cdr list)))))
                                        ; => 2BAI-CPS

(funcall (2bai-cps #'values
                   (funcall (iota-cps #'values 1 5) nil))
         nil)                           ; => (2 4 9 8)
```

## map系関数の利用

高階関数を積極的に利用して、抽象度を上げがちにするスタイルでしょうか。とてもスッキリと書けます。

mapが処理対象のリストが存在することを前提としているため、iotaは書くことができませんでした。抽象度の低い関数ということでしょうか。

```common-lisp
;; (defun iota-hof (start stop &optional (step 1)))


(defun 2bai-hof (list)
  (mapcar (lambda (x)
            (cond ((= (mod x 3) 0) (* x 3))
                  (t (* x 2))))
          list))
                                        ; => 2BAI-HOF

(2bai-hof (iota-tail 1 5))              ; => (2 4 9 8)
```

2baiは、この規模であれば名前無し関数になるかと思います。

```common-lisp
(mapcar (lambda (x)
            (cond ((= (mod x 3) 0) (* x 3))
                  (t (* x 2))))
        (iota-tail 1 5))
                                        ; => (2 4 9 8)
```


それぞれメリット、デメリットがあります。状況に応じて使い分ける感じで！

ありがとうございました。何かのお役に立てれば幸いです。