Stack code from TDD book: trying to remove code duplicates - idris

Faced next problem while reading great Type Driven Development book and trying to implement some small modifications of tasks in it.
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
Code given above is mostly from TDD book. Sorry that its a bit long: it can be compiled. The code is rather straightforward: this is a stack implemented above vector. Then, user can type numbers in command prompt (one per line) and program pushes the numbers on stack. User is also able to call operations, i.e. add. add pops two elements from stack, adds them and pushes the result back on stack. So, add requires at least two numbers to be on stack when it is called.
Please take a look on tryBiOp function. It takes an Integer -> Integer -> Integer (i.e. (+) or (-)) operation as it's argument and returns sequence of StackCmd operations which implements needed action. As a result programmer can write (Just IAdd) => tryBiOp "add" (+) inside stackCalc. This is very close to what I would like to have.
Question. Next thing I would like to do is very the same wrapper (it named tryUnOp) for operations which require one element on stack. And since these operations are not on integers, but on a stack itself (i.e. "duplicate top of stack" or "discard top element") I would like to pass to wrapper the sequence of StackCmd operations instead of Integer -> Integer -> Integer. So, what I would like to gain is
(Just IDuplicate) => tryUnOp "duplicate" $
(do v <- Top
Push v
Pure $ "Duplicated: " ++ show v)
Problem. If you uncomment code in string (Just IDiscard) => ?holedisc -- tryUnOp "discard" discardUnOp (and remove hole), you will see that code can not be compiled. As I see problem is that when I call tryUnOp "discard" discardUnOp Idris can see that tryUnOp's hIn must be of form (S k) because it follows from discardUnOp's type. But stackCalc does not provide such guarantee.
Working solution. It works, but it is essentially the same thing for unary operation as for binary. So, it is not exactly what I would like to have. There is a function which converts name of operation to sequence of stack commands:
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
Any ideas / comments are wellcome!!!

Related

How to use interfaces with parameterized tuple?

I have Coord function that transforms an n-dimensional size to the type of coordinates bounded by given size: Coord [2,3] = (Fin 2, Fin 3).
import Data.Fin
import Data.List
Size : Type
Size = List Nat
Coord : Size -> Type
Coord [] = ()
Coord s#(_ :: _) = foldr1 (,) $ map Fin s
I'd like to use show and other functions like (==) with Coord s:
foo : Coord s -> String
foo x = show x
Error: While processing right hand side of foo. Can't find an implementation for Show (Coord s).
22 | foo : Coord s -> String
23 | foo x = show x
^^^^^^
Earlier I tried to implement Show (Coord s), but looks like it's impossible. Here is linked question about it.
You can make your own list like data type:
data Coords : List Nat -> Type where
Nil : Coords []
(::) : Fin x -> Coords xs -> Coords (x :: xs)
toList : Coords xs -> List Nat
toList [] = []
toList (x::xs) = finToNat x :: toList xs
example : Coords [2, 3]
example = [1, 2]
Show (Coords xs) where
show cs = show $ toList cs
You can also try using Data.Vect.Quantifiers.All or Data.List.Quantifiers.All:
import Data.Vect
import Data.Vect.Quantifiers
example : All Fin [1, 2, 3]
example = [0, 1, 2]
-- not sure why this is isn't included with Idris
export
All (Show . p) xs => Show (All p xs) where
show pxs = "[" ++ show' "" pxs ++ "]"
where
show' : String -> All (Show . p) xs' => All p xs' -> String
show' acc #{[]} [] = acc
show' acc #{[_]} [px] = acc ++ show px
show' acc #{_ :: _} (px :: pxs) = show' (acc ++ show px ++ ", ") pxs
string : String
string = show example

Type error in an Idris exercise

There's an Idris exercise where the task is to extend a stack calculator program from supporting just addition to also supporting subtraction and multiplication. I tried to complete it by generalizing the functions that operate on the stack. However, I'm hit with a type error between my two central functions:
doOp : (Integer -> Integer -> Integer) ->
StackCmd () (S (S height)) (S height)
doOp f = do val1 <- Pop
val2 <- Pop
Push (f val1 val2)
tryOp : StackCmd () (S (S height)) (S height) ->
StackIO hinit
tryOp cmd {hinit = (S (S h))}
= do cmd
result <- Top
PutStr (show result ++ "\n")
stackCalc
doOp is supposed to take a binary function and generate a sequence of actions that would apply it to that stack, while tryOp takes in such a sequence of actions and integrates it into the the IO sequence.
The error is the following:
When checking an application of function Main.StackDo.>>=:
Type mismatch between
StackCmd () (S (S height)) (S height) (Type of cmd)
and
StackCmd () (S (S h)) (S height) (Expected type)
Specifically:
Type mismatch between
height
and
h
The functions are called like this:
Just Add => tryOp (doOp (+))
Just Subtract => tryOp (doOp (-))
Just Multiply => tryOp (doOp (*))
And that results in an error as well:
Can't infer argument height to tryOp, Can't infer argument height to doOp
The error messages seem simple enough to understand, but I don't know how to approach fixing them.
Additionally this is how StackIO and bind are defined:
data StackIO : Nat -> Type where
Do : StackCmd a height1 height2 ->
(a -> Inf (StackIO height2)) -> StackIO height1
namespace StackDo
(>>=) : StackCmd a height1 height2 ->
(a -> Inf (StackIO height2)) -> StackIO height1
(>>=) = Do
Your doOp has the implicit argument height. So doOp (+) {height=5} and doOp (+) {height=10} are different StackCmds, even if the result is the same. That leads to a problem here:
tryOp : StackCmd () (S (S height)) (S height) ->
StackIO hinit
tryOp cmd {hinit = (S (S h))}
h and height can be different. You could have a StackCmd that only operates on a stack with height = 10, while hinit has h = 5. Things you can do: change tryOp to tryOp : StackCmd () (S (S height)) (S height) -> StackIO (S (S height)). This is then a function that will always succeed.
This might have not the intended functionality, as tryOp seems to mean that it can fail. If this is the case, you have to check decEq h height.
tryOp : StackCmd () (S (S height)) (S height) ->
StackIO hinit
tryOp cmd {height} {hinit = (S (S h))} with (decEq h height)
| Yes Refl = do cmd
…
| No contra = do PutStr "Not enough values on stack"
…
The Just Add => tryOp (doOp (+)) part has most likely the same problem; in the context there is not enough information on how big the stack currently is. If you need further help, you need to provide all the definitions.
All of the three parts will then need different StackCmds for all possible stack sizes. This is not really a problem, but a nicer (I think) but bit more complicated (as you might need to apply some algebra rules) solution would lift the variable height from StackCmd. Then the arguments are only the difference in the stack size after applying the operation:
doOp : (Integer -> Integer -> Integer) ->
StackCmd () 2 1
doOp f = do val1 <- Pop
val2 <- Pop
Push (f val1 val2)
tryOp : StackCmd () 2 1 ->
StackIO hinit
tryOp cmd {hinit = S (S n)} = do cmd
…
tryOp cmd {hinit = m} = do PutStr "Not enough values on stack"
…
with
data StackIO : Nat -> Type where
Do : StackCmd a prev next ->
(a -> Inf (StackIO (next + height))) -> StackIO (prev + height)
namespace StackDo
(>>=) : StackCmd a prev next ->
(a -> Inf (StackIO (next + height))) -> StackIO (prev + height)
(>>=) = Do

Inf value is automatically forced after pattern matching

Let's say we have an infinite list:
data InfList : Type -> Type where
(::) : (value : elem) -> Inf (InfList elem) -> InfList elem
And we want to have finite number of its elements:
getPrefix : (count : Nat) -> InfList a -> List a
getPrefix Z _ = []
getPrefix (S k) (value :: xs) = value :: getPrefix k (?rest)
So, what is left:
a : Type
k : Nat
value : a
xs : InfList a
--------------------------------------
rest : InfList a
It turned out that after pattern matching xs become InfList a instead of Inf (InfList a).
Is there a way to have xs delayed?
It seems to be delayed anyway.
If you execute :x getPrefix 10 one with
one : InfList Int
one = 1 :: one
you get 1 :: getPrefix 9 (1 :: Delay one)
I can't find it anymore in the documentation but idris seems to insert Delay automatically.
Just try to add Delay constructor manually. It's removed implicitly.
getPrefix : (count : Nat) -> InfList a -> List a
getPrefix Z _ = []
getPrefix (S k) (value :: Delay xs) = value :: getPrefix k xs

Check if Vector's Lengths are Equal

Given the following from Type-Driven Development with Idris:
import Data.Vect
data EqNat : (num1 : Nat) -> (num2 : Nat) -> Type where
Same : (num : Nat) -> EqNat num num
sameS : (eq : EqNat k j) -> EqNat (S k) (S j)
sameS (Same n) = Same (S n)
checkEqNat : (num1 : Nat) -> (num2 : Nat) -> Maybe (EqNat num1 num2)
checkEqNat Z Z = Just $ Same Z
checkEqNat Z (S k) = Nothing
checkEqNat (S k) Z = Nothing
checkEqNat (S k) (S j) = case checkEqNat k j of
Just eq => Just $ sameS eq
Nothing => Nothing
exactLength : (len : Nat) -> (input : Vect m a) -> Maybe (Vect len a)
exactLength {m} len input = case (checkEqNat m len) of
Just (Same m) => Just input
Nothing => Nothing
If I replace the last function's Just (Same m) with Just eq, the compiler complains:
*Lecture> :r
Type checking ./Lecture.idr
Lecture.idr:19:75:
When checking right hand side of Main.case block in exactLength at Lecture.idr:18:34 with expected type
Maybe (Vect len a)
When checking argument x to constructor Prelude.Maybe.Just:
Type mismatch between
Vect m a (Type of input)
and
Vect len a (Expected type)
Specifically:
Type mismatch between
m
and
len
Holes: Main.exactLength
How does Just (Same m), i.e. the working code, provide "evidence" that exactLength's len and m are equal?
What I find useful when working with Idris is adding holes when you're not sure about something rather than solving them. Like adding a hole into Just ... branch to see what's going on there:
exactLength : (len : Nat) -> (input : Vect m a) -> Maybe (Vect len a)
exactLength {m} len input = case (checkEqNat m len) of
Just (Same m) => ?hole
Nothing => Nothing
and then change (Same m) to eq and back while looking at the results of type checking. In the eq case it's like this:
- + Main.hole [P]
`-- a : Type
m : Nat
len : Nat
eq : EqNat m len
input : Vect m a
--------------------------------
Main.hole : Maybe (Vect len a)
And in the (Same m) case it's like this:
- + Main.hole_1 [P]
`-- m : Nat
a : Type
input : Vect m a
--------------------------------
Main.hole_1 : Maybe (Vect m a)
So eq is something of a type EqNat m len, no one knows whether it's inhabitant or not, while Same m (or Same len) is definitely inhabitant which proves that m and len are equal.
When you start with
exactLength : (len : Nat) -> (input : Vect m a) -> Maybe (Vect len a)
exactLength {m} len input with (_)
exactLength {m} len input | with_pat = ?_rhs
and gradually extend the missing links until you reached
exactLength : (len : Nat) -> (input : Vect m a) -> Maybe (Vect len a)
exactLength {m} len input with (checkEqNat m len)
exactLength {m = m} len input | Nothing = Nothing
exactLength {m = len} len input | (Just (Same len)) = Just input
you can see how idris can derive from the fact that checkEqNat m len returned a Just (Same ...) that it can then infer that {m = len}. AFAIK just writing Just eq is not a proof that eq is indeed inhabited.

Finding and replacing

There are times that we want to find an element in a list with a function a -> Bool and replace it using a function a -> a, this may result in a new list:
findr :: (a -> Bool) -> (a -> a) -> [a] -> Maybe [a]
findr _ _ [] = Nothing
findr p f (x:xs)
| p x = Just (f x : xs)
| otherwise = case findr p f xs of Just xs -> Just (x:xs)
_ -> Nothing
Is there any function in the main modules which is similar to this?
Edit: #gallais points out below that you end up only changing the first instance; I thought you were changing every instance.
This is done with break :: (a -> Bool) -> [a] -> ([a], [a]) which gives you the longest prefix which does not satisfy the predicate, followed by the rest of the list.
findr p f list = case break p list of
(xs, y : ys) -> Just (xs ++ f y : ys)
(_, []) -> Nothing
This function is, of course, map, as long as you can combine your predicate function and replacement function the right way.
findr check_f replace_f xs = map (replace_if_needed check_f replace_f) xs
replace_if_needed :: (a -> Bool) -> (a -> a) -> (a -> a)
replace_if_needed check_f replace_f = \x -> if check_f x then replace_f x else x
Now you can do things like findr isAplha toUpper "a123-bc".