对于有序列表,我有一个类似SQL的join
的简单示例:如果outer
参数是True
那么它的联合;否则它的交集:
import System.Environment
main = do
[arg] <- getArgs
let outer = arg == "outer"
print $ length $ joinLists outer [1..1000] [1,3..1000]
joinLists :: (Ord a, Num a) => Bool -> [a] -> [a] -> [a]
joinLists outer xs ys = go xs ys
where
go [] _ = []
go _ [] = []
go xs@(x:xs') ys@(y:ys') = case compare x y of
LT -> append x $ go xs' ys
GT -> append y $ go xs ys'
EQ -> x : go xs' ys'
append k = if {-# SCC "isOuter" #-} outer then (k :) else id
当我对其进行分析时,我发现每次调用isOuter
时都会评估append
条件:
stack ghc -- -O2 -prof example.hs && ./example outer +RTS -p && cat example.prof
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 44 0 0.0 34.6 0.0 100.0
isOuter Main 88 499 0.0 0.0 0.0 0.0
但我希望只评估一次条件,因此append
循环中的go
会被(k :)
或id
替换。我能以某种方式强迫它吗?它与记忆有关吗?
append
定义:
append k = if trace "outer" outer then (k :) else id
outer
只打印一次。
EDIT2:如果我用无点定义替换append
,那么if
条件只评估一次:
append = if outer then (:) else flip const
答案 0 :(得分:5)
我会尝试向内推lambdas:
append = if {-# SCC "isOuter" #-} outer then \k -> (k :) else \k -> id
原始代码基本上是\k -> if outer ...
,它首先接受参数,然后测试后卫。上面的代码改为在获取参数之前测试守护。
替代:
append | outer = \k -> (k :)
| otherwise = \k -> id
可以进一步将这些lambda简化为更易读的形式。