web-dev-qa-db-ja.com

Haskellに終了の折り目はありますか?

必要なデータがすでにある場合に終了できる折り畳みが必要です。

たとえば、5より大きい最初の3つの数値を見つける必要があります。終了にEitherを使用することにしました。コードは次のようになります。

    terminatingFold :: ([b] -> a -> Either [b] [b]) -> [a] -> [b]
    terminatingFold f l = reverse $ either id id $ fold [] l
      where fold acc [] = Right acc
            fold acc (x:xs) = f acc x >>= flip fold xs

    first3NumsGreater5 acc x =
      if length acc >= 3
        then Left acc
        else Right (if x > 5 then (x : acc) else acc)

もっと賢い/一般的なアプローチはありますか?

6
Stefan Dorn

関数の結果はリストであり、それが遅延して生成された場合、つまり、結果から1つのアイテムを抽出するには、アイテムが見つかるまで入力リストを評価するだけでよいことが望ましいです。

展開は十分に評価されていません これらの種類のタスク。入力リストの「消費」に焦点を合わせるのではなく、それを(内部アキュムレータと組み合わせて)要素ごとに結果を生成できるシードと考えてみましょう。

入力のまだ消費されていない部分とペアになっている汎用アキュムレータを含むSeedタイプを定義してみましょう:

{-# LANGUAGE NamedFieldPuns #-}
import Data.List (unfoldr)

data Seed acc input = Seed {acc :: acc, pending :: [input]}

さて、改定しましょうfirst3NumsGreater5は、要素がもうないという信号のSeedから次の出力要素を生成する関数として:

type Counter = Int

first3NumsGreater5 :: Seed Counter Int -> Maybe (Int, Seed Counter Int)
first3NumsGreater5 (Seed {acc, pending})
  | acc >= 3 =
    Nothing
  | otherwise =
    case dropWhile (<= 5) pending of
      [] -> Nothing
      x : xs -> Just (x, Seed {acc = succ acc, pending = xs})

これで、メイン関数を unfoldr で記述できます。

unfoldFromList ::
  (Seed acc input -> Maybe (output, Seed acc input)) ->
  acc ->
  [input] ->
  [output]
unfoldFromList next acc pending = unfoldr next (Seed {acc, pending})

それを機能させる:

main :: IO ()
main = print $ unfoldFromList first3NumsGreater5 0 [0, 6, 2, 7, 9, 10, 11]
-- [6,7,9]
6
danidiaz

通常、早期終了対応の折り畳みは、2番目の引数が厳密でない結合関数を持つfoldrです。ただし、その情報の流れは右から左(ある場合)ですが、左から右にしたい場合もあります。

可能な解決策は、foldr関数をleftフォールドとして作成することです。これにより、早期に停止することができます。

_foldlWhile :: Foldable t 
           => (a -> Bool) -> (r -> a -> r) -> r 
           -> t a -> r
foldlWhile t f a xs  =  foldr cons (\acc -> acc) xs a
  where
    cons x r acc | t x  =  r (f acc x) 
                 | otherwise  =  acc
_

目的に合わせて、tではなくaccをテストするには、xを調整する必要があります。


この関数は https://wiki.haskell.org/Foldl_as_foldr_alternativefoldlWhileであり、少し書き直されました。そこからの_foldl'Breaking_は、請求書に少し良く合うかもしれません。

遅延レデューサー関数を使用したfoldrは、unfoldrと同じように、コアカーションを完璧に表現できます。

そしてあなたのコードはすでにレイジーです:terminatingFold (\acc x -> Left acc) [1..] => _[]_。あなたが要求したように、それがこの答えが「より賢い」かどうかわからない理由です。


edit:@danidiazによるコメントに続いて、適切に遅延させるには、次のようにコーディングする必要があります。

_first3above5 :: (Foldable t, Ord a, Num a) 
             => t a -> [a]
first3above5 xs  =  foldr cons (const []) xs 0
   where
   cons x r i | x > 5  =  if i==2 then [x]
                                  else x : r (i+1)
              | otherwise  =  r i
_

これは、テストとカウントを抽象化することでさらに一般化できます。

もちろん、これはtake 3 . filter (> 5)を再実装するだけですが、foldrを使用して一般的に行う方法を示しています。

2
Will Ness