MipsMoreLikeWhips
MipsMoreLikeWhips

Reputation: 147

Unbound Variable in Scheme

I know what I want to do, I am having trouble getting there. I am looking for some guidance. I am more or less forcing what I want done and there has to be a better way than the way I am trying to create this function. I currently get an unbound variable error right where I call (set! nadj) and (set! count).

I am trying to make a function where the user inputs a sentence. If more than 25% of that sentence consists of adjectives the function returns false.

This is what I have so far:

(define OK
  (lambda (x)
    (cond
      ((adj? (car x))
       (set! count (+ count 1)))
      ((not (adj? (car x))       
            (set! nadj (+ nadj 1))))
      ((not (null? (OK (cdr x)))))                  
      ((null? x)
       (set! sum (+ nadj count)))
      ;;(set! div (/ count sum))
      ;;(* 100 div)
      ;;(< div 25))
      ((else  #f)))))

What I am trying to do is make a counter for the words that are an adjective and a counter for the words that are not. Then I am trying to add all of the words up and divide them by the amount of words that were adjectives. I then want to multiply that by 100 and return true if it is less than 25%. I am not looking for an answer, more or less I just want some guidance.

Here is the adj? function if you need to see it.

(define adjectives '(black brown fast hairy hot quick red slow))
(define adj?
  (lambda(x)
   (if ( member x adjectives) #t #f)))

I am sure this isn't normal Scheme notation. I program a lot in C++ and Java and I am having a hard time transitioning into Scheme.

Upvotes: 1

Views: 4653

Answers (3)

Will Ness
Will Ness

Reputation: 71065

You can't set an unbound variable, even a global one. Variables refer to locations; setting a variable that doesn't exist anywhere is impossible:

(set! a 1)
;Unbound variable: a           ; a doesn't refer to any location yet
(define a)
;Value: a
(list a)
;Unassigned variable: a       ; now it does, but it hasn't been assigned a value yet
(set! a 1)
;Value: a
(list a)
;Value: (1)
(set! a 2)
;Value: 1
(list a)
;Value: (2)

There's nothing wrong with localized and encapsulated mutation. Setting a global variable is by definition not localized.

You should have created local bindings (locations) for the variables you intended to use. The basic iteration built-in form do does it for you:

(define (OK x) 
  (do ((adj 0) (nadj 0))
      ((null? x)          ; loop termination condition
         (< (/ adj (+ adj nadj))
            0.25))        ; return value form
    ; loop body
    (if (adj? (car x))  
        (set! adj (+ adj 1))
        ; else
        (set! nadj (+ nadj 1)))
    ; some other statements maybe...
    ))

Just another option that sometimes might come handy. Of course the most idiomatic Scheme code is using named let construct. It will also force you to refactor a spaghetti code that you might otherwise write using do. Don't. :)

Upvotes: 0

uselpa
uselpa

Reputation: 18917

I don't know if you are familiar with the named let, but this comes in handy here:

(define (OK x)
  (let loop ((x x) (adj 0) (nadj 0)) ; named let
    (cond
      ((null? x)      (< (/ adj (+ adj nadj)) 0.25))
      ((adj? (car x)) (loop (cdr x) (+ 1 adj) nadj))
      (else           (loop (cdr x) adj (+ 1 nadj))))))

This is a convenient notation for the following, equivalent code:

(define (OK x)
  (define (loop x adj nadj)
    (cond
      ((null? x)      (< (/ adj (+ adj nadj)) 0.25))
      ((adj? (car x)) (loop (cdr x) (+ 1 adj) nadj))
      (else           (loop (cdr x) adj (+ 1 nadj)))))
  (loop x 0 0))

so basically we define an internal function, and what is a loop in a language such as C++ and Java becomes a recursive call (and to add to the confusion, the procedure that gets called recursively is sometimes called loop, as in my example). Since the call is done in tail position, this is just as efficient in Scheme as a classic loop in the languages you mentioned.

Variable assignments are replaced by modifying the parameters of the recursive call, i.e. you usually find no set! procedures in such a simple case.

EDIT an example implementation using set!:

(define OK
  (let ((adj 0) (nadj 0))
    (lambda (x)
      (cond
        ((null? x) (< (/ adj (+ adj nadj)) 0.25))
        (else      (if (adj? (car x))
                       (set! adj (+ 1 adj))
                       (set! nadj (+ 1 nadj)))
                   (OK (cdr x)))))))

Upvotes: 1

&#211;scar L&#243;pez
&#211;scar L&#243;pez

Reputation: 235984

You're correct in stating that your solution is not idiomatic Scheme - we try really hard to avoid mutating variables, all those set! operations are frowned upon: we don't really need them. A more idiomatic solution would be to pass along the counters as parameters, as demonstrated in @uselpa's answer. His solution uses explicit recursion via a named let.

We can go one step further, though - the true spirit of functional programming is to reuse existing higher-order procedures and compose them in such a way that they solve our problems. I don't know which Scheme interpreter you're using, but in Racket the OK procedure can be expressed as simply as this:

(define (OK x)         ; assuming a non-empty list
  (< (/ (count adj? x) ; count the number of adjectives
        (length x))    ; divide by the total number of words
     0.25))            ; is it less than 25%?

If your Scheme interpreter doesn't provide a count procedure import it from SRFI-1; also it's very easy to implement your own - again, this is in the spirit of functional programming: we want to build generic procedures that are useful in their own right, and easily reused and composed in other contexts:

(define (count pred lst)
  (let loop ((lst lst) (counter 0))
    (cond ((null? lst) counter)
          ((pred (car lst)) (loop (cdr lst) (+ 1 counter)))
          (else (loop (cdr lst) counter)))))

Playing Devil's advocate it's possible to fix your function using an imperative style, as long as we define the variables first (by the way, that was causing the "unbound variable" error) - for example, place a let before the looping function: think of it as a variable declaration that happens before the recursion starts. Also notice that the empty list case must appear first, to avoid accessing an element in an empty list, and don't forget to advance the recursion at each step. This is ugly, but should work:

(define (OK x) ; assuming a non-empty list
  ; declare the counters outside the function
  (let ((adj 0) (nadj 0))
    ; looping function
    (let loop ((x x))
      (cond
         ; is the list empty?
        ((null? x)
         ; is the number of adjectives less than 25%?
         (< (/ adj (+ adj nadj)) 0.25))
         ; is current element an adjective?
        ((adj? (car x))
         ; increment adj counter
         (set! adj (+ adj 1))
         ; always advance recursion
         (loop (cdr x)))
         ; is current element anything other than an adjective?
        (else       
         ; increment nadj counter
         (set! nadj (+ nadj 1))
         ; always advance recursion
         (loop (cdr x)))))))

Upvotes: 2

Related Questions