再帰とコアカーソルの違いは何ですか?


55

これらの違いは何ですか?

ウィキペディアでは、これらの用語を説明する情報はほとんどなく、明確なコードもありません。

これらの用語を説明する非常に簡単な例は何ですか?

corecursionはどのように再帰の二重ですか?

古典的なcorecusiveアルゴリズムはありますか?


45
SOへの回答を参照してくださいstackoverflow.com/questions/10138735/… (ごめん、止められませんでした)
ハイパフォーマンスマーク

7
@ HighPerformanceMark、corecursionが何であるかを説明していません。別の質問が必要です
-Abyx

5
しかし、真剣に、これらの用語のウィキペディアの説明の何が問題になっていますか?
ハイパフォーマンスマーク

5
ウィキペディアのコアカージョンの説明はひどいです。コアカーソルが何であるかをまだ知らない人には意味がないと思います。
マルチン

9
@High Performance Mark:駄洒落を理解する前に間違いがあると思ってリンクを3回クリックしました。LOL-
ジョルジオ

回答:


24

これを見るには多くの良い方法があります。私にとって最も簡単なことは、「帰納的」と「共帰納的定義」の関係について考えることです。

セットの帰納的定義はこのようになります。

セット「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つの定義が同じことを計算することを証明しています。これは読者の演習として残されています。


1
@ user1131997ありがとう。私は、Javaへのコードの一部を翻訳することを計画しています、お楽しみに
フィリップ・JF

@PhilipJF:私は愚かだと思うが、「...明らかにゼロがNにあり、yがNにある場合、Succ(y)がNにある...」という理由がわかりません。yがSucc(y)= omegaを満たす何かである場合はどうなりますか?(あなたがゼロと成功回数のいずれかのプロパティを使用していないので、私は、成功回数=平方根とゼロ= 2置き換えることができます)
Taのタンディン

...そして私は見オメガ= 1
Taのタンディン

目標は、オメガが自然状態ではないことを示すことです。これは矛盾によって行われます。オメガがnatよりもnatの場合、N = nat-{omega}は法則を満たします。natが法律を満たしているためです。Nのyの場合、1。yはオメガではなく、2。natのyです。2から、natのSucc(y)がわかり、1 yでオメガではありませんSucc(y)はオメガではありません。したがって、NのSucc(y)にはゼロも含まれます。しかし、Nはnatよりも小さいです。これは矛盾です。したがって、natにはオメガは含まれません。
フィリップJF

ocamlには値の再帰があるため、これは少し嘘です。SML は、帰納的推論をサポートする唯一の「主流」のような言語です。
フィリップJF

10

基本的に、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)


4

Vitomir Kovanovicのブログでこれを確認してください。ポイントまで見つけました:

Lisp、haskell、pythonなどの関数型プログラミング機能を備えたプログラミング言語に見られる非常に優れた機能での遅延評価。変数値の評価がその変数の実際の使用に遅れることを意味します。

たとえば、このようなもので百万個の要素のリストを作成したい場合(defn x (range 1000000))、実際には作成されませんが、指定されただけで、実際にその変数を初めて使用するとき、たとえば、そのリストインタープリターは、そのリストの最初の10個の要素のみを作成します。したがって、最初の実行(10 xを取得)でこれらの要素が実際に作成され、同じ関数への以降のすべての呼び出しは既存の要素で機能します。

これは、メモリ不足エラーなしで無限リストを作成できるため、非常に便利です。リストは、要求した量だけ大きくなります。もちろん、プログラムが大規模なデータコレクションを処理している場合、これらの無限リストの使用量がメモリ制限に達する可能性があります。

一方、コアカージョンは再帰と二重です。これはどういう意味ですか?再帰関数は、それ自体の用語で表されますが、コアカーシブ変数はそれ自体の用語で表されます。

これは例で最もよく表されます。

すべての素数のリストが欲しいとしましょう...


1
ブログサイトは死んでいます。
ジェイソン胡
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.