Clojure变量和循环

时间:2009-06-28 03:31:18

标签: lisp clojure variables loops

从谷歌搜索,我发现不鼓励使用while循环或使用变量。

现在我实现了一个非常简单的算法,它将从输入流中读取字符并进行相应的解析:如果输入为10:abcdefghej,它将解析10,然后在冒号后读取下10个字节。

我有点迷失的是我如何重构它,所以它不依赖于变量。


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

另外,我了解声明变量的唯一方法是使用with-local-vars关键字。在开始时在一个区块中定义所有变量是不是不切实际,或者我错过了一些关键点?

4 个答案:

答案 0 :(得分:18)

你所写的是具有类似lisp语法的C代码(没有违法行为)。通过不做来定义一种风格是非常明确的,但是如果你不知道“那么,那么还有什么呢?”它不是很有帮助。

顺便说一句,我不知道indicator应该做什么。

这就是我如何处理这个问题:

  1. 问题分为两部分:找到要读取的字符数,然后读取那么多字符。因此,我会写两个函数:read-countread-item,后者使用前者。

    (defn read-count [stream]
      ;; todo
      )
    
    (defn read-item [stream]
      ;; todo
      )
    
  2. read-item首先需要确定要读取的字符数。为此,它使用我们也将定义的方便函数read-count

    (defn read-item [stream]
      (let [count (read-count stream)]
        ;; todo
        ))
    
  3. 循环使用Clojure通常最好使用looprecur来处理。 loop也绑定变量,例如letacc用于累积读取的项目,但请注意,它不会在适当的位置进行修改,而是重新绑定每次迭代。

    (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
    
  4. 现在我们需要在该循环中执行某些操作:将c绑定到下一个字符,但在acc为0时返回count(zero? count)是与(= count 0)相同。我为那些不熟悉它的人注释了if形式。

    (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)))))))     ; /
    
  5. 现在我们所需要的只是read-count函数。它使用类似的循环。

    (defn read-count [stream]
      (loop [count 0]
        (let [c (.read stream)]
          (if (= c ":")
              count
              (recur (+ (* count 10)
                        (Integer/parseInt c)))))))
    
  6. 在REPL,debug,refactor上测试它。 .read真的会返回字符吗?有没有更好的方法来解析整数?

  7. 我没有对此进行过测试,而且由于没有任何经验或对Clojure的深入了解(我主要使用Common Lisp),我有点受到阻碍,但我认为它显示了如何在“lispy”中解决这类问题“ 办法。请注意我如何考虑声明或修改变量。

答案 1 :(得分:10)

我认为这个派对有点晚了,但是如果你只是将字符串视为一个字符序列并使用Clojure的序列处理原语,问题就会简单得多:

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

摘要:

  • 将丑陋的,有效的输入流转换为懒惰的字符序列,这样我们就可以假装它只是其余操作的字符串。如您所见,实际上从流中读取的字符数不会超过计算结果所需的字符数。
  • 将字符串拆分为两部分:第一个冒号前的前半部分字符,剩下的部分留下的部分。
  • 使用解构将这些部分绑定到名为beforeafter的本地,并在我们使用:时将其绑定到未使用的本地,名为{colon。 1}}用于描述性。
  • 阅读before以获取其数值
  • after中取出许多字符,然后将它们全部混合成一个包含(apply str)
  • 的字符串

Svante的回答是如何用Clojure编写循环代码的一个很好的例子;我希望我的内容是组装内置函数的一个很好的例子,这样他们就可以满足您的需求。当然这两个都使得C解决方案看起来只是“非常简单”!

答案 2 :(得分:6)

Idomatic Clojure非常适合使用序列。在C中,我倾向于考虑变量或多次改变变量的状态。在Clojure中,我认为在序列方面。在这种情况下,我会将问题分解为三层抽象:

  • 将流转换为字节序列。
  • 将字节序列转换为字符序列
  • 将字符序列转换为字符串序列。

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

字节到字符

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

这是懒惰地评估的,因此每次需要下一个字符串时,它将从输入流中拉出并构造。你可以在网络流上使用它,例如不必首先缓冲整个流,或者担心从这个流中读取代码会担心它是如何构造的。

ps:我现在不在我的REPL所以请编辑修复任何错误:)

答案 3 :(得分:3)

我自己正在学习Clojure,所以不要把它作为大师的建议而是作为同学的建议。

Clojure是一种函数式编程语言。 函数式编程意味着没有循环,没有变量,也没有副作用。如果你偏离这三条规则,你需要很好的理由这样做,而且有正当理由非常罕见。

你显然是一个非常熟练的程序员,所以看看这些信息 你应该更多地了解功能设计与面向对象设计的不同之处。

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

另外,我建议查看一些clojure代码,这是一个托管的示例程序 github.com被编写为clojure截屏教程的一部分。

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

代码所针对的截屏教程可以在这里找到,但它不是免费的:

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

(无论如何我都不隶属于peepcode.com。)

祝Clojure好运!