Gabrielがコメントで述べているように、手動の再帰(パターンマッチ(x:xs)
)とfoldr
非常に珍しい方法を組み合わせています。通常、手動再帰を使用するかfoldr
、再帰が「リストを使い果たすまでリストの要素に関数を繰り返し適用する」というパターンに従う場合に使用します。
あなたのadd
関数は次のようになっていると思います:
add :: String -> BST -> BST
add string Empty = MakeNode Empty string Empty
add string (MakeNode l s r) =
if string < s
then MakeNode (add string l) s r
else MakeNode l s (add string r)
これが邪魔にならないようにすると、関数listToTree
は通常2つの方法のいずれかで記述されます。1つ目は、パターンマッチングと再帰を使用することです。
listToTree [] = Empty
listToTree (x:xs) = add x (listToTree xs)
つまり、空のリストがある場合は空のツリーを返します。または、ヘッドの後にテールが続く場合は、リストのテールによって返されるツリーにリストのヘッドを追加します。
もう1つのアプローチはlistToTree
、リストを折りたたんで書くことです。これはあなたのために再帰を抽象化するので、あなたはただ書くことができます
listToTree = foldr add Empty
foldr
タイプがあるのでこれは機能します
foldr :: (a -> b -> b) -> b -> [a] -> b
とタイプがありadd
ますEmpty
add :: String -> BST -> BST
Empty :: BST
タイプを専門にするa
とb
、
foldr :: (String -> BST -> BST) -> BST -> [String] -> BST
つまり、
foldr add Empty :: [String] -> BST
これらのどれを好むべきですか?おそらく、最初のものは初心者が読んで理解するのが簡単です。ただし、Haskellの経験を積むにつれて、2番目のバージョンが理解しやすくなることがわかります。また、より簡潔であり、フォールドの観点から記述されているため、リスト融合ルールをより頻繁にトリガーできるため、コードの効率が向上する可能性があります。
foldrを理解する
私の意見では、フォールドを理解するための鍵は、フォールドがリストコンストラクターを指定した関数や定数に置き換えることを理解することです。Haskellには、リストに2つの可能なコンストラクターがあります。
[] :: [a]
(:) :: a -> [a] -> [a]
すべての構文を脱糖すると、リストは実際には次のようになります(これは有効なHaskellです-試してみてください!)
xs = 1 : 2 : 3 : []
を呼び出すfoldr op x0 xs
と、foldは、のすべてのコンストラクターを効果的に置き換え、(:)
すべてのコンストラクターをxs
:で置き換えます。op
[]
x0
foldr op x0 xs = 1 `op` 2 `op` 3 `op` x0
op
もちろん、ここにはあいまいさがあります。これは、アソシエートが左にあるのか右にあるのかがわからないためです。タイプが機能するためには、次のように、右に関連付ける関数を提供する必要があります(これが右フォールドと呼ばれる理由です)。
foldr op x0 xs = 1 `op` (2 `op (3 `op` x0))
左の折り目は同じですが、代わりに左に関連付けられる(そして、初期値をリストの最後ではなく最初に置く)ので、最終的には次のようになります。
foldl op x0 xs = ((x0 `op` 1) `op` 2) `op` 3