最不严格(*)

时间:2013-02-16 02:22:47

标签: haskell lazy-evaluation

是否可以在Haskell中使用最严格的语义来实现(*)(标准化的Haskell首选,但扩展是可以的。使用编译器内部是作弊)?例如,这样的定义应该导致以下结果:

0 * ⊥ = 0
⊥ * 0 = 0

且仅限:

⊥ * ⊥ = ⊥

我可以构建满足上述情况之一但不是两者的模式匹配,因为零检查会强制该值。

2 个答案:

答案 0 :(得分:21)

是的,但仅使用受限制的杂质。

laziestMult :: Num a => a -> a -> a
laziestMult a b = (a * b) `unamb` (b * a)

此处unamb是Conal Elliott的amb的“纯粹”变体。在操作上,amb并行运行两个计算,返回先到先得。指称unamb取两个值,其中一个严格地大于(在域理论意义上)而不是另一个,并返回更大的值。 编辑:这也是unamb,而不是lub,所以你需要让它们相等,除非一个是底部。因此,你有一个必须保持的语义要求,即使它不能被类型强制执行系统。这基本上是实现的:

unamb a b = unsafePerformIO $ amb a b

通过异常/资源管理/线程安全,使这一切正常工作的大量工作。

如果laziestMult是可交换的,则

*是正确的。如果*在一个参数中不严格,那么它是“最不严格的”。

有关详情,请参阅Conal's博客

答案 1 :(得分:12)

Phillip JF的答案仅适用于扁平域,但有Num个实例不平坦,例如懒惰的自然物。当你进入这个舞台时,事情变得非常微妙。

data Nat = Zero | Succ Nat
    deriving (Show)

instance Num Nat where
    x + Zero = x
    x + Succ y = Succ (x + y)

    x * Zero = Zero
    x * Succ y = x + x * y

    fromInteger 0 = Zero
    fromInteger n = Succ (fromInteger (n-1))

    -- we won't need the other definitions

对于懒惰的自然人来说,操作最不严格是特别重要的,因为这是他们使用的领域;例如我们使用它们来比较可能无限列表的长度,如果它的操作不是最不严格的,那么当找到有用的信息时,它会发散。

正如预期的那样,(+)不是可交换的:

ghci> undefined + Succ undefined
Succ *** Exception: Prelude.undefined
ghci> Succ undefined + undefined
*** Exception: Prelude.undefined

所以我们将应用标准技巧来实现它:

laxPlus :: Nat -> Nat -> Nat
laxPlus a b = (a + b) `unamb` (b + a)

这似乎有用,起初

 ghci> undefined `laxPlus` Succ undefined
 Succ *** Exception: Prelude.undefined
 ghci> Succ undefined `laxPlus` undefined
 Succ *** Exception: Prelude.undefined

但实际上并没有

 ghci> Succ (Succ undefined) `laxPlus` Succ undefined
 Succ (Succ *** Exception: Prelude.undefined
 ghci> Succ undefined `laxPlus` Succ (Succ undefined)
 Succ *** Exception: Prelude.undefined

这是因为Nat不是扁平域,而unamb仅适用于扁平域。出于这个原因,我认为unamb是一个低级原语,除了定义更高级别的概念之外不应该使用它 - 域是否是平的应该是无关紧要的。使用unamb将对更改域结构的重构敏感 - 同样的原因seq在语义上是丑陋的。我们需要将unamb概括为seq,并将deeqSeq推广到HasLub:这是在Data.Lub模块中完成的。我们首先需要为Nat编写instance HasLub Nat where lub a b = unambs [ case a of Zero -> Zero Succ _ -> Succ (pa `lub` pb), case b of Zero -> Zero Succ _ -> Succ (pa `lub` pb) ] where Succ pa = a Succ pb = b 个实例:

laxPlus'

假设这是正确的,不一定是这样(这是我到目前为止的第三次尝试),我们现在可以写laxPlus' :: Nat -> Nat -> Nat laxPlus' a b = (a + b) `lub` (b + a)

ghci> Succ undefined `laxPlus'` Succ (Succ undefined)
Succ (Succ *** Exception: Prelude.undefined
ghci> Succ (Succ undefined) `laxPlus'` Succ undefined
Succ (Succ *** Exception: Prelude.undefined

它确实有效:

leastStrict :: (HasLub a) => (a -> a -> a) -> a -> a -> a
leastStrict f x y = f x y `lub` f y x

因此,我们倾向于概括交换二元运算符的最不严格模式是:

ghci> Succ (Succ undefined) `laxPlus'` Succ (Succ undefined)
Succ (Succ *** Exception: BothBottom

至少,它保证是可交换的。但是,唉,还有其他问题:

leastStrict

我们期望两个至少为2的数字之和至少为4,但是这里我们得到的数字至少为2.我无法想出一种方法来修改laxPlus'' :: Nat -> Nat -> Nat laxPlus'' a b = lubs [ case a of Zero -> b Succ a' -> Succ (a' `laxPlus''` b), case b of Zero -> a Succ b' -> Succ (a `laxPlus''` b') ] 来给出我们想要的结果,至少在没有引入新的类约束的情况下。要解决这个问题,我们需要深入研究递归定义,并在每个步骤同时对两个参数进行模式匹配:

ghci> Succ (Succ undefined) `laxPlus''` Succ (Succ undefined)
Succ (Succ (Succ (Succ *** Exception: BothBottom

终于我们得到的信息尽可能多,我相信:

laxMult :: Nat -> Nat -> Nat
laxMult a b = lubs [
    case a of
        Zero -> Zero
        Succ a' -> b `laxPlus''` (a' `laxMult` b),
    case b of
        Zero -> Zero
        Succ b' -> a `laxPlus''` (a `laxMult` b')
    ]

ghci> Succ (Succ undefined) `laxMult` Succ (Succ (Succ undefined))
Succ (Succ (Succ (Succ (Succ (Succ *** Exception: BothBottom

如果我们将相同的模式应用于时间,我们得到的东西似乎也有效:

asLeast :: Nat -> Nat
atLeast Zero = undefined
atLeast (Succ n) = Succ (atLeast n)

ghci> atLeast 7 `laxMult` atLeast 7
Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ ^CInterrupted.

毋庸置疑,这里有一些重复的代码,并且开发模式以简洁地表达这些功能(因此具有尽可能少的错误机会)将是一个有趣的练习。但是,我们还有另一个问题......

{{1}}

它非常慢。显然这是因为它的参数大小(至少)是指数的,在每次递归时都会下降到两个分支。它需要更精细才能让它在合理的时间内运行。

最不严格的编程是相对未开发的领域,在实施和实际应用中都需要进行更多的研究。我很兴奋,并认为它有前途的领域。