sicp-ex-3.4



<< Previous exercise (3.3) | Index | Next exercise (3.5) >>


Exercise 3.4 Modify the make-account procedure of exercise 3.3 by adding another local state variable so that, if an account is accessed more than seven consecutive times with an incorrect password, it invokes the procedure call-the-cops.

Igor Saprykin

  
  
  
 (define (make-account balance password) 
   (define (call-the-cops) "Call the cops") 
   (let ((count 0) 
         (limit 7)) 
     (define (withdraw amount) 
       (if (>= balance amount) 
           (begin (set! balance (- balance amount)) 
                  balance) 
           "Not enough money")) 
     (define (deposit amount) 
       (set! balance (+ balance amount)) 
       balance) 
     (define (dispatch pass m) 
       (if (not (eq? pass password)) 
           (lambda (amount) 
             (if (> count limit) 
                 (call-the-cops) 
                 (begin (set! count (+ count 1)) 
                        "Wrong password"))) 
           (begin (set! count 0) 
                  (cond ((eq? m 'withdraw) withdraw) 
                        ((eq? m 'deposit) deposit) 
                        (else (error "Unknown call -- MAKE-ACCOUNT" 
                                     m)))))) 
       dispatch)) 

This solution has a logic error. It will actually invoke the "call-the-copes" function when there's more than 8 wrong inputs in a row, instead of 7.




Daniel-Amariei

  
  
  
 (define (make-accumulator acc) 
   (lambda (x)  
     (set! acc (+ acc x)) 
     acc)) 
  
  
 (define (make-account balance secret-password) 
   (define (call-the-cops) "The cops have been caled!") 
   (define attempts (make-accumulator 0)) 
   (define (attempts-made) (attempts 0)) 
   (define (reset-attempts) (attempts (- (attempts 0)))) 
    
   (define (is-correct-password? password) 
    (cond ((equal? secret-password password) 
           (reset-attempts) true) 
           (else (attempts 1)  
                 false))) 
    
   (define (withdraw amount) 
     (cond ((>= balance amount) 
            (set! balance (- balance amount)) 
            balance) 
           (else  
            "Insufficient funds"))) 
   
    
   (define (deposit amount) 
     (set! balance (+ balance amount)) 
     balance) 
    
   (define (dispatch password m) 
     (cond ((not (is-correct-password? password)) 
            (if (> (attempts-made) 7) 
                (lambda (x) (call-the-cops)) 
                (lambda (x) "Incorrect password"))) 
           ((equal? m 'withdraw) withdraw) 
           ((equal? m 'deposit) deposit) 
           (else (error "Unknown request: MAKE-ACCOUNT" m)))) 
   dispatch) 


eliyak

I put all the password-checking functionality into a separate internal function called correct-password?. Unfortunately my implementation of scheme does not know how to (call-the-cops).

 (define (make-account balance password) 
   (define bad-password-count 0) 
   (define (correct-password? p) 
     (if (eq? p password) 
         (set! bad-password-count 0) 
         (begin 
           (if (= bad-password-count 7) 
               (call-the-cops) 
               (set! bad-password-count (+ bad-password-count 1))) 
           (display "Incorrect password") 
           #f))) 
   (define (withdraw amount) 
     (if (>= balance amount) 
         (begin (set! balance (- balance amount)) 
                balance) 
         "Insufficient funds")) 
   (define (deposit amount) 
     (set! balance (+ balance amount)) 
     balance) 
   (define (dispatch p m) 
     (if (correct-password? p) 
         (cond 
           ((eq? m 'withdraw) withdraw) 
           ((eq? m 'deposit) deposit) 
           (else (error "Unknown request -- MAKE-ACCOUNT" m))) 
         (lambda (x) (display "")))) 
   dispatch) 


anonymous

(this code was contributed at the 27 Dec 2013 edit)

  
 (define (make-account balance password) 
   (let ((bad-passwords 0)) 
     (define (withdraw amount) 
       (if (>= balance amount) 
           (begin (set! balance (- balance amount)) 
                  balance) 
           (print "Insufficient funds"))) 
     (define (deposit amount) 
       (set! balance (+ balance amount)) 
       balance) 
     (define (dispatch p m) 
       (if (good-password? p) 
           (cond ((eq? m 'withdraw) withdraw) 
                 ((eq? m 'deposit) deposit) 
                 (else (error "Unknown request -- MAKE-ACCOUNT" m))) 
           (lambda (x) (print "Incorrect password") (newline)))) 
     (define (good-password? p) 
       (cond ((eq? p password) 
              (set! bad-passwords 0) 
              true) 
             ((< bad-passwords 7) 
              (set! bad-passwords (+ bad-passwords 1)) 
              false) 
             (else 
              (call-the-cops)))) 
     (define (call-the-cops) 
       (print "Cops called!") 
       false) 
     dispatch)) 


Caleb Gossler

I wrapped dispatch in a function that handles the password checking. It keeps dispatch much cleaner.

 (define (make-account password balance) 
   (define (withdraw amount) 
     (if (>= balance amount) 
         (begin (set! balance (- balance amount)) 
                balance) 
         "Insufficient funds")) 
   (define (deposit amount) 
     (set! balance (+ balance amount)) 
     balance) 
   (define (dispatch m) 
     (cond ((eq? m 'withdraw) withdraw) 
           ((eq? m 'deposit) deposit) 
           (else (error "Unknown request -- MAKE-ACCOUNT" 
                        m)))) 
   (password-protect password dispatch)) 
  
 (define (password-protect user-password f) 
   (define invalid-attempts 0) 
   (lambda (password m) 
     (cond [(= invalid-attempts 7) 
            (call-the-cops)] 
           [(eq? password user-password) 
            (set! invalid-attempts 0) 
            (f m)] 
           [else 
            (set! invalid-attempts (+ 1 invalid-attempts)) 
            (error "Incorrect password")]))) 
  
 (define (call-the-cops) 
   (error "The cops have been called!")) 
  


Han Chan

My implementation is much simpler. I just wrap the dispatch function with an "auth-layer" function, which will do the authentication. Then I wrap the "auth-layer" function with a "secure-layer", which will check the error counter and decide to call the cops or not.

 (define (secure-make-account balance password) 
  
   (define error-count 0) 
  
   (define (call-the-police) "Too much errors, we have called LAPD") 
    
   (define (withdraw amount) 
     (if (>= balance amount) 
         (begin (set! balance (- balance amount)) balance) 
         "Insufficient funds")) 
    
   (define (deposit amount) 
     (set! balance (+ balance amount)) balance) 
    
   (define (dispatch m) 
     (cond  ((eq? m 'withdraw) withdraw) 
            ((eq? m 'deposit) deposit) 
            (else (error "Unknown request -- MAKE-ACCOUNT" m))) 
     ) 
  
   (define (auth-layer pass m) 
     (if (eq? pass password) 
         (dispatch m) 
         (lambda (x) (begin (set! error-count (+ 1 error-count)) 
                            "Incorrect password")))) 
   (define (secure-layer pass m) 
     (if (= 2 error-count) 
         (lambda (x) (call-the-police)) 
         (auth-layer pass m))) 
    
   secure-layer) 

The spec says "accessed more than seven consecutive times..." And so the above code has two problems: first, 2 != 7 and second, "consecutive" means "following one another without interruption." Thus, a matching (i.e. successful) password must reset the error-count.




Sphinxsky

  
  
  
  
 (load "make-monitored.scm") 
  
 (define (make-account balance password) 
  
     (define (withdraw amount) 
         (if (>= balance amount) 
             (begin 
                 (set! balance (- balance amount)) 
                 balance) 
             "insufficient funds")) 
  
     (define (deposit amount) 
         (set! balance (+ balance amount)) 
         balance) 
  
     (define (call-the-cops) "We have already called the police for your actions.") 
  
     (define (password-errors-logic monitored) 
         (lambda (m) 
             (let ((wrong-times (monitored 'how-many-calls?))) 
                 (if (= wrong-times 6) 
                     (begin 
                         (monitored 'reset-count) 
                         (call-the-cops)) 
                     (monitored 
                         (if (eq? m 'reset-count) 
                             'reset-count 
                             (- 6 wrong-times))))))) 
  
     (define counter 
         (password-errors-logic 
             (make-monitored 
                 (lambda (m) 
                     (string-append 
                         "Incorrect password: " 
                         "You can still enter " 
                         (number->string m) 
                         " wrong times."))))) 
  
     (define (dispatch p m) 
         (if (eq? p password) 
             (begin 
                 (counter 'reset-count) 
                 (cond ((eq? m 'withdraw) withdraw) 
                     ((eq? m 'deposit) deposit) 
                     (else (error "unknown request -- MAKE-ACCOUNT" m)))) 
             counter)) 
      
     dispatch) 
  
  


zxymike93

My solution is not so original but it's plain and simple.

  
 (define (make-account balance password) 
  
   (define psswd password) 
  
   (define lock 0) 
    
   (define (count-incorrect-password amount) 
     (begin (set! lock (+ lock 1)) 
            (display "Incorrect password") 
            (newline))) 
  
   (define (call-the-cops amount) 
     (error "Bee-boo Bee-boo Bee-boo")) 
  
   (define (withdraw amount) 
     (if (>= balance amount) 
         (begin (set! balance  
                      (- balance amount)) 
                balance) 
         "Insufficient funds")) 
   (define (deposit amount) 
     (set! balance (+ balance amount)) 
     balance) 
    
   (define (dispatch pw m) 
     (cond [(eq? pw psswd) 
            (cond ((eq? m 'withdraw) withdraw) 
                  ((eq? m 'deposit) deposit) 
                  (else (error "Unknown request:  
                  MAKE-ACCOUNT" m)))] 
           [else 
            (cond [(>= lock 6) call-the-cops] 
                  [else count-incorrect-password])])) 
    
   dispatch) 


Carl Egbert

A solution that uses a high-order, reusable make-passworded function, which should work with any function with an identical API to make-account, and also reuses the make-monitored solution from exercise 3.2:

  
 ;; original function from the text 
 (define (make-account balance) 
   (define (withdraw amount) 
     (if (>= balance amount) 
         (begin (set! balance (- balance amount)) 
                balance) 
         "Insufficient funds")) 
   (define (deposit amount) 
     (set! balance (+ balance amount)) 
     balance) 
   (define (dispatch m) 
     (cond  ((eq? m 'withdraw) withdraw) 
            ((eq? m 'deposit) deposit) 
            (else (error "Unknown request -- MAKE-ACCOUNT" 
                         m)))) 
   dispatch) 
  
 ;; solution from exercise 3.2 
 (define (make-monitored f) 
   (let ((count 0)) 
     (define (how-many-calls?) 
       count) 
     (define (reset-count) 
       (set! count 0)) 
     (define (monitored x) 
       (begin 
         (set! count (+ 1 count)) 
         (f x))) 
     (define (dispatch x) 
       (cond 
         ((eq? x 'how-many-calls?) (how-many-calls?)) 
         ((eq? x 'reset-count) (reset-count)) 
         (else (monitored x)))) 
     dispatch)) 
  
  
  
 (define (make-passworded f) 
   (define (failed-entry x) "incorrect password") 
   (define (call-the-cops x) "weeeoooo weeeeoooo weeeeeoooo") 
  
   (define (constructor initial password) 
     (define (correct-pass? attempt) 
       (eq? password attempt)) 
     (define try-pass? 
       (make-monitored correct-pass?)) 
     (define (reset-login-attempts) 
       (try-pass? 'reset-count)) 
     (define (failed-attempts) 
       (try-pass? 'how-many-calls?)) 
  
     (let ((passworded-f (f initial))) 
       (define (dispatch attempt x) 
         (if (try-pass? attempt) 
             (begin (reset-login-attempts) 
                    (passworded-f x)) 
             (if (< 6 (failed-attempts)) 
                 call-the-cops 
                 failed-entry))) 
       dispatch)) 
   constructor) 
  
 (define passworded-make-account 
   (make-passworded make-account)) 


roy-tobin

Unique here is the straightforward logic cascade at the cond expression that handles all five possible contingencies in as many lines of code.

 (define (make-account balance passwd) 
 (let ((access-failures 0)) 
   (define (call-the-cops) (error "Out of the car, longhair!")) 
   (define (bad-passwd dontcare) "Incorrect password") 
   (define (withdraw amount) 
       (if (>= balance amount) 
           (begin (set! balance (- balance amount)) balance) 
           "Insufficient funds")) 
   (define (deposit amount) 
       (set! balance (+ balance amount)) 
       balance) 
   (define (dispatch phrase m) 
       (if (eq? phrase passwd) 
           (set! access-failures 0) 
           (set! access-failures (+ 1 access-failures))) 
       (cond ((> access-failures 7) (call-the-cops)) 
             ((> access-failures 0) bad-passwd) 
             ((eq? m 'withdraw)     withdraw) 
             ((eq? m 'deposit)      deposit) 
             (else (error "unknown request -- MAKE-ACCOUNT" m)))) 
   dispatch))