Search code examples
recursionfunctional-programmingstate-machineregular-languageagda

Understanding failures of recursive calls on simple proof about FSA and regular languages in Agda


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'


Solution

  • 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.


    The correct code in details

    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)
    

    Answers to non-proof related questions

    1. On predicates vs functions returning types, you asked:

    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.

    1. On the weird highlighting, you asked:

    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.


    Answer to your proof-related question

    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