Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/144.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 优化咖喱所需的无点风格_Haskell_Ghc_Currying_Pointfree_Partial Application - Fatal编程技术网

Haskell 优化咖喱所需的无点风格

Haskell 优化咖喱所需的无点风格,haskell,ghc,currying,pointfree,partial-application,Haskell,Ghc,Currying,Pointfree,Partial Application,假设我们有这样一个(人为的)函数: import Data.List (sort) contrived :: Ord a => [a] -> [a] -> [a] contrived a b = (sort a) ++ b 我们将其部分应用于其他地方,例如: map (contrived [3,2,1]) [[4],[5],[6]] 从表面上看,这正如人们所期望的: [[1,2,3,4],[1,2,3,5],[1,2,3,6]] 但是,如果我们抛出一些跟踪s: impo

假设我们有这样一个(人为的)函数:

import Data.List (sort)

contrived :: Ord a => [a] -> [a] -> [a]
contrived a b = (sort a) ++ b
我们将其部分应用于其他地方,例如:

map (contrived [3,2,1]) [[4],[5],[6]]
从表面上看,这正如人们所期望的:

[[1,2,3,4],[1,2,3,5],[1,2,3,6]]
但是,如果我们抛出一些
跟踪
s:

import Debug.Trace (trace)

contrived :: Ord a => [a] -> [a] -> [a]
contrived a b = (trace "sorted" $ sort a) ++ b
map (contrived $ trace "a value" [3,2,1]) [[4],[5],[6]]
我们看到,传递到
设计的
中的第一个列表仅计算一次,但它针对
[4,5,6]
中的每个项目进行排序:

[sorted
a value
[1,2,3,4],sorted
[1,2,3,5],sorted
[1,2,3,6]]
现在,
artived
可以非常简单地转换为无点样式:

contrived :: Ord a => [a] -> [a] -> [a]
contrived a = (++) (sort a)
realFunction a b = conditionOne && conditionTwo
  where conditionOne = map (something a) b
        conditionTwo = somethingElse a b
部分应用时:

map (contrived [3,2,1]) [4,5,6]
仍然如我们预期的那样工作:

[[1,2,3,4],[1,2,3,5],[1,2,3,6]]
但如果我们再次添加
trace
s:

contrived :: Ord a => [a] -> [a] -> [a]
contrived a = (++) (trace "sorted" $ sort a)
map (contrived $ trace "a value" [3,2,1]) [[4],[5],[6]]
我们看到,现在传递到
artived
的第一个列表被计算,并且只被排序一次:

[sorted
a value
[1,2,3,4],[1,2,3,5],[1,2,3,6]]
为什么会这样?既然到无点风格的转换是如此简单,为什么GHC不能推断出它只需要在
artived
的第一个版本中对
a
进行一次排序


注意:我知道,对于这个相当简单的示例,可能最好使用无点样式。这是一个我简化了很多的人为的例子。在我看来,当以无点风格表达时,我所关注的真正函数就不那么清晰了:

contrived :: Ord a => [a] -> [a] -> [a]
contrived a = (++) (sort a)
realFunction a b = conditionOne && conditionTwo
  where conditionOne = map (something a) b
        conditionTwo = somethingElse a b
在无点风格中,这需要围绕
(&&&&
)编写一个难看的包装器(
两者
):


顺便说一句,我也不知道为什么
这两个
包装器都能工作;
realFunction
的无点风格类似于
artived
的无点风格版本,因为部分应用程序只评估一次(即,如果
something
sorted
a
则只评估一次)。似乎由于
两者都不是无点的,Haskell应该有与非无点的
人造的

相同的问题如果我理解正确,您正在寻找:

contrived :: Ord a => [a] -> [a] -> [a]
contrived a = let a' = sort a in \b -> a' ++ b
                    -- or ... in (a' ++)
如果希望只计算一次排序,则必须在
\b
之前进行排序

你是对的,编译器可以优化这一点。这就是所谓的“完全惰性”优化

如果我没记错的话,GHC并不总是这样做,因为在一般情况下,它并不总是一个实际的优化。考虑设计实例

foo :: Int -> Int -> Int
foo x y = let a = [1..x] in length a + y
当传递这两个参数时,上述代码在常量空间中工作:列表元素在生成时立即被垃圾收集

当部分应用
x
时,
foo x
的闭包只需要O(1)个内存,因为列表尚未生成。代码式

let f = foo 1000 in f 10 + f 20  -- (*)
仍然在恒定的空间中运行

相反,如果我们写

foo :: Int -> Int -> Int
foo x = let a = [1..x] in (length a +)
然后,
(*)
将不再在常量空间中运行。第一次调用
F10
将分配一个1000长的列表,并将其保存在内存中,以便第二次调用
F20


请注意,您的部分应用程序

... = (++) (sort a)
本质上意味着

... = let a' = sort a in \b -> a' ++ b

因为参数传递涉及绑定,如
let
。因此,您的
排序a
的结果将保留下来,以备将来调用。

+1(+如果可以的话,请提供更多!)感谢您的明确解释。在一般情况下,我明白为什么GHC在默认情况下不进行这种优化。我忘了提到我尝试使用
($!)
强制对排序进行严格评估。我一定是做错了,因为瞧,
($!)
只是你建议的let构造的另一种拼写@BaileyParker实际上,
let
不会立即强制求值,而
$
会强制该参数
f x+gx中的x=e
仅当
f,g
需要时才会计算
e
,但会将其值保存在内存中以备第二次调用。