Код стека из книги TDD: попытка удалить дубликаты кода - PullRequest
0 голосов
/ 02 декабря 2018

Столкнулся с следующей проблемой, читая большую книгу Type Driven Development и пытаясь реализовать в ней небольшие модификации задач.

module Main

import Data.Vect

%default total

data Forever = More Forever

partial
forever : Forever
forever = More forever

data StackCmd : Type -> (inputHeight : Nat) -> (outputHeight : Nat) -> Type where
  Push : Integer -> StackCmd () height (S height)
  Pop : StackCmd Integer (S height) height
  Top : StackCmd Integer (S height) (S height)

  PutStr : String -> StackCmd () h h
  PutStrLn : String -> StackCmd () h h
  GetStr : StackCmd String h h

  Pure : a -> StackCmd a h h
  (>>=) : StackCmd a h1 h2 -> (a -> StackCmd b h2 h3) -> StackCmd b h1 h3


runStack : (stck : Vect inH Integer) -> StackCmd ty inH outH -> IO (ty, Vect outH Integer)
runStack stck (Push x) = pure ((), x :: stck)
runStack (x :: xs) Pop = pure (x, xs)
runStack (x :: xs) Top = pure (x, x :: xs)
runStack xs (PutStr str) = do putStr str; pure ((), xs)
runStack xs (PutStrLn str) = do putStrLn str; pure ((), xs)
runStack xs (GetStr) = do str <- getLine; pure (str, xs)
runStack stck (Pure x) = pure (x, stck)
runStack stck (x >>= f) = do (x', stck') <- runStack stck x 
                             runStack stck' (f x')

data StackIO : Nat -> Type where
  Do :    StackCmd a h1 h2 -> (a -> Inf (StackIO h2)) -> StackIO h1
  QuitCmd : (a : Nat) -> StackIO a

namespace StackDo
  (>>=) : StackCmd a h1 h2 -> (a -> Inf (StackIO h2)) -> StackIO h1
  (>>=) = Do

data Input : Type where
  INumber : Integer -> Input
  IAdd : Input
  IDuplicate : Input
  IDiscard : Input

parseInput : String -> Maybe Input
parseInput str = 
  case str of
    "" => Nothing
    "add" => Just IAdd
    "duplicte" => Just IDuplicate
    "discard" => Just IDiscard
    _      => if all isDigit $ unpack str then Just (INumber $ cast str) else Nothing


run : Forever -> Vect n Integer -> StackIO n -> IO ()
run _          _    (QuitCmd a) = pure ()
run (More far) stck (Do sa f)   = do (a', stck') <- runStack stck sa 
                                     run far stck' (f a')

biOp : (Integer -> Integer -> Integer) -> StackCmd String (S (S height)) (S height)
biOp op = do a <- Pop 
             b <- Pop
             let res = a `op` b
             Push res
             Pure $ show res

discardUnOp : StackCmd String (S height) height
discardUnOp = do v <- Pop
                 Pure $ "Discarded: " ++ show v

duplicateUnOp : StackCmd String (S height) (S (S height))
duplicateUnOp = do v <- Top
                   Push v
                   Pure $ "Duplicated: " ++ show v

mutual
  tryBiOp : String -> (Integer -> Integer -> Integer) -> StackIO hin
  tryBiOp _      op {hin=S (S k)} = do res <- biOp op
                                       PutStrLn res
                                       stackCalc
  tryBiOp opName _                = do PutStrLn $
                                         "Unable to execute operation " ++ opName ++ ": fewer then two items on stack."
                                       stackCalc

  tryUnOp : Show a => String -> StackCmd a hIn hOut -> StackIO hIn
  tryUnOp _ op   {hIn=S h} = do res <- op
                                PutStrLn $ show res
                                stackCalc
  tryUnOp opName _         = do PutStrLn $ 
                                  "Unable to execute " ++ opName ++ " operation: no elements on stack."
                                stackCalc

  stackCalc : StackIO height
  stackCalc = do PutStr "> "
                 inp <- GetStr
                 case parseInput inp of
                   Nothing => do PutStrLn "invalid input"; stackCalc
                   (Just (INumber x)) => do Push x; stackCalc
                   (Just IAdd) => tryBiOp "add" (+)
                   (Just IDuplicate) => ?holedup
                   (Just IDiscard) => ?holedisc -- tryUnOp "discard" discardUnOp

partial
main : IO ()
main = run forever [] stackCalc

Код, приведенный выше, в основном из книги TDD.Извините, это немного долго: его можно скомпилировать.Код довольно прост: это стек, реализованный над вектором.Затем пользователь может вводить числа в командной строке (по одному в строке) и программа помещает числа в стек.Пользователь также может вызывать операции, т.е. add.add извлекает два элемента из стека, добавляет их и возвращает результат обратно в стек.Итак, add требует, чтобы по крайней мере два числа были в стеке при его вызове.

Пожалуйста, обратите внимание на функцию tryBiOp.Он принимает Integer -> Integer -> Integer (то есть (+) или (-)) операцию в качестве аргумента и возвращает последовательность операций StackCmd, которая реализует необходимое действие.В результате программист может написать (Just IAdd) => tryBiOp "add" (+) внутри stackCalc.Это очень близко к тому, что я хотел бы иметь.

Вопрос. Следующее, что я хотел бы сделать, - это та же самая оболочка (она называется tryUnOp) для операций, которые требуют одногоэлемент в стеке.И поскольку эти операции выполняются не с целыми числами, а с самим стеком (т. Е. «Дубликат вершины стека» или «сбросить верхний элемент»), я хотел бы передать оболочке последовательность операций StackCmd вместо Integer -> Integer -> Integer.Итак, я хотел бы получить:

(Just IDuplicate) => tryUnOp "duplicate" $ 
                       (do v <- Top
                           Push v
                           Pure $ "Duplicated: " ++ show v)

Проблема. Если вы раскомментируете код в строке (Just IDiscard) => ?holedisc -- tryUnOp "discard" discardUnOp (и удалите дыру), вы увидите, что код не может быть скомпилирован,Как я вижу, проблема в том, что когда я звоню tryUnOp "discard" discardUnOp Идрис может видеть, что tryUnOp s hIn должен иметь форму (S k), потому что это следует из типа discardUnOp.Но stackCalc такой гарантии не дает.

Рабочее решение. Работает, но по сути это то же самое для унарной операции, что и для двоичного.Так что это не совсем то, что я хотел бы иметь.Есть функция, которая преобразует имя операции в последовательность команд стека:

data UnaryOperation : Type where
  UODup : UnaryOperation
  UODisc : UnaryOperation

UnaryOpOutHeight : UnaryOperation -> Nat -> Nat
UnaryOpOutHeight UODup inheightBase = S (S inheightBase)
UnaryOpOutHeight UODisc inheightBase = inheightBase

unaryStackCmd : (op: UnaryOperation) -> StackCmd String (S h) (UnaryOpOutHeight op h)
unaryStackCmd UODup = duplicateUnOp
unaryStackCmd UODisc = discardUnOp

mutual
  tryUnOp' : String -> UnaryOperation -> StackIO height
  tryUnOp' _      op {height=S h} = do res <- unaryStackCmd op
                                       PutStrLn res
                                       stackCalc
  tryUnOp' opName _            = do PutStrLn $
                                      "Unable to execute " ++ opName ++ " operation: no elements on stack."
                                    stackCalc

Любые идеи / комментарии приветствуются !!!

...