I'm trying to prove that a simple FSA in Agda only accepts string which end in zero- this is the first example in Sipser's book. I didn't implement evalFSA as a predicate, but rather as function, and am confused as to whether this was the right or wrong choice, as I'm now having trouble proving the soundness and completeness results with respect to the machine and the language, and whether this implementation detail is the cause of my difficululties.
As soon as I pattern match on x below, it highlights the below line blue. what does this mean, why is it doing it, and why does pattern matching on x0 resolve it?
soundM : (xs : List Σ') → evalFSA' M xs → endsIn0 xs
soundM (x ∷ []) evM = {!!}
soundM (x0 ∷ x1 ∷ xs) evM = {!!}
-- soundM (0' ∷ []) f = tt
and here is the final issue. why can't I apply the recursive call in the 1' case? the only difference between the f's is the use current state of the machine and the input string, but obviously this seems like a symmetry of the system that shouldn't effect our ability to compute.
soundM' : (xs : List Σ') → evalFSA' M xs → endsIn0 xs
soundM' (0' ∷ []) evM = tt
soundM' (0' ∷ x1 ∷ xs) f = soundM' (x1 ∷ xs) f
soundM' (1' ∷ x1 ∷ xs) f = soundM' {!!} f
Here is the inferred f in the 0' case:
f : modal.helper M 0' (x1 ∷ xs) M xs (δ' S₁ x1)
And similairly in the 1' case:
f : modal.helper M 1' (x1 ∷ xs) M xs (δ' S₂ x1)
I'm having, simultaneous issues with what I'm calling completeness as well
completeM : (xs : List Σ') → endsIn0 xs → evalFSA' M xs ≡ ⊤
completeM (0' ∷ []) ex = refl
completeM (0' ∷ x1 ∷ xs) ex = completeM (x1 ∷ xs) ex
completeM (1' ∷ x1 ∷ xs) ex = {!!}
Here is the code to get here
module fsa where
open import Bool
open import Level using (_⊔_)
open import Data.Nat.Base as Nat using (ℕ; zero; suc; _<′_; _+_)
open import Data.List.Base as List using (List; []; _∷_)
-- open import Data.Product as Prod using (∃; _×_; _,_)
open import Data.Empty
open import Data.Unit
open import Relation.Binary.PropositionalEquality using (_≡_; refl; subst)
-- open import Data.Fin as Fin
record FSA : Set₁ where
field
Q : Set
Σ : Set
δ : Q → Σ → Q
q₀ : Q
F : Q → Set
evalFSA' : (fsa : FSA) → List (FSA.Σ fsa) → Set
evalFSA' fsa [] = ⊥
evalFSA' fsa (x ∷ xs) = helper fsa (x ∷ xs) (FSA.q₀ fsa)
where
helper : (fsa : FSA) → List (FSA.Σ fsa) → (FSA.Q fsa) → Set
helper fsa [] q = FSA.F fsa q
helper fsa (x ∷ xs) q = helper fsa xs ((FSA.δ fsa) q x)
data Q' : Set where
S₁ : Q'
S₂ : Q'
data Σ' : Set where
0' : Σ'
1' : Σ'
q₀' : Q'
q₀' = S₁
F' : Q' → Set
F' S₁ = ⊤
F' S₂ = ⊥
δ' : Q' → Σ' → Q'
δ' S₁ 0' = S₁
δ' S₁ 1' = S₂
δ' S₂ 0' = S₁
δ' S₂ 1' = S₂
M : FSA
FSA.Q M = Q'
FSA.Σ M = Σ'
FSA.δ M = δ'
FSA.q₀ M = q₀'
FSA.F M = F'
exF1 = evalFSA' M (0' ∷ [])
exF2 = evalFSA' M (1' ∷ (0' ∷ 0' ∷ 1' ∷ []))
-- a more general endIn that i was orignally trying to use, but likewise failed to get to work
data Dec (A : Set) : Set where
yes : A → Dec A
no : (A → ⊥) → Dec A
sigDec : (x y : Σ') → Dec (x ≡ y)
sigDec 0' 0' = yes refl
sigDec 0' 1' = no (λ ())
sigDec 1' 0' = no (λ ())
sigDec 1' 1' = yes refl
endsIn : {X : Set} → ((x y : X) → Dec (x ≡ y)) → List X → X → Set
endsIn d [] x = ⊥
endsIn d (x ∷ []) x0 with (d x0 x)
... | yes refl = ⊤
... | no x1 = ⊥
endsIn d (x ∷ x1 ∷ xs) x0 = endsIn d (x1 ∷ xs) x0
_endsIn'_ : List Σ' → Σ' → Set
xs endsIn' x = endsIn sigDec xs x
endsIn0 : List Σ' → Set
endsIn0 [] = ⊥
endsIn0 (0' ∷ []) = ⊤
endsIn0 (0' ∷ x ∷ xs) = endsIn0 (x ∷ xs)
endsIn0 (1' ∷ xs) = endsIn0 xs
-- testing
10endsin0 = (1' ∷ 0' ∷ []) endsIn' 0'
n10endsin0 = (1' ∷ 1' ∷ []) endsIn' 0'
Your post is very large and contains numerous elements, which all can be commented in different ways. I will address them one by one, and explain the choices I made to make these elements more easily accessible. Note that these choices consist in minor elements in your codes, mostly cosmetic, which do not change in any way the semantics of your definitions. I start by giving you the correct code in details, after which I answer the questions.
Let us start by cleaning up these imports to the minimum needed:
module FSA where
open import Data.List.Base
open import Data.Empty
open import Data.Unit
I then kept your definition of your automaton record:
record FSA : Set₁ where
field
Q : Set
Σ : Set
δ : Q → Σ → Q
q₀ : Q
F : Q → Set
I have extracted your helper
function from the evalFSA'
function. The reason for this change is that when using when
, the function inherits all the parameters from the parent function, which makes it harder to comprehend further goals such as modal.helper M 0' (x1 ∷ xs) M xs (δ' S₁ x1)
.
helper : (fsa : FSA) → List (FSA.Σ fsa) → (FSA.Q fsa) → Set
helper fsa [] q = FSA.F fsa q
helper fsa (x ∷ xs) q = helper fsa xs ((FSA.δ fsa) q x)
evalFSA' : (fsa : FSA) → List (FSA.Σ fsa) → Set
evalFSA' fsa [] = ⊥
evalFSA' fsa (x ∷ xs) = helper fsa (x ∷ xs) (FSA.q₀ fsa)
Then your case study automaton remains the same, although I simplified the record instantiation without using copatterns:
data Q' : Set where
S₁ : Q'
S₂ : Q'
data Σ' : Set where
0' : Σ'
1' : Σ'
q₀' : Q'
q₀' = S₁
F' : Q' → Set
F' S₁ = ⊤
F' S₂ = ⊥
δ' : Q' → Σ' → Q'
δ' S₁ 0' = S₁
δ' S₁ 1' = S₂
δ' S₂ 0' = S₁
δ' S₂ 1' = S₂
M : FSA
M = record { Q = Q' ; Σ = Σ' ; δ = δ' ; q₀ = q₀' ; F = F' }
I also simplified your predicate endsWith0
as follows:
endsWith0 : List Σ' → Set
endsWith0 [] = ⊥
endsWith0 (0' ∷ []) = ⊤
endsWith0 (_ ∷ xs) = endsWith0 xs
From this point on, soundM
and completeM
are proved as follows (I homogenized their signatures):
soundM : ∀ xs → evalFSA' M xs → endsWith0 xs
soundM (0' ∷ []) evM = evM
soundM (0' ∷ x₁ ∷ xs) evM = soundM (x₁ ∷ xs) evM
soundM (1' ∷ 0' ∷ xs) evM = soundM (0' ∷ xs) evM
soundM (1' ∷ 1' ∷ xs) evM = soundM (1' ∷ xs) evM
completeM : ∀ xs → endsWith0 xs → evalFSA' M xs
completeM (0' ∷ []) ex = ex
completeM (0' ∷ x₁ ∷ xs) = completeM (x₁ ∷ xs)
completeM (1' ∷ 0' ∷ xs) = completeM (0' ∷ xs)
completeM (1' ∷ 1' ∷ xs) = completeM (1' ∷ xs)
I didn't implement evalFSA as a predicate, but rather as function, and am confused as to whether this was the right or wrong choice
There are no good answer to this question. Both ideas are possible, and often debated on other questions on this site. I personally always use predicates when possible, but others have arguments in favor of functions returning ⊤
or ⊥
. And, as you noticed it is possible to prove what you wanted using your implementation.
it highlights the below line blue. what does this mean
As far as I know, this is a bug. Some weird coloration like that started to occasionally happen to me recently as well, but they apparently do not mean anything.
You asked the following question:
why can't I apply the recursive call in the 1' case? the only difference between the f's is the use current state of the machine and the input string, but obviously this seems like a symmetry of the system that shouldn't effect our ability to compute
Answering this question is actually quite simple, but this answer was somewhat hidden in your implementation because you embedded the definition of helper
inside the definition of evalFSA'
, which I changed as explained.
Let us consider the following code while proving soundM
:
soundM : (xs : List Σ') → evalFSA' M xs → endsWith0 xs
soundM (0' ∷ []) evM = evM
soundM (0' ∷ x ∷ xs) evM = soundM (x ∷ xs) {!evM!}
soundM (1' ∷ x ∷ xs) evM = soundM (x ∷ xs) {!evM!}
When asking Agda what is the goal and the type of the current element in the first goal, it answers:
Goal: helper M xs (δ' q₀' x)
Have: helper M xs (δ' S₁ x)
Since you have defined q₀'
as follows:
q₀' : Q'
q₀' = S₁
Agda know that q₀'
is definitionally equal to S₁
which means the current term has actually the same type as the goal, which is why it is accepted.
In the other hole however, asking Agda the same information gives:
Goal: helper M xs (δ' q₀' x)
Have: helper M xs (δ' S₂ x)
In this case, q₀'
is not definitionally equal to S₂
(and not equal in any way actually) which means these types are not equal, and it is not possible to conclude right away.
As shown in my code, pattern matching an additional time on x
allows agda to further reduce the goal which allows us to conclude.
A similar reasoning is used to provide a proof of completeM