4

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

有没有更好的方法来实现reduceByusing recursion-schemes?这些alter论点似乎很脆弱,cata在那里真的合适吗?我听说有些东西可以作为anacata.

4

2 回答 2

2

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

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

在创建此实例期间没有损坏任何固定点。我们的reduceBynow 可以用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)

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

于 2017-02-11T00:21:39.707 回答
1

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

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即将变质从分组中分离)可以说使这一点更加明显:

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
于 2017-02-10T18:50:17.427 回答