🛠️コンビネータ入門
ステートレスYコンビネータを定義し、他のステートレス関数やラムダ式から階乗数やフィボナッチ数を計算しますと
階乗はでかい数でもやった。
とりあえず、やる気をだすために、リファクタリングする。といっても関数名を短縮名から長い名前に変えるだけ。
var factorial = Y(function(f) {
return function (n) {
return n > 1 ? n * f(n - 1) : 1;
};
});
var fibonacci = Y(function(f) {
return function(n) {
return n > 1 ? f(n - 1) + f(n - 2) : n;
};
});
名前を変えると影響範囲が分かってくる。基本は関数Yを通して、新しい関数を作っているしくみのようだった。であれば、Yの中身を見ればいいが、もともとややこしいので、そのまま引数だけ見ると、Fを引数としてnを引数とした関数を戻り値にする関数がYの引数であることが分かる。
function Y(f) {
var g = f((function(h) {
return function() {
var g = f(h(h));
return g.apply(this, arguments);
}
})(function(h) {
return function() {
var g = f(h(h));
return g.apply(this, arguments);
}
}));
return g;
}
つまりここでの引数fは「nを引数とした関数を戻り値にする関数」であると意識しながらY自体はgを戻り値にしていて、gはhを引数としてとある関数を戻り値にする関数を引数にしたfでありfは「nを引数とした関数を戻り値にする関数」なのでなんとなくだがとある関数というのが、うまいことするとシーソーみたいに動くのかなというのは何と無しに分かる。
一度固有の処理に戻る。再帰的なセオリーで書かれているのが分かる。
var fibonacci = Y(function(f) {
return function(n) {
return n > 1 ? f(n - 1) + f(n - 2) : n;
};
});
nが1以下になったら結果を返すけどそれまではfを呼び出すことになる。繰り返すが「nを引数とした関数を戻り値にする関数」である。そんであのシーソー部分を冷静に見る。ダブルドラゴンと名付けた。
gは「hを引数としてとある関数を戻り値にする関数」を引数にしたfでこれはYの戻り値になるわけだが、この「とある関数」は引数にした関数hを引数にした結果をfで処理した結果を実行して返すのが引数も関数なのでこちらの引数をも「とある関数」とする。
と、再帰するらしい。。。。
要るか、これ?
一つ思ったのは、顕著に言語系によって構成が変わるところで、きれいに読めるのは毎度おなじみのピコリスプだった。こんな感じ
(de Y (F)
(let X (curry (F) (Y) (F (curry (Y) @ (pass (Y Y)))))
(X X) ) )
美しい。。。
そしてコード博覧会、まずはここで腕力魅せるジュリア
julia> """
# Y combinator
* `λf. (λx. f (x x)) (λx. f (x x))`
"""
Y = f -> (x -> x(x))(y -> f((t...) -> y(y)(t...)))
記号で攻めてきた
APL勢 Joy J
Y=. '('':''<@;(1;~":0)<@;<@((":0)&;))'(2 : 0 '')
(1 : (m,'u'))(1 : (m,'''u u`:6('',(5!:5<''u''),'')`:6 y'''))(1 :'u u`:6')
)
こっちがジェイ
DEFINE y == [dup cons] swap concat dup cons i;
fac == [ [pop null] [pop succ] [[dup pred] dip i *] ifte ] y.
こっちがジョイ
LISP系をもう一度、クロージャとかとか
(defn Y [f]
((fn [x] (x x))
(fn [x]
(f (fn [& args]
(apply (x x) args))))))
(def fac
(fn [f]
(fn [n]
(if (zero? n) 1 (* n (f (dec n)))))))
(def fib
(fn [f]
(fn [n]
(condp = n
0 0
1 1
(+ (f (dec n))
(f (dec (dec n))))))))
これがクロージャ
(defun Y (f)
((lambda (g) (funcall g g))
(lambda (g)
(funcall f (lambda (&rest a)
(apply (funcall g g) a))))))
(defun fac (n)
(funcall
(Y (lambda (f)
(lambda (n)
(if (zerop n)
1
(* n (funcall f (1- n)))))))
n))
(defun fib (n)
(funcall
(Y (lambda (f)
(lambda (n a b)
(if (< n 1)
a
(funcall f (1- n) b (+ a b))))))
n 0 1))
? (mapcar #'fac '(1 2 3 4 5 6 7 8 9 10))
(1 2 6 24 120 720 5040 40320 362880 3628800))
? (mapcar #'fib '(1 2 3 4 5 6 7 8 9 10))
(1 1 2 3 5 8 13 21 34 55)
コモンリスプ
(define Y
(lambda (X)
((lambda (procedure)
(X (lambda (arg) ((procedure procedure) arg))))
(lambda (procedure)
(X (lambda (arg) ((procedure procedure) arg)))))))
; Fib
(define Fib* (lambda (func-arg)
(lambda (n) (if (< n 2) n (+ (func-arg (- n 1)) (func-arg (- n 2)))))))
(define fib (Y Fib*))
(fib 6)
→ 8
; Fact
(define F*
(lambda (func-arg) (lambda (n) (if (zero? n) 1 (* n (func-arg (- n 1)))))))
(define fact (Y F*))
(fact 10)
→ 3628800
エコーリスプ
短く書けてる系REBOL Ruby
Y: closure [g] [do func [f] [f :f] closure [f] [g func [x] [do f :f :x]]]
みやすい
y = lambda do |f|
lambda {|g| g[g]}[lambda do |g|
f[lambda {|*args| g[g][*args]}]
end]
end
fac = lambda{|f| lambda{|n| n < 2 ? 1 : n * f[n-1]}}
p Array.new(10) {|i| y[fac][i]} #=> [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
fib = lambda{|f| lambda{|n| n < 2 ? n : f[n-1] + f[n-2]}}
p Array.new(10) {|i| y[fib][i]} #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
こちらはrubyでさすが
let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g
Ocaml APL勢とは違った安定性
Y: parse arg Y _; $= /*the Y combinator.*/
do j=1 for words(_); interpret '$=$' Y"("word(_,j)')';end; return $
REXX
善戦したsmalltalk
Y := [:f| [:x| x value: x] value: [:g| f value: [:x| (g value: g) value: x] ] ].
fib := Y value: [:f| [:i| i <= 1 ifTrue: [i] ifFalse: [(f value: i-1) + (f value: i-2)] ] ].
(fib value: 10) displayNl.
fact := Y value: [:f| [:i| i = 0 ifTrue: [1] ifFalse: [(f value: i-1) * i] ] ].
(fact value: 10) displayNl.
small talk
ポイントフリー (Point-free):
この「ポイント」とは、具体的な引数やデータの「点」を指すことが多い。ポイントフリースタイルでは、関数の定義において具体的な引数を使わないことからこの名前がつけられました。
固定小数点 (Fixed-point):
この「ポイント」は、数学的な「固定点」を指します。関数f(x)に対して、f(x) = x となるxの値を固定点と言います。固定小数点コンビネータは、この固定点を見つけるためのツールとして使われることが多い。