Search code examples
racketstate-machineracket-student-languages

Finite State Machine Simulation Implementation in ISL+ (Racket)


I'm a newbie working through HTDP2 (Felleisen et al.) on my own but have gotten stuck on question #380 of chapter IV -Intertwined Data. The problem is within the context of creating a DSL but I am first reacquainted with a general FSM Simulator and provided with the following code:

; An FSM is a [List-of 1Transition]
; A 1Transition is a list of two items:
;   (cons FSM-State (cons FSM-State '()))
; An FSM-State is a String that specifies a color

; data examples 
(define fsm-traffic
  '(("red" "green") ("green" "yellow") ("yellow" "red")))

; FSM FSM-State -> FSM-State 
; matches the keys pressed by a player with the given FSM 
(define (simulate state0 transitions)
  (big-bang state0 ; FSM-State
    [to-draw
     (lambda (current)
      (overlay (text current 12 "black")
               (square 100 "solid" current)))]
[on-key
  (lambda (current key-event)
    (find transitions current))]))

; [X Y] [List-of [List X Y]] X -> Y
; finds the matching Y for the given X in alist
(define (find alist x)
  (local ((define fm (assoc x alist)))
    (if (cons? fm) (second fm) (error "not found"))))

The problem is then stated as follows:

Reformulate the data definition for 1Transition so that it is possible to restrict transitions to certain keystrokes. Try to formulate the change so that find continues to work without change. What else do you need to change to get the complete program to work? Which part of the design recipe provides the answer(s)? See exercise 229 for the original exercise statement.

Exercise 229 introduces a structure type definition to keep track of the states and the transitions but since the problem statement asks me to stay within the provided find function I am hard pressed to come up with a similar structure type of definition. Instead I came up with the following Data Definition:

; An FSM is a [List-of Transition]
; A Transition is a two-item list of the following form:
; (cons FSM-State (cons (cons KeyEvent (cons FSM-State '()))'()))

; data example
(define fsm-traffic (list (list "red" (list "r" "green"))
                          (list "green" (list "g" "yellow"))
                          (list "yellow" (list "y" "red"))))

Hence calling (find fsm-traffic "green") I get (list "g" "yellow") I have thus modified the on-key clause as follows:

(lambda (current key-event)
  (if (string=? key-event (first (first (rest (first transitions)))))
      (second (find transitions current))
      (error "invalid input")))

Now if I start the program with (simulate "red" fsm-traffic) State0 is rendered and if I press "r" it goes to "green" FSM-State but then it won't accept "g" to go to the following state and so on.

If I begin the world-program with (simulate "yellow" fsm-traffic) then FSM-State "yellow" is rendered but it will not transition to any other state (the error clause is activated); similarly with "green" as the starting state.

My hunch is that since I defined fsm-traffic with the "red" state first it accepts its input to transition to "green" but since the same is not happening with the other states big-bang is not "juggling" the transitions parameter right. But I just don't know how to fix that. I also don't know if I've gone wrong since the start with my data definition.

Thank you in advance for helping me out.

P.D. please let me know if I have followed the rules on posting on stackoverflow (this is my first post :).


Solution

  • You identified the source of the problem is correctly. You defined fsm-traffic with the "red" state and the "r" transition first, and the on-key handler only looks at the first. It does this in the if question:

    (string=? key-event (first (first (rest (first transitions)))))
    ;                    ^                   ^
    ;                    |                   this makes it only look at `"red"`
    ;                    this makes it only look at `"r"` within that
    

    This if question seems complicated. Just like we would do if it was a function with a signature and purpose, we can design the inputs, output, and purpose of this expression.

    Inputs: What should it depend on?

    Which keys are valid depends on the transitions table, but it also depends on the current state. This is the reason why only "r" is valid on the "red" state, but "g" is valid on the "green" state. So its inputs are:

    current : FSM-State
    key-event : KeyEvent
    transitions : FSM
    

    Your existing expression ignores current.

    Output and purpose

    It is an if question, so it should output a Boolean. The purpose of this boolean is to determine whether the key is valid on the current state.

    It should be true when there is any transition in the table for both the current state and the key-event.

    This purpose statement with "any" and "both" sounds complicated enough that it deserves to be its own function. Using the inputs, output, and purpose we just made:

    ;; FSM-State KeyEvent FSM -> Boolean
    ;; Determines whether there is any transition in the FSM table for both the current
    ;; state and the key event.
    (define (transition-for-state-and-key? state key table)
      ....)
    

    Your current expression for the body this doesn't include the state,

    (string=? key (first (first (rest (first table)))))
    

    But the actual body should depend on both state and key.

    Just like this depends on both, the find function should also depend on both state and key.

    ;; FSM-State KeyEvent FSM -> FSM-State
    ;; Finds the matching transition-state from the given state with the given key in the table.
    (define (find-transition-state state key table)
      ....)
    

    And note the relationship between transition-for-state-and-key? and find-transition-state. The transition-for-state-and-key? function should return true exactly when find-transition-state won't error.

    This should be enough for you to continue.