Reputation: 51
I am new to racket and have run into an instance where I need a mutable numeric variable
Below is a function that works thru a string of bits (101011....) and if it encounters a 1 alters a numeric variable named "value" and if it encounters a 0 has to alter that same variable "value". So when we get to the end of a string we should end up with a total for "value".
(define (implode bstr value)
(for ([c (string-length bstr)])
(display (string-ref bstr c))
(if (eqv? (string-ref bstr c) #\1) (displayln (+ (/ value 3) 17))
(displayln (/ value 3)))))
How without a mtauble variable do I have this variable change as the prgram is running please ?
Upvotes: 5
Views: 3024
Reputation: 12013
Racket supports mutation. For example:
#lang racket
;; count-whales: (listof string) -> number
;; Returns a count of the number of whales in lst.
(define (count-whales lst)
(define c 0)
(for ([thing lst])
(when (equal? thing "shamu")
(set! c (add1 c))))
c)
;; Example:
(count-whales '("shamu" "donald duck" "shamu"))
You're probably used to seeing loops this way, where mutation allows us to record some running value. There's no obstacle to writing with mutation. But that being said, the above function is not idiomatic Racket. A Racketeer will instead use a loop that can accumulate a value, rather than use mutation.
Here's what the above function looks like when written with an accumulator:
#lang racket
;; count-whales: (listof string) -> number
;; Returns a count of the number of whales in lst.
(define (count-whales lst)
(for/fold ([c 0])
([thing lst])
(if (equal? thing "shamu")
(add1 c)
c)))
;; Example:
(count-whales '("shamu" "donald duck" "shamu"))
Upvotes: 7
Reputation: 235984
As has been suggested, this problem can be easily solved using recursion, without using mutable variables. That's the preferred way to write procedures in Scheme:
(define (implode bstr value)
(let loop ((value value)
(lst (string->list bstr)))
(cond ((null? lst)
value)
((char=? (car lst) #\1)
(loop (+ (/ value 3) 17) (cdr lst)))
((char=? (car lst) #\0)
(loop (/ value 3) (cdr lst)))
(else (error "unexpected char" (car lst))))))
Upvotes: 6
Reputation: 29546
I'm not sure what is the goal of the implode
function, but yes, you definitely don't need to use mutation. Racket is a highlevel language that has tail call elimination which means that a loop via a local function as done in Óscar's example is something that the compiler will later turn into efficient code that uses mutation or whatever is needed.
But more than that, it would be good to show how to write Racket code that is more concise and perhaps closer to the mental model of mutation. The main ingredient here is for/fold
, which is doing a "for loop" but uses a "state variable" throughout the loop. The "state" here is quoted because it's not really there -- it's actually a macro that expands to a similar kind of loop as in Óscar's example. I also improved other parts of the code in a few minor but useful-to-know ways: you can iterate over a string directly, no need for an index; the in-string
is not really needed there, but it makes the kind of iteration more explicit, makes it faster, and will throw an error if bstr
is not a string; you can use case
to check the character you're on; and as long as you're using that, it's better to throw an error if a character is neither "0" nor "1".
Here's how it looks like:
(define (implode bstr)
(for/fold ([value 0]) ([c (in-string bstr)])
(case c
[(#\0) (+ (/ value 3) 17)]
[(#\1) (/ value 3)]
[else (error 'implode "bad character: ~e" c)])))
Still, this produces some weird results, but again, I'm not sure what this function is supposed to do...
Upvotes: 5