在编写探索生日悖论的程序时,我有以下工作Haskell代码
sort :: Ord a => [a] -> [a]
-- body
hasDuplicates :: Eq a => [a] -> Bool
-- body
boolToInt :: Bool -> Int
-- body
main = do
-- stuff
repeats <- liftM sum . replicateM numTrials . liftM boolToInt .
liftM hasDuplicates . liftM sort . replicateM checkNum $
randomRIO (1::Int, 365)
-- stuff
在最后一行中,有很多liftM
一个接一个地组成。这种成分可以优化吗?
我想到map
ping liftM
到[boolToInt, hasDuplicates, sort]
然后compose
ing,但该列表是异构的,因此无效。 iterate
因类似原因无效。
答案 0 :(得分:5)
是的,你可以组成其中一些。最简单的方法是识别liftM
实际上只是fmap
个Monad
实例的实现。(1)因此通常的仿函数法适用:
fmap id = id
fmap f . fmap g = fmap (f . g)
因此
liftM boolToInt .
liftM hasDuplicates .
liftM sort
可以写
fmap (boolToInt . hasDuplicates . sort)
我们怎样才能做得更好?让我们在上下文中看一下。
liftM sum . replicateM numTrials .
fmap (boolToInt . hasDuplicates . sort) .
replicateM checkNum
这里似乎没有很多重复,但是如果每个都有很多试验或许多检查,可能会有很大的低效率,因为你在对它们求和之前在内存中建立这些列表。你可以手动解决这个问题,但它不会很愉快(2)。解决它的好方法是使用流媒体包。另一件需要考虑的事情是,由于我们正在使用的唯一影响是随机性,如果出现重复,我们可以停止试验。我稍后会尝试演示。
liftM
的完整目的是能够为类型Monad
编写m
个实例,然后编写instance Functor m where fmap = liftM
}。您通常不应将liftM
用于其他任何事情。
例如,
fmap sum . replicateM n
可以写
sumReplications = go 0 where
go !acc 0 _ = pure acc
go acc n m = m >>= \res -> go (acc + res) (n - 1) m
答案 1 :(得分:4)
你可以liftM
整个作文,而不是liftM
每个函数。
liftM (sum . replicate numTrials . boolToInt . hasDuplicates . sort)