web-dev-qa-db-ja.com

Haskell(:)と(++)の違い

このような質問で申し訳ありません。 Haskellの:演算子と++演算子の違いについてはあまりよくわかりません。

x:y:[] = [x,y]  

また

[x] ++ [y] = [x,y]

私にこの質問を引き起こした逆関数については、

reverse ::[a]->[a]
reverse [] = []
reverse (x:xs) = reverse(xs)++[x]

次のように動作しないのはなぜですか?

reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs):x:[]

型エラーを与える。

43
DarthVader

_:_演算子は「cons」演算子と呼ばれ、リストの先頭に要素を追加するために使用されます。したがって、_[]_はリストであり、_x:[]_は空のリストの前にxを追加して、リスト_[x]_を作成します。その後、_y:[x]_を否定すると、_[y, x]_と同じリスト_y:x:[]_になります。

_++_演算子は、2つのリストをオペランドとして受け取り、それらを単一のリストに「結合」するリスト連結演算子です。したがって、リスト_[x]_とリスト_[y]_がある場合、これらを_[x]++[y]_のように連結して、_[x, y_を取得できます。

_:_は要素とリストを受け取り、_++_は2つのリストを受け取ります。

動作しないコードについては。

_reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs):x:[]
_

逆関数はリストに評価されます。 _:_演算子はリストを最初の引数として受け取らないため、reverse(xs):xは無効です。ただし、reverse(xs)++[x]は有効です。

71

:要素をリストに構成します。

++は2つのリストを追加します。

前者にはタイプがあります

a -> [a] -> [a]

一方、後者にはタイプがあります

[a] -> [a] -> [a]
21
Brian

(++)との連結

おそらくこれを深く考えているのですが、私が理解している限り、たとえば_(++)_を使用してリストを連結しようとすると:

_[1, 2, 3] ++ [4, 5]
_

_(++)_は、完全な左リストを走査する必要があります。 (++)のコード を見ると、さらに明確になります。

_(++) :: [a] -> [a] -> [a]
(++) []     ys = ys
(++) (x:xs) ys = x : xs ++ ys
_

したがって、毎回reverse(xs)++[x]を呼び出すと、リストが大きくなる(または視点に応じて小さくなる)ので、_(++)_の使用を避けることが望ましいでしょう。呼び出しごとにリストする)

例:

連結を通じて提案されたように逆を実装するとしましょう。

_reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs)++[x]
_

リスト[1、2、3、4]を逆にすると、次のようになります。

_reversex [1, 2, 3, 4]
reversex [2, 3, 4]               ++ [1]
reversex [3, 4]           ++ [2] ++ [1]
reversex [4]       ++ [3] ++ [2] ++ [1]
reversex [] ++ [4] ++ [3] ++ [2] ++ [1]
         [] ++ [4] ++ [3] ++ [2] ++ [1]
         [4]       ++ [3] ++ [2] ++ [1]
         [4, 3]           ++ [2] ++ [1]
         [4, 3, 2]               ++ [1]
         [4, 3, 2, 1]
_

cons演算子を使用した末尾再帰(:)!!!

呼び出しスタックを処理する1つの方法は、 アキュムレーター を追加することです。 (常にアキュムレーターを追加することは常に可能とは限りません。しかし、扱う再帰関数のほとんどは primitive recursive であり、 tail recursive functions に変換できます。)

アキュムレータの助けを借りて、cons演算子_(:)_を使用して、この例を機能させることができます。アキュムレーター(この例ではys)は現在の結果を累積し、パラメーターとして渡されます。アキュムレータにより、cons演算子を使用して、最初のリストの先頭を毎回追加することで結果を蓄積できるようになりました。

_reverse' :: (Ord a) => [a] -> [a] -> [a]
reverse' (x:xs) ys = reverse' xs (x:ys)
reverse' [] ys     = ys
_

ここで注意すべきことが1つあります。

アキュムレータは追加の引数です。 Haskellがデフォルトのパラメーターを提供するかどうかはわかりませんが、この場合はニースです。なぜなら、次のように常にアキュムレーターとして空のリストを使用してこの関数を呼び出すからです:_reverse' [1, 2, 3, 4] []_

末尾再帰に関する多くの文献があり、StackExchange/StackOverflowにも同様の質問がたくさんあると確信しています。間違いを見つけたら私を修正してください。

敬具、

EDIT 1

ウィルネスは、興味のある方のための本当に良い答えへのリンクをいくつか指摘しました。

EDIT 2

OK。 dFeuerと彼の修正のおかげで、Haskellが少し良くなったと思う。

1. _$!_は私の理解を超えています。すべてのテストで、事態を悪化させているように見えました。

2. dFeuerが指摘したように:_(:)_のxおよびyへの適用を表すサンクは、_x:y_と意味的に同一ですが、より多くのメモリを必要とします。したがって、これはcons演算子(および遅延コンストラクター)にとって特別であり、何らかの方法で物事を強制する必要はありません。

3.代わりに、非常によく似た関数を使用してリストの整数を合計する場合、BangPatternsまたはseq関数による厳密な評価により、適切に使用するとスタックが大きくなりすぎないようにします。例えば。:

_sumUp' :: (Num a, Ord a) => [a] -> a -> a
sumUp' (x:xs) !y = reverse' xs (x + y)
sumUp' [] y      = y
_

Yの前の強打に注目してください。 ghciで試してみましたが、メモリの消費量は少なくなりました。

12
Nima Mousavi

consは、演算子よりも型コンストラクタになる傾向があります。ここの例は:let..in..の式で使用できますが、++は使用できません

let x : xs = [1, 2, 3] in x -- known as type deconstructing

1を返しますが

let [x] ++ [y, z] = [1, 2, 3] in x

エラーVariable not in scope xを返します

簡単にするには、consを次のように考えてください

data List a = Cons a (List a) -- is equvalent with `data [a] = a:[a]`

https://en.wikibooks.org/wiki/Haskell/Other_data_structures

また、短所を使用して配列を反転する場合。ここに例があります、知識はPrologから取られます

import Data.Function

reversex1 [] = []
reversex1 arr = reversex arr []

reversex [] arr = arr
reversex (x:xs) ys = reversex xs (x:ys)

main = do
    reversex1 [1..10] & print
3
Clite Tailor