7

I'm new to Scheme and am trying to have an if-statement perform more than one action if its condition is true. I tried something like:

(if (char=? (string-ref str loc) #\a)
        ((+ count 1) (+ reference 1))
        ~else action, etc.~

And it complains about my action, saying

application: not a procedure

If I remove the parentheses, so that the action for a true condition is:

(+ count 1) (+ reference 1)

It complains

if: bad syntax

and fails to run at all. What am I missing?

nbro
  • 15,395
  • 32
  • 113
  • 196
V1rtua1An0ma1y
  • 597
  • 2
  • 10
  • 16

3 Answers3

14

There are two problems with the code. The first, an if form can't have more than one expression in the consequent and in the alternative. Two possible solutions for that - you can either use begin (not just a couple of parenthesis, that's for procedure application) to surround multiple expression:

(if (char=? (string-ref str loc) #\a)
    ; needs an explicit `begin` for more than one expression
    (begin
      (+ count 1)
      (+ reference 1))
    ; needs an explicit `begin` for more than one expression
    (begin
      ~else action, etc~))

... or use a cond, which is a better alternative because it already includes an implicit begin:

(cond ((char=? (string-ref str loc) #\a)
       ; already includes an implicit `begin`
       (+ count 1)
       (+ reference 1))
      (else
       ; already includes an implicit `begin`
        ~else action, etc~))

The second problem is more subtle and serious, both of the expressions in the consequent part are probably not doing what you expect. This one: (+ count 1) is doing nothing at all, the incremented value is lost because you didn't use it after incrementing it. Same thing with the other one: (+ reference 1), but at least here the value is being returned as the result of the conditional expression. You should either pass both incremented values along to a procedure (probably as part of a recursion):

(cond ((char=? (string-ref str loc) #\a)
       ; let's say the procedure is called `loop`
       (loop (+ count 1) (+ reference 1)))
      (else
        ~else action, etc~))

Or directly update in the variables the result of the increments, although this is not the idiomatic way to write a solution in Scheme (it looks like a solution in C, Java, etc.):

(cond ((char=? (string-ref str loc) #\a)
       ; here we are mutating in-place the value of the variables
       (set! count (+ count 1))
       (set! reference (+ reference 1)))
      (else
        ~else action, etc~))
Óscar López
  • 232,561
  • 37
  • 312
  • 386
2

If you're trying to perform multiple actions with side-effects in one arm of the if, then you'll need to put them into a begin, like this:

(if (char=? (string-ref str loc) #\a)
    (begin (set! count (+ count 1))
           (set! reference (+ reference 1)))
    ~else action, etc.~

If, instead of causing changes in variables, you want to return two values at once, then you'll either need to combine the expressions into a single object, like this:

(if (char=? (string-ref str loc) #\a)
    (cons (+ count 1) (+ reference 1)))
    ~else expression~

in which case to pull out the count and reference, you'll need to apply car and cdr to the result of the if—or you could actually return multiple values, like this:

(if (char=? (string-ref str loc) #\a)
    (values (+ count 1) (+ reference 1)))
    ~else expression~

in which case to pull out the count and reference, you'll need to bind the multiple values to variables somehow in the code that calls the if. One way to do that is with let-values, possibly something like this:

(define count&ref
  (λ (str ch)
    (let loop ([loc 0] [count 0] [reference 0])
      ; whatever other stuff you're doing
      (if (char=? (string-ref str loc) ch)
          (values (+ count 1) (+ reference 1)))
          ~else expression~ )))

(let-values ([(new-count new-ref) (count&ref "some stuff" #\u)])
  ;in here, new-count and new-ref are bound to whatever count&ref returned
  )

On the other hand, if count and reference are variables that you're just keeping track of within a loop, the simplest way might be to call the next iteration of the loop inside the if, like this:

(let loop ([loc 0] [count 0] [reference 0])
  ; whatever other stuff you're doing
  (if (char=? (string-ref str loc) #\a)
      (loop (+ loc 1) (+ count 1) (+ reference 1))
      ~else expression~ ))
Ben Kovitz
  • 4,920
  • 1
  • 22
  • 50
0

You can use begin to group a set of expressions to be executed one by one.

(if (char=? (string-ref str loc) #\a)
        (begin (+ count 1) (+ reference 1))
        ~else action, etc.~

begin only returns the value of the last expression, that is (+ reference 1), so the value of (+ count 1) is not used.

nbro
  • 15,395
  • 32
  • 113
  • 196
Ankur
  • 33,367
  • 2
  • 46
  • 72