Haskell无意义性能-高效地将多个函数映射到同一数据

Haskell无意义性能-高效地将多个函数映射到同一数据,haskell,functional-programming,pointfree,Haskell,Functional Programming,Pointfree,我经常需要将多个函数映射到同一个数据。我已经实现了dpMap来为我做到这一点 dpMap fns = (`map` fns) . flip ($) dpMap是一个函数,这是否意味着我只读取了数据dt一次(就像一个输入相同的电路。一个无意义的系统提醒一个电路;只是管道没有数据) 作为一个例子考虑考虑列表D. ./P>的最小值和最大值。 minimax dt = (dpMap [minimum, maximum]) dt (我可以去掉dt,但必须使用-xnomonomomorphismrest

我经常需要将多个函数映射到同一个数据。我已经实现了dpMap来为我做到这一点

dpMap fns = (`map` fns) . flip ($)
dpMap是一个函数,这是否意味着我只读取了数据dt一次(就像一个输入相同的电路。一个无意义的系统提醒一个电路;只是管道没有数据)

作为一个例子考虑考虑列表D. ./P>的最小值和最大值。

minimax dt = (dpMap [minimum, maximum]) dt
(我可以去掉dt,但必须使用-xnomonomomorphismrestriction)

与以这样的点形式实现相同的函数相比,是否有性能优势

minimax2 dt = [minimum dt, maximum dt]
编辑:是否有一个使用恒定内存的dpMap的通用实现

我发现了另一个很好的博客帖子:;希望这有帮助

EDIT2:在更多的上下文之后,这里有一个解决方案,尽管我没有dpMap的精确实现,但模式非常简单,不需要单独的函数:

minimax = (,) <$> minimum <*> maximum
如果您还想计算总和和长度

func = (,,,) <$> minimum <*> maximum <*> sum <*> length

TL;DR:语言本身的性能没有保证。一点也没有。这是一个编译器的东西

根据经验,命名实体将驻留在内存中。如果只有一个使用者惰性地访问它,则可以合理地期望对其进行优化,以使编译后的程序在恒定内存中运行

内存单元的创建和消耗将被交错,每个单元在处理后都将被GC隔离


minimax2 dt=[最小dt,最大dt]
中,表达式
[最小dt,最大dt]
位于定义命名实体
dt
的范围内。最有可能(即几乎肯定)GHC将其分配为内存实体,即一次,表达式中的两个
dt
将引用同一实体(指向它,就像指针一样)

但正如Cat Plus在评论中指出的,当然,如何访问实体是另一回事。这两个子表达式将分别访问它,也就是说,它将完全保留在内存中。那不好

我们可以做得更好,只需访问一次就可以找到答案,只需折叠一次,同时收集两条数据。在这种情况下,几乎可以肯定GHC将执行优化,其中该列表不会作为一个整体保留在内存中

这就是通常所说的懒散消费列表。在这种情况下,它的创建将与那一次访问交错,产生的每个内存单元将立即被GC(垃圾收集)消耗和释放,从而实现恒定的内存操作

但这取决于我们只扫描列表一次的能力:

砰砰的一声模式阻止了砰砰声的积累,使得对论点的评估更加迫切。测试:

Prelude> minmax [1..6]
(1,6)
Prelude> minmax []
*** Exception: <interactive>:1:4-65: Non-exhaustive patterns in function minmax
Prelude>minmax[1..6]
(1,6)
序曲>最小最大值[]
***异常::1:4-65:函数minmax中的非穷举模式
当然,空列表没有定义最小值或最大值


为了启动优化,在使用GHC编译时必须使用
-O2
标志

我将对这个答案中的问题有一个相当广泛的看法,主要是因为Willence的答案中的评论

在a中,Max Rabkin介绍了一些关于折叠组合器的工作。科纳尔·埃利奥特(Conal Elliott)接受了这个想法,并发表了更多的博客文章和关于黑客的文章。我强烈推荐你阅读这篇文章,它很短,也很容易理解。ZipFold包可能非常有用,尽管它已经有一段时间没有更新了

爱德华·科米特(Edward Kmett)最近的巡演《镜头》(lens)也包括一些。我不确定我是否只想用它,但如果你用的是镜头,它可能值得一看

另一种方法是使用并行性。如果你写信

import Control.Parallel

minimax2 dt = let a = minimum dt
                  b = maximum dt
              in a `par` b `pseq` [a,b]
并与-threaded链接,那么
minimax2
就有可能在接近恒定空间的地方运行,这取决于调度程序的变幻莫测、月亮的相位等(主要是调度程序和函数IIRC的分配模式)。当然,这并不能提供可靠的保证,但在实践中可以很好地工作。将此方法推广到
dpMap
应该很简单,您可能希望使用
Control.Parallel.Strategies
或类似方法,而不是直接使用较低级别的
par


最后,大多数iteratee派生的库都非常擅长处理这类任务。一般来说,它们提供了对何时产生和使用输入流的显式控制。在
iteratee
中,我提供了与
dpMap
几乎相同的功能,将添加与
dpMap
完全相同的
sequence
,以及许多zip,所有这些功能都在恒定空间中运行(前提是消费函数本身就是恒定空间)。如果大多数其他软件包都提供了类似的操作,我也不会感到惊讶。

这是毫无意义的,因为两个版本都会在列表中重复两次。使用fold一次性获得min/max。另一个无意义的定义:
dpMap fns=(fns)。纯
。注意
dpMap=sequence
(虽然有专门的类型)。感谢您的详细回答:-)。这种方法的一个问题是,每当我需要对同一数据执行多组操作时,我都需要定义fold的一个新变体。是否有dpMap的一般实现?附言:我还在为评论中的建议绞尽脑汁。@GeneralBecos为什么是新的折叠?老家伙!(或foldr,任何合适的)。不要介意这些评论,在这里找到给定函数的最短、最不可理解的格式副本是一项运动。:)当然,YMMV和序列实际上可能有意义(它与monad和
版本w有关
{-# OPTIONS_GHC -O2 -XBangPatterns #-}

import Data.List (foldl')

minmax :: (Ord b) => [b] -> (b, b)
minmax (x:xs) = foldl' (\(!a,!b) x -> (min a x,max b x)) (x,x) xs
Prelude> minmax [1..6]
(1,6)
Prelude> minmax []
*** Exception: <interactive>:1:4-65: Non-exhaustive patterns in function minmax
import Control.Parallel

minimax2 dt = let a = minimum dt
                  b = maximum dt
              in a `par` b `pseq` [a,b]