【问题标题】:RamdaJS reduceBy() in Haskell using recursion-schemesHaskell 中的 RamdaJS reduceBy() 使用递归方案
【发布时间】:2023-03-07 21:41:01
【问题描述】:

我有以下代码使用recursion-schemes 库:

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Functor.Foldable
import Data.Maybe

import qualified Data.Map as M

reduceBy valueAlgebra keyFn = cata $ fooAlgebra valueAlgebra keyFn

fooAlgebra
  :: Ord k =>
     (ListF t a -> a) -> (t -> k) -> ListF t (M.Map k a) -> M.Map k a   
fooAlgebra valueAlgebra keyFn = \case
    Nil -> M.empty
    Cons elt acc -> M.alter 
         (Just . (valueAlgebra . Cons elt) . fromMaybe (valueAlgebra Nil)) 
         (keyFn elt)
         acc

用作let countBy = reduceBy (\case Nil -> 0 ; Cons a b -> succ b) id in countBy [42,5,5,8,8,8]。代码模仿http://ramdajs.com/docs/#reduceBy

有没有更好的方法来使用recursion-schemes 来实现reduceByalter 参数似乎很脆弱,cata 真的适合吗?我听说有些东西可以用anacata 来实现。

【问题讨论】:

  • 似乎您可以使用 catamorphism 来获取列表图,然后只需 fmap 为每个组使用 catamorphism(实际上是折叠)。
  • 更模块化的方式来应用valueAlgebra?似乎是个好主意。现在我将代数传递给alter,它接受其教堂编码版本。而且解码很痛苦。

标签: haskell recursion-schemes


【解决方案1】:

我自己的尝试基于迄今为止的所有建议:

type ListAlgebra a b = ListF a b -> b

reduceBy :: Ord k => ListAlgebra t b -> (t -> k) -> [t] -> M.Map k b
reduceBy valueAlgebra keyFn x = cata valueAlgebra <$> cata groupAlgebra x where
    groupAlgebra = \case
        Nil -> M.empty
        Cons elt acc -> M.alter (Just . maybe [elt] (elt:)) (keyFn elt) acc

另一个攻击方向是注意到keyFn可以从groupAlgebra中分解出来,所以它变成groupAlgebra' :: ListAlgebra (k, v) (M.Map k [v])。在这种形式中,它完全是一个embed,虽然有点异国情调:

newtype XMap k v = XMap { unXMap :: M.Map k [v] }
type instance Base (XMap k v) = ListF (k, v)
instance Ord k => Corecursive (XMap k v) where
    embed = \case
        Nil -> XMap M.empty
        Cons (key,elt) acc -> XMap $ M.alter (Just . maybe [elt] (elt:)) key $ unXMap acc

在创建此实例期间没有损坏任何固定点。我们的reduceBy 现在可以用refix“cast”(一种从(Co)recursive 实例获取代数和余代数的亚型)来构造:

reduceBy :: Ord k => ListAlgebra t b -> (t -> k) -> [t] -> M.Map k b
reduceBy valueAlgebra keyFn =
    fmap (cata valueAlgebra) . unXMap . refix . map (keyFn &&& id)

请注意,该方法是完全模块化的:您可以轻松地将函数拆分为独立的组合器,还可以使用变形和其他展开灵活地构建映射,而不仅仅是使用列表。

【讨论】:

    【解决方案2】:

    我认为您的方法没有任何问题。 alter 的参数看起来不太令人愉快,但这主要是因为 alter 使用起来有点笨拙。由于您不需要从地图中删除元素,因此可以使用insertWith 而不是alter 重写fooAlgebra...

    fooAlgebra
      :: Ord k =>
         (ListF t a -> a) -> (t -> k) -> ListF t (M.Map k a) -> M.Map k a
    fooAlgebra valueAlgebra keyFn = \case
        Nil -> M.empty
        Cons elt acc -> M.insertWith
             (\_ grpAcc -> valueAlgebra (Cons elt grpAcc))
             (keyFn elt)
             (valueAlgebra (Cons elt (valueAlgebra Nil)))
             acc
    

    ...您可能会或可能不会找到改进。

    至于使用 catamorphism,感觉很自然,因为您正在破坏原始结构以生成元素的分组摘要。 (同样值得注意的是,如果keyFn 是一个常量函数,那么reduceBy 本质上就变成了所有元素与valueAlgebra 的普通旧折叠。)danidiaz 建议的重构(即分离valueAlgebra catamorphism从分组一)可以说使这一点更加明显:

    reduceBy valueAlgebra keyFn =
        fmap (cata valueAlgebra) . cata (groupAlgebra keyFn)
    
    groupAlgebra
      :: Ord k => (t -> k) -> ListF t (M.Map k [t]) -> M.Map k [t]
    groupAlgebra keyFn = \case
        Nil -> M.empty
        Cons elt acc -> M.alter
             (Just . (elt :) . fromMaybe [])
             (keyFn elt)
             acc
    

    【讨论】:

      猜你喜欢
      • 2019-05-20
      • 2021-12-06
      • 2018-02-18
      • 2022-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-26
      相关资源
      最近更新 更多