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!"