在Haskell中使用幻像类型验证程序的正确性

时间:2011-01-26 12:13:28

标签: haskell types correctness

假设我正在使用堆栈计算机的代码,它可以在整数和双精度上执行一些简单的操作(推送常量,添加,mul,dup,交换,弹出,转换类型)。

现在,我正在编写的程序采用其他语言进行描述,并将其转换为此堆栈计算机的代码。我还需要计算堆栈的最大大小。

我怀疑可以使用Haskell类型检查器来消除一些错误,例如:

  • 从空堆栈中弹出
  • 使用int multiplication倍增双倍

我认为我可以声明,例如:

dup :: Stack (a :%: b) -> Stack (a :%: b :%: b)
int2double :: Stack (a :%: SInt) -> Stack (a :%: SDouble)

等等。但后来我不知道如何生成代码并计算堆栈大小。

有可能这样做吗?它会简单/方便/值得吗?

4 个答案:

答案 0 :(得分:9)

请参阅Chris Okasaki的“在Haskell中嵌入Postfix语言的技巧”:http://www.eecs.usma.edu/webs/people/okasaki/pubs.html#hw02

另外,这个:

{-# LANGUAGE TypeOperators #-}
module Stacks where

data a :* b = a :* b deriving Show
data NilStack = NilStack deriving Show

infixr 1 :*

class Stack a where
    stackSize :: a -> Int

instance Stack b => Stack (a :* b) where
    stackSize (_ :* x) = 1 + stackSize x

instance Stack NilStack where
    stackSize _ = 0

push :: Stack b => a -> b -> a :* b
push = (:*)

pop :: Stack b => a :* b -> (a,b)
pop (x :* y) = (x,y)

dup :: Stack b => a :* b -> a :* a :* b
dup (x :* y) = x :* x :* y

liftBiOp :: Stack rest => (a -> b -> c) -> a :* b :* rest -> c :* rest
liftBiOp f (x :* y :* rest) = push (f x y) rest

add :: (Stack rest, Num a) => a :* a :* rest -> a :* rest
add = liftBiOp (+)

{-
demo: 

*Stacks> stackSize  $ dup (1 :* NilStack)
2

*Stacks> add $ dup (1 :* NilStack)
2 :* NilStack

-}

由于您的堆栈类型不同,您无法将其打包成常规状态monad(虽然您可以将其打包成参数化monad,但这是一个不同的故事)但除此之外,这应该是直截了当,愉快的,并静态检查。

答案 1 :(得分:6)

您可能会对此感兴趣:

https://github.com/gergoerdi/arrow-stack-compiler/blob/master/StackCompiler.hs

它是一个简单的汇编程序,可以在其类型中维护堆栈大小。例如。以下两个签名声明binOp,给定代码在两个寄存器上工作并按原样保留堆栈大小,创建从堆栈弹出两个参数并推送结果的代码。 compileExpr使用binOp和其他构造来创建用于计算表达式并将其推送到堆栈顶部的代码。

binOp :: (Register -> Register -> Machine n n) -> Machine (S (S n)) (S n)
compileExpr :: Expr -> Machine n (S n)

请注意,这只是一个概念验证的蠢事,我现在只是将它上传到GitHub给你看,所以不要指望找到任何好的东西。

答案 2 :(得分:4)

简单方便吗?我不确定。

我从问题描述开始。任务是从非正式的规范转变为更接近我们在Haskell(类型)中的规范。

这里有两个问题:在输入语言上强制使用基于类型的不变量(算术表达式?),并确保编译成堆栈机器程序的源语言表达式实际上正在做我们想要的。

第一个可以使用GADT轻松解决:您只需要按类型索引表达式(例如,Expr a用于表达式,其值为a型)。

第二,不太确定。您当然可以按类型级自然索引列表(例如,使用GADT)。列表上某些函数的类型(例如头部和尾部)然后变得足够精确,以便我们可以使它们合计。堆栈计算机的堆栈是否是同质的(即只包含整数或仅包含双精度数或......)?

其他属性也可以编码(并强制执行),但是这条路线可能需要程序员付出相当大的努力。

答案 3 :(得分:0)

我认为这应该没有问题,但是当你尝试在循环中做某事时,你会遇到问题。比你需要像类型级自然数这样有趣的东西。考虑这样的函数:

popN :: Int -> Stack (?????)

但如果你不需要这些东西,可以随意做任何你想做的事。顺便说一句,循环只有在元素数量之前和之后相同时才起作用,否则它将无法编译。 (A-la无限型)。

您可以尝试使用类型类修复此问题,但我想您尝试执行此操作时最好使用具有依赖类型的语言。