sicp-ex-3.4



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


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 won't work because when call-the-cops returns a string literal, there will be a object not applicable error since a parameter amount is provided to it. "Wrong password" has the same problem.

We should wrap these in a lambda function that accepts an input.


Besides what Shawn mentioned, this solution also 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.



  
  
  
 (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) 


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) 


 (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)) 

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!")) 
  


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) 



 (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)) 

  
  
  
  
 (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) 
  
  

-------

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))