Haskell 中的 RamdaJS reduceBy() 使用递归方案
RamdaJS reduceBy() in Haskell using recursion-schemes
我有以下使用 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
来实现 reduceBy
? alter
的论点似乎很脆弱,cata
真的合适吗?我听说有些东西可以用 ana
和 cata
.
来实现
我看不出你的方法有什么问题。 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
...您可能会或可能不会找到改进。
至于使用变质,这感觉很自然,因为您正在破坏原始结构以生成元素的 group-wise 摘要。 (同样值得注意的是,如果 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
根据目前所有的建议我自己的尝试:
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)
请注意,该方法是完全模块化的:您可以轻松地将函数拆分为独立的组合器,还可以使用变形和其他展开来灵活地构建映射,而不仅仅是使用列表。
我有以下使用 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
来实现 reduceBy
? alter
的论点似乎很脆弱,cata
真的合适吗?我听说有些东西可以用 ana
和 cata
.
我看不出你的方法有什么问题。 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
...您可能会或可能不会找到改进。
至于使用变质,这感觉很自然,因为您正在破坏原始结构以生成元素的 group-wise 摘要。 (同样值得注意的是,如果 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
根据目前所有的建议我自己的尝试:
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)
请注意,该方法是完全模块化的:您可以轻松地将函数拆分为独立的组合器,还可以使用变形和其他展开来灵活地构建映射,而不仅仅是使用列表。