これらの違いは何ですか?
ウィキペディアでは、これらの用語を説明する情報はほとんどなく、明確なコードもありません。
これらの用語を説明する非常に簡単な例は何ですか?
corecursionはどのように再帰の二重ですか?
古典的なcorecusiveアルゴリズムはありますか?
これらの違いは何ですか?
ウィキペディアでは、これらの用語を説明する情報はほとんどなく、明確なコードもありません。
これらの用語を説明する非常に簡単な例は何ですか?
corecursionはどのように再帰の二重ですか?
古典的なcorecusiveアルゴリズムはありますか?
回答:
これを見るには多くの良い方法があります。私にとって最も簡単なことは、「帰納的」と「共帰納的定義」の関係について考えることです。
セットの帰納的定義はこのようになります。
セット「Nat」は、「Zero」がNatにあり、nがNatにある場合、「Succ n」がNatにあるような最小セットとして定義されます。
次のOcamlに対応
type nat = Zero | Succ of nat
この定義について注意すべきことの1つは、
omega = Succ(omega)
はこのセットのメンバーではありません。どうして?今では、Nがオメガを持たないことを除いて、Natと同じ要素をすべて持っている集合Nを考えてみましょう。明らかにゼロはNにあり、yがNにある場合、Succ(y)はNにありますが、Nは矛盾よりも小さいNatより小さくなります。だから、オメガはナットにありません。
または、おそらくコンピューター科学者にとってより便利です:
あるセット "a"が与えられた場合、 "リストof a"は、 "Nil"がaのリストにあり、xsがaのリストにあり、xが "Cons x xs"にある最小セットとして定義されます。 aのリストにあります。
のようなものに対応する
type 'a list = Nil | Cons of 'a * 'a list
ここで有効な言葉は「最小」です。「最小」と言わなかった場合、セットNatにバナナが含まれているかどうかを判断する方法はありません。
再び、
zeros = Cons(Zero,zeros)
omegaが有効なNatでなかったように、natのリストの有効な定義ではありません。
定義データを誘導このようにすることは、私たちが使用してそれに作用する関数を定義することができます再帰を
let rec plus a b = match a with
| Zero -> b
| Succ(c) -> let r = plus c b in Succ(r)
その後、誘導(特に、構造誘導)を使用して、「プラスaゼロ= a」のように、これに関する事実を証明できます。
私たちの証明は、a。
基本ケースでは、aをゼロにします。 plus Zero Zero = match Zero with |Zero -> Zero | Succ(c) -> let r = plus c b in Succ(r)
だから知っているplus Zero Zero = Zero
。自然にa
なりましょう。帰納的仮説を仮定しplus a Zero = a
ます。私たちは今、いることを示しplus (Succ(a)) Zero = Succ(a)
、これは明白ですので、plus (Succ(a)) Zero = match a with |Zero -> Zero | Succ(a) -> let r = plus a Zero in Succ(r) = let r = a in Succ(r) = Succ(a)
誘導により、このようにplus a Zero = a
すべてのためa
のNATで
もちろん、より興味深いことを証明できますが、これは一般的な考え方です。
これまで、「最小」セットにすることで得られた帰納的に定義されたデータを扱ってきました。そのため、今度は最大のセットにすることで取得する、共導的に定義されたcodataを使用したいと思います。
そう
aをセットにします。セット「Stream of a」は、aのストリーム内の各xについて、xが順序付けられたペア(head、tail)で構成され、headがaに、tailがaのストリームにあるような最大セットとして定義されます。
Haskellでは、これを次のように表現します
data Stream a = Stream a (Stream a) --"data" not "newtype"
実際、Haskellでは、組み込みのリストを通常使用します。これは、順序付けられたペアまたは空のリストです。
data [a] = [] | a:[a]
バナナは、順序付きペアでも空のリストでもないため、このタイプのメンバーでもありません。しかし、今私たちは言うことができます
ones = 1:ones
これは完全に有効な定義です。さらに、この共同データに対して共同再帰を実行できます。実際、関数は、再帰的および再帰的の両方である可能性があります。再帰は、データで構成されるドメインを持つ関数によって定義されましたが、共同再帰は、共同データである共同ドメイン(範囲とも呼ばれる)を持つことを意味します。プリミティブな再帰とは、小さなデータに到達するまで、常に小さなデータを「呼び出す」ことを意味していました。プリミティブな共再帰は、以前のデータ以上のデータに対して常に「自身を呼び出す」。
ones = 1:ones
原始的に再帰的です。関数map
(命令型言語の「foreach」のようなもの)は、原始的に再帰的(一種)であり、原始的に共再帰的です。
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x):map f xs
zipWith
関数とリストのペアを取り、その関数を使用してそれらを結合する関数についても同じことが言えます。
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = (f a b):zipWith f as bs
zipWith _ _ _ = [] --base case
関数型言語の典型的な例はフィボナッチ数列です
fib 0 = 0
fib 1 = 1
fib n = (fib (n-1)) + (fib (n-2))
これは原始的に再帰的ですが、無限のリストとしてよりエレガントに表現できます
fibs = 0:1:zipWith (+) fibs (tail fibs)
fib' n = fibs !! n --the !! is haskell syntax for index at
誘導/共誘導の興味深い例は、これら2つの定義が同じことを計算することを証明しています。これは読者の演習として残されています。
基本的に、corecursionは再帰アキュムレータスタイルであり、最初のケースから先に結果を構築しますが、通常の再帰はベースケースから先に結果を構築します。
(現在Haskellと言えば)。だからこそfoldr
(厳密結合機能付き)が再帰を表し、foldl'
(厳密櫛と、F。)/ scanl
/ until
/ iterate
/ unfoldr
/等corecursionを発現します。Corecursionはどこにでもあります。foldr
非厳密な櫛で。f。consを法とする末尾再帰を表します。
Haskellのガード付き再帰は、末尾再帰モジュロconsに似ています。
これは再帰です:
fib n | n==0 = 0
| n==1 = 1
| n>1 = fib (n-1) + fib (n-2)
fib n = snd $ g n
where
g n | n==0 = (1,0)
| n>0 = let { (b,a) = g (n-1) } in (b+a,b)
fib n = snd $ foldr (\_ (b,a) -> (b+a,b)) (1,0) [n,n-1..1]
($
「of」と読みます)。これがコアカージョンです:
fib n = g (0,1) 0 n where
g n (a,b) i | i==n = a
| otherwise = g n (b,a+b) (i+1)
fib n = fst.snd $ until ((==n).fst) (\(i,(a,b)) -> (i+1,(b,a+b))) (0,(0,1))
= fst $ foldl (\(a,b) _ -> (b,a+b)) (0,1) [1..n]
= fst $ last $ scanl (\(a,b) _ -> (b,a+b)) (0,1) [1..n]
= fst (fibs!!n) where fibs = scanl (\(a,b) _ -> (b,a+b)) (0,1) [1..]
= fst (fibs!!n) where fibs = iterate (\(a,b) -> (b,a+b)) (0,1)
= (fibs!!n) where fibs = unfoldr (\(a,b) -> Just (a, (b,a+b))) (0,1)
= (fibs!!n) where fibs = 0:1:map (\(a,b)->a+b) (zip fibs $ tail fibs)
= (fibs!!n) where fibs = 0:1:zipWith (+) fibs (tail fibs)
= (fibs!!n) where fibs = 0:scanl (+) 1 fibs
= .....
折りたたみ:http : //en.wikipedia.org/wiki/Fold_(higher-order_function)
Vitomir Kovanovicのブログでこれを確認してください。ポイントまで見つけました:
Lisp、haskell、pythonなどの関数型プログラミング機能を備えたプログラミング言語に見られる非常に優れた機能での遅延評価。変数値の評価がその変数の実際の使用に遅れることを意味します。
たとえば、このようなもので百万個の要素のリストを作成したい場合
(defn x (range 1000000))
、実際には作成されませんが、指定されただけで、実際にその変数を初めて使用するとき、たとえば、そのリストインタープリターは、そのリストの最初の10個の要素のみを作成します。したがって、最初の実行(10 xを取得)でこれらの要素が実際に作成され、同じ関数への以降のすべての呼び出しは既存の要素で機能します。これは、メモリ不足エラーなしで無限リストを作成できるため、非常に便利です。リストは、要求した量だけ大きくなります。もちろん、プログラムが大規模なデータコレクションを処理している場合、これらの無限リストの使用量がメモリ制限に達する可能性があります。
一方、コアカージョンは再帰と二重です。これはどういう意味ですか?再帰関数は、それ自体の用語で表されますが、コアカーシブ変数はそれ自体の用語で表されます。
これは例で最もよく表されます。
すべての素数のリストが欲しいとしましょう...