2

私は過去に Haskell に手を出していましたが、最近また真剣に取り組んでおり、現実世界の Haskell を読んでいます。彼らが輝かせた例のいくつかは、私にはまだ理解できていません。これでそのような:

myLength []     = 0
myLength (x:xs) = 1 + myLength (xs)

これがどのように機能するのかわかりません。実際に 1 も追加されるのは何ですか? 再帰は、追加できるものをどのように返しますか? 理解できません。

そして、ここにこれがあります:

splitLines [] = []
splitLines cs =
       let (pre, suf) = break isLineTerminator cs
       in  pre : case suf of 
                   ('\r':'\n':rest) -> splitLines rest
                   ('\r':rest)      -> splitLines rest
                   ('\n':rest)      -> splitLines rest
                   _                -> []

isLineTerminator c = c == '\r' || c == '\n'

これはどのように機能しますか、実際にプレも取り付けられているのは何ですか? case 式の結果がどのように pre に連結できるのかわかりません。これらの関数の評価を詳しく説明してくれる人が必要なだけかもしれません。私は何か非常に重要なものを見逃しているに違いありません。

前もって感謝します!

編集:わかっています、それはコピーと貼り付けの失敗でした。ごめん。

編集 2: 私の混乱は、これらの関数が実際に /returning しているものにあったようです。答えてくれてありがとう、ついにクリックしました!それは有り難いです!

4

5 に答える 5

10

最初のものに関しては、これは再帰の非常に基本的な方法です。ただし、次の部分が欠けているようです。

myLength [] = 0

リストから一度に 1 つの要素を縮小し、結果に 1 つ追加することで機能します。視覚化するには、呼び出しを検討してください

myLength [1,2,3]

これは次のように評価されます。

1 + myLength [2,3]
1 + 1 + myLength [3]
1 + 1 + 1 + myLength []
1 + 1 + 1 + 0

これは 3 です。

2 つ目については、次の改行で既に文字列が pre と suf の 2 つの部分に分割されています。これで、suf は \n、\r、または \r\n のいずれかで始まります。これらを削除したいと考えています。そのため、パターン マッチングを使用します。rest 変数が本質的に suf 変数から最初の改行文字を引いたものであることがわかります。

したがって、最初の行である pre と、残りのテキストである rest があります。したがって、rest を行に分割し続けるために、再帰的に splitLines を呼び出し、結果を pre に連結します。

視覚化するために、「foo\nbar\r\nbaz」という文字列があるとします。

したがって、呼び出すと、結果は次のようになります。

[ pre => foo, suf => \nbar\r\nbaz, rest => bar\r\n\baz ]
foo : splitLines bar\r\nbaz

その後、splitLines が再度呼び出され、結果が次のように展開されます。

[ pre => bar, suf => \r\nbaz, rest = baz ]
foo : bar : splitLines baz

それからもう一度:

[ pre => baz, suf => [], rest = [] ]
foo : bar : baz

これが最終結果です。

于 2009-05-25T21:09:18.090 に答える
4

myLengthの定義は、リストが空の場合を見逃していると思います:

myLength [] = 0
myLength (x:xs) = 1 + myLength (xs)

この定義でmyLengthは、空のリストの は 0 です。パターンは、リストを最初のアイテムと、残りの(x:xs)アイテムを含むリスト にアンパックします。リストに項目が 1 つある場合、は空のリストになるため、結果は 1 + 0 になります。axsxs

再帰は、最初に基本ケースを見て、次に再帰の各レベルが結果に基づいてどのように構築されるかを理解するのが最も簡単です。(基本ケースは、関数がそれ自体を呼び出さないケースです。再帰関数に基本ケースがない場合、出力は無限になります。)

2 番目の例では、ベース ケース (ケース ステートメントの最後のケース) も空のリストです。したがって、 pre は常にリストに追加され、新しい、より長いリストが生成されます。

于 2009-05-25T20:53:24.347 に答える
2

Re: myLength (x:xs) = 1 + myLength (xs)-- これは の定義の「半分」です。myLengthパターン マッチによると、引数に先頭と末尾がある場合、結果は末尾の再帰的な末尾呼び出しよりも 1 つ多くなります -- 必要がある引数が一致しないx:xs場合、つまり引数が空のリストの場合、結果は 0 であると言う別の半分になります。

2 番目のケースでは、さまざまなパターンの一致の可能性が、 によってもう少し明確になりcaseます。

ところで、怠惰はここでは重要な問題ではありません。ML は熱心な評価を行いますが、Haskell のようなパターン マッチングは非常によく似ています。パターンマッチングは、本当にブラッシュアップする必要があるようです。

于 2009-05-25T20:53:57.433 に答える
2

まず、最初の例は次のようになります (編集:修正したようです):

myLength []     = 0
myLength (x:xs) = 1 + myLength (xs)

これは次のように機能します: 3 つの項目を含むリストを指定すると、1 に尾の長さを加えた値が返されます (これは 1 に尾の長さを加えたものです (これは 1 に尾の長さを加えたものです)) []。ポイント)、これは 1)、これは w)、これは 3 (最終的な答え) です。ネストされた括弧が理解に役立つかもしれません。;-)

于 2009-05-25T21:04:25.260 に答える
1

関数の型シグネチャがどうなるかを見ることは有益です。それらは次のとおりです。

myLength :: [a] -> Int

ではmyLength、 への再帰呼び出しの結果に 1 が追加されています。myLengthこれは でありInt、結果は になりますInt

splitLines :: [Char] -> [[Char]]

ではsplitLinespre(a [Char]) が case ステートメントの結果の先頭に追加されています。これは、ケースを見ると、 への再帰呼び出しの結果であるか、splitLinesまたは[[Char]]です。または空のリスト。どちらの場合も、先頭にpre(a [Char]) を追加すると[[Char]]、順番に a になります。

于 2009-05-25T21:05:25.537 に答える