関数 - Alexandria

概要
このページではCommon Lispの汎用ユーティリティであるAlexandriaの関数に関するオペレータを紹介します。

ensure-function関数: 関数でも関数指定子でも関数実体を返す

高階関数で関数を引数として渡すとき、クォート(')だけで渡しても、シャープクォート(#')で渡しても、多くの場合は適切に処理されます。しかし、実際にはクォートだけならそれはquoteスペシャルオペレータが使われるためシンボルが返され、シャープクォートならfunctionスペシャルオペレータが使われるため関数の実体が返されます。

このような細かい動作の違いを解消するため、関数の実体を確実に取り出すことができるのがAlexandriaのensure-function関数です。
(alexandria:ensure-function '+)
; => #<SYSTEM-FUNCTION +>

(alexandria:ensure-function #'+)
; => #<SYSTEM-FUNCTION +>

disjoin関数: 述語関数をorでつなぐ

Common Lispには「述語」と呼ばれる関数があり、条件判定を行うために用いられます。Alexandriaのdisjoin関数は、複数の述語関数を引数に取り、それらの述語のいずれかがtを返す場合にtを返すような「述語関数の合成」を行います。

例えば、「ゼロまたはプラス」という条件であれば以下のように記述できます。
;; 書き方サンプル1: 事実をそのまま記述する
(mapcar #'(lambda (x) (<= 0 x)) '(-1 0 1))
; => (NIL T T)

;; 書き方サンプル2: 手動で述語関数を組み合わせる
(mapcar #'(lambda (x) (or (zerop x) (plusp x))) '(-1 0 1))
; => (NIL T T)

;; 書き方サンプル3: disjoin高階関数を使う
(mapcar (alexandria:disjoin #'zerop #'plusp) '(-1 0 1))
; => (NIL T T)

合成すべき述語関数が多くなればdisjoinが便利です。

conjoin関数: 述語関数をandでつなぐ

Alexandriaのconjoin関数はdisjoinの逆で、述語関数をandで繋ぎます。
(mapcar (alexandria:conjoin #'integerp #'plusp) '(-1 0 1 1.0))
; => (NIL NIL T NIL)

compose関数: 関数を合成する

disjoinconjoinは述語関数の合成ですが、数学的な関数合成を行うのがcomposeです。

例えば、2乗を行うdouble関数を定義し、さらにANSI標準の1を加算する1+関数を合成するには以下のようにします。
(defun double (x) (expt x 2))
; => DOUBLE

(funcall (alexandria:compose #'double #'1+) 10)
; => 121
結果を見れば分かる通りこれは f(x)=x2 と g(x)=x+1 を合成した f(g(x)) = (x+1)2 です。右の関数が左の関数に合成されていき、最終的には一つの関数になります。

curry関数: 関数と引数をlambdaで包む

ある種の計算を行う際は、「カリー化」を使うと手軽で便利なことがあります。

例えば、年を表す数列'(15 16 17)があるとします。これは2000年分が省略されているので、全ての値に2000を足せば完全な年を表すことができます。
(mapcar (lambda (x) (+ 2000 x)) '(15 16 17))
; => (2015 2016 2017)

Alexandriaのcurry関数は上の例におけるlambdaを合成します。
(mapcar (alexandria:curry #'+ 2000) '(15 16 17))
; => (2015 2016 2017)

rcurry関数: 引数の順序を逆にしたcurry

rcurrycurryの違いは引数を適用する順序です。以下の例を見てください。
;; curry は (lambda (x) (- 10 x)) となる
(mapcar (alexandria:curry #'- 10) '(1 2 3))
; => (9 8 7)

;; rcurry は (lambda (x) (- x 10)) となる
(mapcar (alexandria:rcurry #'- 10) '(1 2 3))
; => (-9 -8 -7)

named-lambdaマクロ: 名前付のlambda

lambdaは「無名関数」のため、普通は「再帰」を書くことができません。再帰関数を定義する場合はdefunlabelsを使います。

Alexandriaのnamed-lambdalabelsを使ってlambdaを再実装したもので、再帰を扱うことができます。第5章「データと制御フロー」のlabelsの項で示したサンプルをnamed-lambdaで書いてみます。

(shadowing-import 'alexandria:named-lambda)
; => T

(defvar counter
  (named-lambda my-loop (lst sum)
    (if (null lst)
        sum
        (my-loop (cdr lst) (+ (reduce #'+ (car lst)) sum)))))
; => COUNTER

counter
; => #<FUNCTION MY-LOOP (LST SUM)
;      (BLOCK MY-LOOP
;       (IF (NULL LST) SUM (MY-LOOP (CDR LST) (+ (REDUCE #'+ (CAR LST)) SUM))))>

(funcall counter '((1 2 3) (4 5 6) (7 8 9 10)) 0)
; => 55

0 件のコメント :

コメントを投稿