Clojure Variables and Loops

From a googling search, I found that using while loops or using variables is not recommended.

Now I have implemented a very simple algorithm that will read characters from the input stream and parse accordingly: if input 10:abcdefghej , it will parse 10 , and then read the next 10 bytes after the colon.

What I have lost is how I can reorganize this, so it does not rely on variables.

 (defn decode-string [input-stream indicator] (with-local-vars [length (str (char indicator) ) delimiter (.read input-stream ) string (str "") counter 0 ] (while (not(= (var-get delimiter) 58 )) (var-set length (str (var-get length) (char (var-get delimiter)) )) (var-set delimiter (.read input-stream ))) (var-set length (new BigInteger (var-get length)) ) (var-set counter (var-get length)) (while (not(zero? (var-get counter) )) (var-set string (str (var-get string) (char (.read input-stream )) )) (var-set counter (dec (var-get counter)))) (var-get string))) 

Also, I understand that the only way to declare variables is to use the with-local-vars . Is it not impractical to define all the variables in one block at the beginning or am I missing some important point?

+7
variables loops lisp clojure
source share
4 answers

A bit late for this party, I suppose, but the problem is much simpler if you just treat the string as a sequence of characters and use the Clojure sequence processing primitives:

 (defn read-prefixed-string [stream] (let [s (repeatedly #(char (.read stream))) [before [colon & after]] (split-with (complement #{\:}) s) num-chars (read-string (apply str before))] (apply str (take num-chars after)))) user> (let [in (java.io.StringReader. "10:abcdefghij5:klmnopqrstuvwxyz")] (repeatedly 2 #(read-prefixed-string in))) ("abcdefghij" "klmno") 

Summary:

  • Convert the ugly, sidestream input stream to a lazy character sequence so we can pretend it's just a string for the rest of this operation. As you can see, no more characters are actually read from the stream than is necessary to calculate the result.
  • Divide the line into two parts: the first half of the characters before the first colon, and the second half will remain.
  • Use destructuring to bind these parts to locales with the names before and after and cross out : while we are on it, binding it to an unused local name with the name colon for the description.
  • Read before to get its numerical value
  • Take a lot of characters from after and concatenate them all together in a string with (apply str)

Svante's answer is a great example of how to write loop code with Clojure; I hope that mine is a good example of assembling built-in functions so that they do what you need. Of course, both of them make Solution C look like anything, but "very simple"!

+10
source share

What you write is C code with lisp -like syntax (no offense). Defining a style with what you don't do is very important, but it’s not very useful if you don’t know “well, and then again?”

By the way, I do not know what to do indicator .

This is how I approach this problem:

  • The task consists of two parts: find the number of characters to read, and then read that many characters. Therefore, I would write two functions: read-count and read-item , with the latter using the former.

     (defn read-count [stream]
       ;;  todo
       )
    
     (defn read-item [stream]
       ;;  todo
       )
    
  • read-item First you need to determine the number of characters to read. To do this, it uses the convenient read-count function, which we will also define.

     (defn read-item [stream]
       (let [count (read-count stream)]
         ;;  todo
         )))
    
  • The loop found in Clojure is usually best used with loop and recur . loop also binds variables like let . acc intended to accumulate read elements, but note that it is not changed in place, but resets each iteration.

     (defn read-item [stream]
       (loop [count (read-count stream)
              acc ""]
         ;;  todo
         (recur (dec count); new value for count
                (str acc c)))));  new value for acc
    
  • Now we need to do something in this loop: bind c next character, but return acc when count is 0. (zero? count) matches (= count 0) . I annotated the if form a little for those who are not familiar with it.

     (defn read-item [stream]
       (loop [count (read-count stream)
              acc ""]
         (if (zero? count); condition
             acc;  then
             (let [c (.read stream)]; \
               (recur (dec count);> else
                      (str acc c)))))));  /
    
  • Now we only need the read-count function. It uses a similar loop.

     (defn read-count [stream]
       (loop [count 0]
         (let [c (.read stream)]
           (if (= c ":")
               count
               (recur (+ (* count 10)
                         (Integer / parseInt c))))))))
    
  • Check it out on REPL, debug, refactor. Does .read return characters? Is there a better way to parse an integer?

I have not tested this, and I am a little disturbed by the lack of experience and in-depth knowledge about Clojure (I usually use Common Lisp), but I think it shows how to approach this problem in a “sticky” form. Note that I am not thinking of declaring or changing variables.

+18
source share

Idomatic Clojure really gives itself the ability to work with sequences. In C, I tend to think in terms of variables or changing the state of a variable a bunch of times. In Clojure, I think in terms of sequences. In this case, I would break the problem into three levels of abstraction:

  • turn a stream into a sequence of bytes.
  • turn a sequence of bytes into a sequence of characters
  • translate a sequence of characters into a sequence of strings.

stream in bytes:

 defn byte-seq [rdr] "create a lazy seq of bytes in a file and close the file at the end" (let [result (. rdr read)] (if (= result -1) (do (. rdr close) nil) (lazy-seq (cons result (byte-seq rdr)))))) 

bytes to characters

 (defn bytes-to-chars [bytes] (map char bytes)) 

chars-to-strings [chars]

 (defn chars-to-strings [chars] (let [length-str (take-wile (#{1234567890} %) chars) length (Integer/parseInt length-str) length-of-lengh (inc (count length-str)) str-seq (drop length-of-length chars)] (lazy-seq (cons (take length str-seq) (recur (drop (+ length-of-length length) chars)))))) 

This is evaluated lazily, so every time the next line is needed, it will be pulled out of the input stream and constructed. you could use this in a network stream, for example, without having to first buffer the entire stream or worry that reading code from this stream worries about how it is built.

ps: im not on my REPL at the moment, so please edit to fix any erros :)

+6
source share

I myself study Clojure, so take it not as advice from a guru, but as advice from students.

Clojure is a functional programming language. Functional programming means no cycles, variables, or side effects. If you ever deviate from these three rules, you need very good reasons for this, and acceptable reasons are pretty rare.

You are obviously a very experienced programmer, so take a look at this information and you should hope to get more ideas on how functional design differs from object-oriented design.

http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#Sequence_Functions

Also, I would recommend looking at the Clojure code, here is an example of a program hosted on github.com that was written as part of the Clojure screencast tutorial.

http://github.com/technomancy/mire/tree/master

The screencast tutorial for which the code was intended can be found here, but it is not free:

http://peepcode.com/products/functional-programming-with-clojure

(I'm not affiliated with peepcode.com at all).

Good luck with Clojure!

+3
source share

All Articles