Haskell:懒惰评估可以帮助提前停止投票吗?

时间:2011-06-26 23:19:34

标签: haskell lazy-evaluation

假设我有10个重度不同的函数(并行与否)决定相同的问题。 是否有一种很好的方法来实现一种投票方案,当达到多数并且不再需要计算时,它会自动实现懒惰?

obs:这是关于懒惰ev的范围/限制的更多问题。 当然,简单的“if”可以检测到多数。

由于

[编辑1]

  

......简单的“if”可以检测到多数。

抱歉,我的意思是“单身如果” - > “单身等待所有过程完成”。

  

......平行与否......

在这种情况下,我只是不知道并行性问题。 (我模棱两可的英语问题)

5 个答案:

答案 0 :(得分:4)

简短回答。是的,可以实现这样的系统,但不,内置的懒惰对你没有帮助。

答案很长。我相信你需要一点不同的懒惰。 Haskell的懒惰评估是一种normal evaluation order,其工作原理如下:

  1. 调用该函数时,求值程序首先尝试计算它,而不计算其参数。
  2. 如果控制流程到了需要计算某个参数的点,则会对其进行评估。然后继续评估该功能。
  3. 因此,根据需要评估参数,“按需”。而且,它们被逐一评估。对于语言本身来说这是一个好主意,即使没有这些惰性函数,具有应用评估顺序的命令式语言也无法工作 - 像orand之类的运算符在大多数编程语言中都是懒惰的。但在你的情况下,你真的需要它吗?不。您需要并行计算所有参数,并在计算某些args时完成函数本身的评估。

    如何实施。您需要完全重新实施评估系统,我相信没有副作用和懒惰评估的纯函数式编程只会阻碍您。这是一种方法。创建函数,比如paplly :: [ArgumentType] -> TriggerFunction -> ResultType,其中papply代表“并行应用”,ArgumentType是一种计算的实际参数(在你的情况下,它可能是函数关闭+问题到求解),TriggerFunction是一个函数,在计算其中一个args时调用,在你的情况下ResultType是布尔值。此功能必须如下工作:

    1. 并行运行所有参数的评估。
    2. 当计算其中一个参数时,它必须使用评估结果调用TriggerFunction。
    3. 触发器功能必须具有“记忆”才能记住以前的所有结果。如果,在被调用时,它发现,有足够的参数来完成主函数的评估,它会这样做,中断计算其余的args。
    4. 这只是其中一种方法,而不是功能最强大(它使用可变的“内存”)。您还可以与其他参数并行运行触发器功能,并使用某种同步在所有参数之间传递控制。或者您可以使用Erlang或Scala中的某种消息。不幸的是,我没有足够的经验与Haskell编写实际代码,但@Dietrich Epp的帖子似乎代表了类似的想法,所以你可以用它作为基础。

答案 1 :(得分:3)

你想要一个像这样的函数:

majority :: [Bool] -> Bool

你希望它能并行工作。没有汗!不幸的是,我不知道如何在不绕过类型系统的情况下做到这一点。以下是一个示例实现:

import Control.Concurrent
import Control.Concurrent.MVar
import System.IO.Unsafe

majority :: [Bool] -> Bool
majority votes = unsafePerformIO $
  do v <- newEmptyMVar
     nfalse <- newMVar 0
     ntrue <- newMVar 0
     let n = length votes
         m = (n `div` 2) + 1
         count x =
           let (var, min) = if x then (ntrue, m) else (nfalse, n-m+1)
           in do i <- modifyMVar var $ \i -> return (i+1, i+1)
                 if i == min then putMVar v x else return ()
     threads <- mapM (forkIO . count) votes
     r <- takeMVar v
     mapM_ killThread threads
     return r

注意:我不肯定这是正确的。

答案 2 :(得分:2)

你可以在没有平行评估的情况下使用惰性自然来做到这一点。在这种情况下,我选择在hackage上使用peano-inf包:http://hackage.haskell.org/package/peano-inf

import Number.Peano.Inf
import Debug.Trace
import Data.List

myList = [trace "1" True, trace "2" True, trace "3" False, trace "4" True, trace "5" True]

btoNat True = 1 :: Nat
btoNat False = 0 :: Nat

ans = sum $ map btoNat myList
{-
*Main> ans > 2
1
2
3
4
True
-}

请注意,跟踪中不会打印5,因为在此之前评估会被缩短。

要实现并行性,需要手动生成并杀死线程等,这很好,但肯定不太愉快。

请注意,上面的代码使用标准总和。这种不常见的用例就是为什么尽管很多人觉得它不值得,但总和并不尽可能严格。

答案 3 :(得分:1)

我尝试将sclv的解决方案与luqui关于unamb的评论结合起来,并希望分享我的结果。我将从测试用例开始:

list1 = [True, True, undefined, True, undefined]
list2 = [undefined, False, False]
list3 = concat $ replicate 500 list1
list4 = concat $ replicate 500 list2


main = mapM (print . vote) [list1, list2, list3, list4]

vote :: [Bool] -> Bool

这应该打印

True
False
True
False

我首先从list1示例开始。传递它的投票功能可能如下所示:

voteByTrue list = sum (map bToNat list) >= threshold
  where
    threshold = (genericLength list + 1) `quot` 2

这与sclv的答案相同。现在我们需要使sum更加懒惰,以便在遇到undefined加数时计算不会中止。我对此的第一个看法是:

Zero |+ y = y
Succ x |+ y = Succ (x + y)

instance Num Nat where
    x + y = (x |+ y) `lub` (y |+ x)

这里,|+是第一个参数中的加法运算符strict,而+在它的两个参数中都是非严格的。它适用于玩具示例,如list1,但由于线程数呈指数性膨胀,此解决方案的性能会迅速恶化(请参阅每个+如何生成2个线程,每个线程都是如此再次调用+,通常使用相同的参数)。有了这样的性能,vote list3不会足够快地终止。为了解决这个问题,我试图违反unamb的合同并实施了以下功能:

-- | The same as unamb, but does not have the 
--   'agree unless bottom' precondition.
brokenUnamb = unamb

infoMinMax a b = (x, y) 
  where
    ~(x, y) = (a `seq` (b, a)) `brokenUnamb` (b `seq` (a, b))

此函数按其持有的信息量对其两个参数进行排序。它始终返回评估值较少的x和更多评估值y。这违反了unamb论证的条件,从而打破了纯洁。但是,它允许我们更有效地实施+

instance Num Nat where
    x + y = x' |+ y' where (y', x') = infoMinMax x y

这允许我们通过大型测试(list3)!现在,进行虚假测试......结果证明infoMinMax函数在这里也很有用!

vote list = voteByTrue list `maxInfo` voteByFalse list
  where
    voteByFalse = not . voteByTrue . map not
    maxInfo x y = snd (infoMinMax x y)

现在,这允许程序通过所有四个测试,尽管大的测试需要几秒钟才能完成。如果我将undefined替换为odd (sum [1..]),CPU使用率也会猛增至200%,因此确实会发生一些并行性。

但是,纯度仍然存在问题。有人可以建议一个简单unamb足够的解决方案吗?

答案 4 :(得分:-1)

如果我们认为这些函数产生布尔值,问题就变成是否有可能编写一个接受10个布尔值的函数,如果其中6个为真,则返回true,总是需要少于10个输入的值。

一种简单的方法,但是不符合规定要求的方法是测试每个输入依次计算真实数量和真假数量如果trues&gt; = 6 stop return true否则如果falses&gt; = 6 stop return false如果我们在没有触发这些条件的情况下到达最后一个输入,则返回false。因为这会在某些情况下测试所有输入,所以我认为这个问题的答案是否定的,懒惰的评估在这个例子中没有帮助。