Haskell 如何在哈斯凯尔建造咖啡馆而不是咖啡馆?

Haskell 如何在哈斯凯尔建造咖啡馆而不是咖啡馆?,haskell,ghc,compiler-optimization,Haskell,Ghc,Compiler Optimization,如何将一个常量应用程序表单转换成,而不是一个常量应用程序表单,以防止它在程序的生命周期内被保留 我尝试过这种方法: -- | Dummy parameter to avoid creating a CAF twoTrues :: () -> [[[Bool]]] twoTrues _ = map (++ (True : repeat False)) . trueBlock <$> [1..] ——|避免创建CAF的伪参数 twoTrues::()->[[[Bool]]] tw

如何将一个常量应用程序表单转换成,而不是一个常量应用程序表单,以防止它在程序的生命周期内被保留

我尝试过这种方法:

-- | Dummy parameter to avoid creating a CAF
twoTrues :: () -> [[[Bool]]]
twoTrues _ = map (++ (True : repeat False)) . trueBlock <$> [1..]
——|避免创建CAF的伪参数
twoTrues::()->[[[Bool]]]
twoTrues_=map(++(True:repeat False))。trueBlock[1..]
但它似乎不起作用-配置文件显示它仍被保留,并仍将其标记为CAF


我在谷歌上找到了一个与此相关的结果,尼尔·米切尔(Neil Mitchell)正是问了这个问题——但不幸的是,这个答案指的是一个死链接。

一个完整的例子

这里有一个小例子说明了这种情况:

module A where

big :: () -> [Int]
big _ = [1..10^7]
看起来像一个函数,对吗?但是GHC做什么呢?它将枚举浮动到顶层

A.big1 :: [Int]
[ Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=0, Value=False,
         ConLike=False, Cheap=False, Expandable=False,
         Guidance=IF_ARGS [] 7 0}]
A.big1 =
  case A.$wf1 10 A.big2 of ww_sDD { __DEFAULT ->
  eftInt 1 ww_sDD
  }

A.big :: () -> [Int]
[Arity=1,    
 Unf=Unf{Src=InlineStable, TopLvl=True, Arity=1, Value=True,
         ConLike=True, Cheap=True, Expandable=True,
         Guidance=ALWAYS_IF(unsat_ok=True,boring_ok=True)
         Tmpl= \ _ -> A.big1}]
A.big = \ _ -> A.big1
不内联,还有更多函数

将所有内容都设置为一个函数,包括
enumFromTo
,将参数传递给工人:

big :: () -> [Int]
big u = myEnumFromTo u 1 (10^7)
{-# NOINLINE big #-}

myEnumFromTo :: () -> Int -> Int -> [Int]
myEnumFromTo _ n m = enumFromTo n m
{-# NOINLINE myEnumFromTo #-}
现在我们终于没有咖啡了!即使使用
-O2

A.myEnumFromTo [InlPrag=NOINLINE]
  :: () -> Int -> Int -> [Int]
A.myEnumFromTo =
  \ _ (n_afx :: Int) (m_afy :: Int) ->
    $fEnumInt_$cenumFromTo n_afx m_afy

A.big [InlPrag=NOINLINE] :: () -> [Int]
A.big = \ (u_abx :: ()) -> A.myEnumFromTo u_abx A.$s^2 lvl3_rEe


什么不起作用

关闭-完全懒惰


完全惰性转换将定义向外浮动。默认情况下,它在
-O1
或更高版本时打开。让我们试着用
-fno full lazness
关闭它。但是,它不起作用。

您需要对优化器隐藏rhs是CAF的事实。 像这样的东西应该可以

twoTrues :: () -> [[[Bool]]]
twoTrues u = map (++ (True : repeat (false u))) . trueBlock <$> [1..]

{-# NOINLINE false #-}
false :: () -> Bool
false _ = False
twoTrues::()->[[[Bool]]
twoTrues u=map(++(真:重复(假u)))。trueBlock[1..]
{-#NOINLINE false}
false::()->Bool
假

泛化。如果你有一个常数值,你能把它泛化为某个变量的函数吗?我在问题中的函数命名为twoTrues,立即表明这个常数是序列中的第三个,
zeroTrues
oneTrue
twoTrues
threeTrues
等等,确实如此。因此,将
twoTrues
归纳为一个函数
nTrues
,该函数接受一个参数n并删除
twoTrues
,将从程序中消除一个CAF

碰巧的是,在这种情况下,我只考虑了我的程序的
zeroTrues
oneTrue
twoTrues
,因为这就是我所需要的,但是我的程序自然可以扩展到处理
n
>2的
nTrues
——因此推广到
nTrues
,意味着“一路推广”到
zeroTrues
oneTrue
等用户是有意义的。情况并非总是如此

注:可能还有其他CAF需要处理,要么在代码中,要么由GHC的“优化”(在这些病理情况下,这些不是真正的优化)产生

然而,这个答案可能需要程序员做比严格必要的更多的工作。正如唐的回答所显示的那样,实际上没有必要概括

另一方面,在某些情况下,泛化常量可以使您更清楚地了解实际操作,并有助于重用。它甚至可以揭示以更好的系统方式和/或更高效地计算一系列值的方法


关于这个特殊情况的一点说明(可以忽略):在这个特殊情况下,我不想让
nTrues
本身成为一个无限列表(这将再次成为一个CAF,重新引入原始问题!),而不是一个函数。一个原因是,虽然
twoTrues
可以以无限列表的形式使用,但我看不出
nTrues
以无限列表的形式使用会有什么用处(无论如何,在我的应用程序中)。

最简单的解决方案可能是告诉编译器内联它。(注意:此答案未经测试。如果不起作用,请在下面进行评论。)

即使(假设)编译器出于某种原因拒绝内联它,您也可以使用
cpp
,方法是#定义它:

#define twoTrues (map (++ (True : repeat False)) . trueBlock <$> [1..])
#定义两个trues(map(++(True:repeat False)).trueBlock[1..]
(当然,尽管采用这种方法,它不会出现在模块的界面中,因此您只能在该模块中使用它)

-cpp
选项告诉GHC使用cpp预处理输入文件


如果在n>1处引用
twoTrues
,内联意味着将代码复制n次。然而,虽然这在理论上是不可取的,但在实践中可能不会是一个重大问题。

引入虚拟参数后,还必须使其看起来结果实际上取决于参数。否则,GHC的聪明可能会让它再次变成咖啡馆

我建议如下:

twoTrues u = map (++ (True : repeat False)) . trueBlock <$> [(u `seq` 1)..]
twoTrues u=map(++(真:重复假))。trueBlock[(u`seq`1)…]

这似乎是一个长期存在的问题。在我看来,这是一个关键的问题。

每当你把
()
作为一个参数,你要说的实际上是

虽然我在这里声明了一个参数,但我对它是什么不感兴趣,也不会对它的值做任何事情

你对它不感兴趣,因为
()
没有任何有趣的东西;你不会用它做任何事情,因为你不能用
()
做任何事情

问题是编译器有权优化它,因为只有一个可能的值可以传递,所以它的使用总是可以预测的,所以为什么不假设它呢?但它把它移回了CAF,这使得这个想法行不通

幸运的是,还有另一种方法可以做到这一点。查看以下对twoTrues的修改:

twoTrues :: a -> [[[Bool]]]
twoTrues _ = map (++ (True : repeat False)) . trueBlock <$> [1..]
由于
a
是未使用的类型参数,因此调用者可以传递任何内容。因为你不知道它会是什么,所以你不知道你能用它做什么。这实际上迫使您忽略它的值。所以基本上是德克拉
twoTrues :: a -> [[[Bool]]]
twoTrues _ = map (++ (True : repeat False)) . trueBlock <$> [1..]
map concat $ twoTrues()
usual :: a -> String
usual _ = "Hello World!"

unusual :: a -> String
unusual a = seq a "Hello World!"