Проверка правильности программы с использованием типов phantom в Haskell

104
6

Предположим, что я работаю с кодом стекового компьютера, который может выполнять некоторые простые операции (push, add, mul, dup, swap, pop, convert types) на ints и double.


Теперь программа, которую я пишу, берет описание на каком-то другом языке и переводит его в код для этой стековой машины. Мне также нужно вычислить максимальный размер стека.


Я подозреваю, что можно использовать проверку типа Haskell для устранения некоторых ошибок, например:


    выскакивание из пустого стека
    умножение удваивается с использованием умножения int

Я думал, что могу объявить, например:


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

и т.д. Но тогда я не знаю, как сгенерировать код и рассчитать размер стека.


Возможно ли это сделать так? И будет ли это просто/удобно/стоит?

спросил(а) 2011-01-26T15:13:00+03:00 9 лет, 7 месяцев назад
1
Решение
124

См. Крис Окасаки "Методы внедрения Postfix Languages ​​в Haskell": 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

-}


Поскольку ваш стек различается по типу, вы не можете упаковать его в обычную государственную монаду (хотя вы можете упаковать ее в параметризованную монаду, но это другая история), но кроме этого, это должно быть простым, приятным, и статически проверяется.

ответил(а) 2011-01-26T23:08:00+03:00 9 лет, 7 месяцев назад
108

Это может вас заинтересовать:


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


Это простой ассемблер, который поддерживает размер стека в своем типе. Например. следующие две сигнатуры указывают, что binOp, заданный код, который работает в двух регистрах и оставляет размер стека as-is, создает код, который выдает два аргумента из стека и толкает результат. compileExpr использует binOp и другие конструкторы для создания кода, который оценивает выражение и толкает его поверх стека.

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

Обратите внимание, что это всего лишь доказательство того, что я просто загрузил его в GitHub только для того, чтобы показать вам, поэтому не ожидайте найти в нем ничего хорошего.

ответил(а) 2011-01-26T18:10:00+03:00 9 лет, 7 месяцев назад
90

Просто и удобно? Я не уверен.


Я бы начал с описания проблемы. Задача состоит в том, чтобы перейти от неофициального к спецификации, которая ближе к тому, что у нас есть в Haskell (типы).


Здесь есть две проблемы: принудительное включение инвариантов на основе ввода на языке ввода (арифметические выражения?) и обеспечение того, чтобы выражение исходного языка, скомпилированное в программу стекового компьютера, фактически выполняло то, что мы хотим.


Первое можно легко решить с помощью GADT: вам просто нужно индексировать выражения по их типам (например, Expr a для выражений, которые оценивают значение типа a).


Второй, не так уверен. Разумеется, вы можете, конечно, индексировать списки по типам натуралов (например, с помощью GADT). Типы определенных функций в списках (например, голова и хвост) становятся настолько точными, что мы можем сделать их тотальными. Является ли стек вашей стековой машины однородным (то есть содержит только целые числа или только удваивает или...)?


Другие свойства могут быть закодированы (и принудительно), но для этого маршрута могут потребоваться значительные усилия со стороны программиста.

ответил(а) 2011-01-26T19:52:00+03:00 9 лет, 7 месяцев назад
40

Я думаю, что это должно быть возможно без проблем, но вы сталкиваетесь с проблемами, когда пытаетесь сделать что-то в цикле. Чем вам нужны такие забавные вещи, как натуральные числа типа. Рассмотрим такую ​​функцию:


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

Но если вам не нужны такие вещи, не стесняйтесь делать все, что хотите. BTW, петли работают только в том случае, если количество элементов одинаковое до и после, иначе он не будет компилироваться. (A-la бесконечный тип).


Вы можете попытаться исправить это, используя классы типа, но я предполагаю, что вы пытаетесь сделать это лучше использовать язык с зависимыми типами.

ответил(а) 2011-01-26T16:32:00+03:00 9 лет, 7 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема