在我使用map
定义foldr
之后,我想到了一个问题:
如果可以使用map
定义foldr
,那么相反呢?
从我的观点来看,这是不可能的,但我找不到合适的解释。 谢谢你的帮助!
答案 0 :(得分:15)
让我们从一些类型的签名开始。
foldr :: (a -> b -> b) -> b -> [a] -> b
map :: (a -> b) -> [a] -> [b]
我们可以使用map
来模拟fold
,因为fold
是一个通用运算符(here是一个关于此属性的更数学但相当友好的论文)。
我确信使用map
来模拟foldr
有一些创造性的方法。这肯定是一个有趣的练习。但我不认为这是一个直截了当的,而不是疯狂的无瑕疵"解决方案,并且为了解释它,让我们暂时忘记foldr
并专注于一个更简单的累积功能:
sum :: [Int] -> Int
sum == foldr (+) 0
,表示foldr
实现sum
。如果我们可以foldr
实施map
,我们绝对可以sum
实施map
。我们能做到吗?
我认为sum
的签名是一个惊天动地 - sum
返回Int
,map
总是返回一些内容。所以也许map
可以完成繁重的工作,但我们仍然需要另一种类型为[a] -> a
的函数才能获得最终结果。在我们的例子中,我们需要一个[Int] -> Int
类型的函数。这是非常不幸的,因为这正是我们首先想要避免的。
所以我猜答案是:您可以使用foldr
实施map
- 但它可能需要使用foldr
:)
答案 1 :(得分:5)
查看它的最简单方法是看map
保留列表的主干。如果你看一下更通用的fmap(这是一个地图,但不仅仅是列表而是一般的Functor
),它甚至是一个法则
fmap id = id
有许多方法可以“作弊”,但在对问题的最直接解释中,折叠比地图更为通用。有一个很好的技巧,在Edward Kmett的镜头库中使用了很多。考虑Const
monad,其定义如下:
newtype Const a b = Const { runConst :: a }
instance Functor (Const a) where fmap _ (Const a) = Const a
instance (Monoid a) => Monad (Const a) where
return _ = Const mempty
Const a >>= Const b = Const (a <> b)
现在你可以根据monadic map operation mapM
制定一个折叠,只要结果类型是monoidal:
fold :: Monoid m => [m] -> m
fold = runConst . mapM Const
答案 2 :(得分:2)
如果你做某种作弊辅助功能:
f [x] a = x a
f (x:xs) a = f xs (x a)
foldr g i xs = f (map g $ reverse xs) i