可以查看最终结果的一部分的同构

时间:2019-05-13 23:47:47

标签: haskell lazy-evaluation recursion-schemes catamorphism

是否有一个递归方案的名称,就像一个变态法,但它允许在仍运行时查看最终结果?这是一个精心设计的示例:

toPercents :: Floating a => [a] -> [a]
toPercents xs = result
  where
  (total, result) = foldr go (0, []) xs
  go x ~(t, r) = (x + t, 100*x/total:r)

{-
>>> toPercents [1,2,3]
[16.666666666666668,33.333333333333336,50.0]
-}

此示例在折叠的每一步都使用total,即使直到最后都不知道其值。 (显然,这取决于工作的懒惰。)

3 个答案:

答案 0 :(得分:3)

虽然这不一定是您想要的,但我们可以使用同胚性来编码懒惰技巧:

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-}

import Data.Functor.Foldable
import Data.Functor.Foldable.TH

data CappedList c a = Cap c | CCons a (CappedList c a)
    deriving (Eq, Show, Ord, Functor, Foldable, Traversable)
makeBaseFunctor ''CappedList

-- The seq here has no counterpart in the implementation in the question.
-- It improves performance quite noticeably. Other seqs might be added for
-- some of the other "s", as well as for the percentage; the returns, however,
-- are diminishing.
toPercents :: Floating a => [a] -> [a]
toPercents = snd . hylo percAlg sumCal . (0,)
    where
    sumCal = \case
        (s, []) -> CapF s
        (s, a : as) -> s `seq` CConsF a (s + a, as)
    percAlg = \case
        CapF s -> (s, [])
        CConsF a (s, as) -> (s, (a * 100 / s) : as)

这与懒惰技巧相对应,因为多亏了hylo融合,中间CappedList才真正建立起来,而toPercents仅一次通过消耗了输入列表。使用CappedList的要点是as moonGoose puts it,将总和放在(虚拟)中间结构的底部,以便使用percAlg完成的列表重建可以从中访问开始。

(也许值得一提的是,即使它是一次性完成的,但无论是我的版本还是您的版本,似乎都很难从此技巧中获得稳定的内存使用率。欢迎。)

答案 1 :(得分:2)

我认为没有明确的方案允许功能1窥视功能2的最终结果的每一步。但是,似乎有些奇怪。我认为最终可以归结为1)运行函数2,然后运行具有函数2已知结果的函数1(即两次通过),这是在您的内存中获得恒定内存的唯一方法例如)或2)并排运行它们,创建一个函数thunk(或依靠懒惰)以最终将它们组合在一起。

您给出的惰性foldr版本自然可以自然地转化为同构。这是功能化的同化形式,

{-# LANGUAGE LambdaCase -#}

import Data.Functor.Foldable    

toPercents :: Floating a => [a] -> [a]
toPercents = uncurry ($) . cata alg
  where
    alg = \case
        Nil -> (const [], 0)
        Cons x (f,s) ->  (\t -> 100*x / t : f t, s + x)

尽管在造型上看起来必须手动并行化两个变态现象似乎并不好,特别是因为那时它没有编码这样一个事实,即它们都不是逐步地依赖于另一个。 Hoogle可以找到bicotraverse,但这并不一定是通用的,因此让我们编写代数并行运算符(&&&&)

import Control.Arrow

(&&&&) :: Functor f => (f a -> c) -> (f b -> d) -> f (a,b) -> (c,d)
f1 &&&& f2 = (f1 . fmap fst &&& f2 . fmap snd)

toPercents' :: Floating a => [a] -> [a]
toPercents' = uncurry ($) . cata (algList &&&& algSum)

algSum :: (Num a) => ListF a a -> a
algSum = \case
    Nil -> fromInteger 0
    Cons x !s -> s + x

algList :: (Fractional a) => ListF a (a -> [a]) -> (a -> [a])   
algList = \case
    Nil -> const []
    Cons x s -> (\t -> 100*x / t : s t) 

答案 2 :(得分:0)

只是疯狂的实验。我认为我们可以融合。

fix = hylo (\(Cons f a) -> f a) (join Cons),我们可以在fix上替换

toPercents :: Floating a => [a] -> [a]
toPercents xs = result
  where
    (_, result) = hylo (\(Cons f a) -> f a) (join Cons) $ \(~(total, _)) -> 
      let
        alg Nil = (0, [])
        alg (Cons x (a, as)) = (x + a, 100 * x / total: as)
      in
        cata alg xs