Haskell 最低严格(*)
是否可以在Haskell中用最不严格的语义实现Haskell 最低严格(*),haskell,lazy-evaluation,Haskell,Lazy Evaluation,是否可以在Haskell中用最不严格的语义实现(*)(首选标准化的Haskell,但扩展是可以的。使用编译器内部构件是欺骗)?例如,这样的定义应导致以下结果为真: 0 * ⊥ = 0 ⊥ * 0 = 0 只有: ⊥ * ⊥ = ⊥ 我可以构建满足上述其中一种情况的模式匹配,但不能同时满足这两种情况,因为零检查强制该值。是,但仅使用受约束的杂质 laziestMult :: Num a => a -> a -> a laziestMult a b = (a * b) `una
(*)
(首选标准化的Haskell,但扩展是可以的。使用编译器内部构件是欺骗)?例如,这样的定义应导致以下结果为真:
0 * ⊥ = 0
⊥ * 0 = 0
只有:
⊥ * ⊥ = ⊥
我可以构建满足上述其中一种情况的模式匹配,但不能同时满足这两种情况,因为零检查强制该值。是,但仅使用受约束的杂质
laziestMult :: Num a => a -> a -> a
laziestMult a b = (a * b) `unamb` (b * a)
这是科纳尔·埃利奥特(Conal Elliott)对amb
的“纯”变体。在操作上,amb
并行运行两个计算,返回哪个先到。从外延上讲,unab取两个值,其中一个值严格大于另一个值(在领域理论意义上),并返回较大的值Edit:这也是unab,而不是lub,因此除非一个是bottom,否则需要使它们相等。因此,您必须满足一个语义要求,即使它不能由类型系统强制执行。这主要体现为:
unamb a b = unsafePerformIO $ amb a b
在异常/资源管理/线程安全的情况下,要使这一切正常工作,需要做很多工作
如果*
是可交换的,laziestMult
是正确的。如果*
在一个参数中不严格,则为“最不严格”
有关更多信息,请参见blogPhillip JF的回答仅适用于平面域,但也有一些非平面的实例,例如lazy naturals。当你进入这个竞技场,事情变得相当微妙
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
对于懒惰的自然人来说,最不严格的操作尤其重要,因为这是他们使用的领域;e、 我们使用它们来比较可能无限的列表的长度,如果它的操作不是最严格的,那么当有有用的信息被发现时,这将产生分歧
正如所料,(+)
是不可交换的:
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
不是平面域,而unab
仅适用于平面域。正因为这个原因,我认为unab
将对改变域结构的重构非常敏感——同样的原因seq
在语义上是丑陋的。我们需要推广unab
,就像seq
推广到deeqSeq
一样:这在模块中完成。我们首先需要为Nat
编写一个HasLub
实例:
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
我们期望至少为2的两个数之和至少为4,但这里我们得到一个至少为2的数。我想不出一种方法来修改leastStrit
以获得我们想要的结果,至少在不引入新的类约束的情况下是这样。要解决此问题,我们需要深入研究递归定义,并在每一步同时对两个参数进行模式匹配:
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.
它慢得可怕。很明显,这是因为它(至少)参数的大小是指数级的,在每个递归上下降为两个分支。它需要更微妙的技巧才能在合理的时间内运行
最不严格的编程是一个相对未开发的领域,在实现和实际应用中都有必要进行更多的研究。我对它感到兴奋,并认为它是有前途的领土。你的意思是在实践中(通过勺子等不安全的东西)还是理论上,这是不安全的还是在实践中只使用H2010?你可能会在有限的情况下这样做,比如底部值是从调用<代码>错误< /代码>引起的。然而,能够识别任何底部值就等于解决了停止问题。如果第一个参数是非终止计算,会发生什么情况?@sabauma如果绝对必须检查与0的相等性,那么这可能是真的,这将解释为什么这可能不可能发生。@singpolyma即使你没有与0进行比较,第一种模式必须确保第二个参数在返回零之前确实处于底部。这更多是因为您在底部进行模式匹配。@sabauma显然无法在底部进行模式匹配:)您对
unab
的定义几乎正确,但您忘记了一个重要的前提条件:如果两个参数都不匹配⊥, 那么它们必须完全相等。这个包也有这个功能,所以你可以只写parComb(*)
。我认为Nat
应该只指归纳自然:或者是平面域Nat=Z | S!Nat
或真正的离散集Nat
不应该包含像x=sx
这样的值,因为没有自然数具有这种形式。相反,我们应该把“懒惰的自然”称为其他的东西,也许是“超自然的数字”?@PhilipJF,你是否也反对把[]
称为“列表”。但是没有Nat那么多,因为“list”是这样一个“cs”字,而natura