首页 > 解决方案 > 如何使用 Data.SBV 来帮助导出正确的堆栈机器实现?

问题描述

Graham Hutton,在 Haskell 编程的第 2 版中,最后两章讨论了基于堆栈机器的 AST 实现这一主题。最后,他展示了如何从AST的语义模型中推导出该机器的正确实现。

我试图Data.SBV在推导中寻求帮助,但失败了。

我希望有人可以帮助我了解我是否:

  1. 要求一些Data.SBV不能做的事情,或者
  2. 要求Data.SBV可以做的事情,但要求不正确。
-- test/sbv-stack.lhs - Data.SBV assisted stack machine implementation derivation.

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ScopedTypeVariables #-}

import Data.SBV
import qualified Data.SBV.List as L
import           Data.SBV.List ((.:), (.++))  -- Since they don't collide w/ any existing list functions.

-- AST Definition
data Exp = Val SWord8
         | Sum Exp Exp

-- Our "Meaning" Function
eval :: Exp -> SWord8
eval (Val x)   = x
eval (Sum x y) = eval x + eval y

type Stack  = SList Word8

-- Our "Operational" Definition.
--
-- This function attempts to implement the *specification* provided by our
-- "meaning" function, above, in a way that is more conducive to
-- implementation in our available (and, perhaps, quite primitive)
-- computational machinery.
--
-- Note that we've (temporarily) assumed that this machinery will consist
-- of some form of *stack-based computation engine* (because we're
-- following Hutton's example).
--
-- Note that we give the *specification* of the function in the first
-- (commented out) line of the definition. The derivation of the actual
-- correct definition from this specification is detailed in Ch. 17 of
-- Hutton's book.
eval' :: Exp -> Stack -> Stack
-- eval' e s = eval e : s         -- our "specification"
eval' (Val n) s = push n s        -- We're defining this one manually.
 where
  push :: SWord8 -> Stack -> Stack
  push n s = n .: s
eval' (Sum x y) s = add (eval' y (eval' x s))
 where
  add :: Stack -> Stack
  add = uninterpret "add" s       -- This is the function we're asking to be derived.

-- Now, let's just ask SBV to "solve" our specification of `eval'`:
spec :: Goal
spec = do x :: SWord8 <- forall "x"
          y :: SWord8 <- forall "y"
          -- Our spec., from above, specialized to the `Sum` case:
          constrain $ eval' (Sum (Val x) (Val y)) L.nil .== eval (Sum (Val x) (Val y)) .: L.nil

我们得到:

λ> :l test/sbv-stack.lhs
[1 of 1] Compiling Main             ( test/sbv-stack.lhs, interpreted )
Ok, one module loaded.
Collecting type info for 1 module(s) ... 
λ> sat spec
Unknown.
  Reason: smt tactic failed to show goal to be sat/unsat (incomplete quantifiers)

发生了什么?!好吧,也许,要求 SBV 解决谓词(即 - )
以外的任何问题都行不通?a -> Bool

标签: haskellsmtstack-machinesbv

解决方案


这里的基本问题是您混合了 SMTLib 的序列逻辑和量词。这个问题对于 SMT 求解器来说太难处理了。如果您将自己局限于基本逻辑,那么这种功能的综合确实是可能的。(位向量、整数、实数。)但是将序列添加到混合中会将其放入不可判定的片段中。

这并不意味着 z3 不能合成你的add函数。也许未来的版本可能能够处理它。但在这一点上,你受到启发式的摆布。要了解原因,请注意您要求求解器综合以下定义:

        add :: Stack -> Stack
        add s = v .: s''
          where (a, s')  = L.uncons s
                (b, s'') = L.uncons s'
                v        = a + b

虽然这看起来相当单纯和简单,但它需要的能力超出了 z3 当前的能力。通常,z3 目前可以合成仅对具体元素做出有限选择的函数。但是,如果输出依赖于每个输入选择的输入,它就无法做到这一点。(把它想象成一个案例分析生成引擎:它可以变出一个将某些输入映射到其他输入的函数,但无法确定是否应该增加某些东西或必须添加两个东西。这是从有限模型中的工作得出的寻找理论,并且远远超出了这个答案的范围!有关详细信息,请参见此处:https ://arxiv.org/abs/1706.00096 )

SBV 和 SMT 解决此类问题的更好用例是实际告诉它add函数是什么,然后使用 Hutton 的策略证明某个给定程序是正确“编译”的。请注意,我明确说的是“给定”程序:对于任意程序也很难建模和证明这一点,但是对于给定的固定程序,您可以很容易地做到这一点。如果你有兴趣证明任意程序的对应关系,你真的应该看看 Isabelle、Coq、ACL2 等定理证明器;它可以处理归纳,这是一种你无疑需要解决这类问题的证明技术。请注意,SMT 求解器通常无法执行归纳。(你可以使用电子匹配来模拟一些类似证明的归纳,但是它'

这是您的示例,已编码以证明\x -> \y -> x + y程序已根据引用语义“正确”编译和执行:

{-# LANGUAGE ScopedTypeVariables #-}

import Data.SBV
import qualified Data.SBV.List as L
import           Data.SBV.List ((.:))

-- AST Definition
data Exp = Val SWord8
         | Sum Exp Exp

-- Our "Meaning" Function
eval :: Exp -> SWord8
eval (Val x)   = x
eval (Sum x y) = eval x + eval y

-- Evaluation by "execution"
type Stack  = SList Word8

run :: Exp -> SWord8
run e = L.head (eval' e L.nil)
  where eval' :: Exp -> Stack -> Stack
        eval' (Val n)   s = n .: s
        eval' (Sum x y) s = add (eval' y (eval' x s))

        add :: Stack -> Stack
        add s = v .: s''
          where (a, s')  = L.uncons s
                (b, s'') = L.uncons s'
                v        = a + b

correct :: IO ThmResult
correct = prove $ do x :: SWord8 <- forall "x"
                     y :: SWord8 <- forall "y"


                     let pgm = Sum (Val x) (Val y)

                         spec    = eval pgm
                         machine = run  pgm

                     return $ spec .== machine

当我运行它时,我得到:

*Main> correct
Q.E.D.

证明几乎不需要时间。您可以通过添加其他运算符、绑定表单、函数调用来轻松扩展它,如果您愿意,整个工作都可以。只要您坚持使用固定的“程序”进行验证,它就应该可以正常工作。

如果你犯了一个错误,让我们说add通过减法定义(将它的最后一行修改为 ready v = a - b),你会得到:

*Main> correct
Falsifiable. Counter-example:
  x = 32 :: Word8
  y =  0 :: Word8

我希望这能让您了解 SMT 求解器的当前功能是什么,以及如何通过 SBV 将它们用于 Haskell。

程序综合是一个活跃的研究领域,拥有许多定制技术和工具。开箱即用的 SMT 求解器不会让您到达那里。但是,如果您确实在 Haskell 中构建了这样一个自定义系统,您可以使用 SBV 访问底层 SMT 求解器,以解决您在此过程中必须处理的许多约束。

除此之外: SBV 包附带了一个扩展示例,其精神相似但目标不同:https://hackage.haskell.org/package/sbv-8.5/docs/Documentation-SBV-Examples-Strings-SQLInjection。 html。这个程序展示了如何使用 SBV 和 SMT 求解器在理想化的 SQL 实现中查找 SQL 注入漏洞。这在这里可能会引起一些兴趣,并且会更符合 SMT 求解器在实践中的典型使用方式。)


推荐阅读