Clojure 变量和循环

7

通过谷歌搜索,我发现使用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个回答

18
您正在编写的是具有Lisp语法风格的C代码(无意冒犯)。通过“不做什么”来定义风格是非常明确的,但如果您不知道“那还有其他方法吗?”则没有太大帮助。
顺便说一下,我不知道`indicator`应该做什么。
这是我处理此问题的方式:
1. 该问题有两个部分:查找要读取的字符数,然后读取相应数量的字符。因此,我将编写两个函数:`read-count`和`read-item`,后者使用前者。
``` clojure (defn read-count [stream] ;; 待完成 )
(defn read-item [stream] ;; 待完成 ) ```
2. `read-item`首先需要确定要读取的字符数。为此,它使用我们也将定义的方便函数`read-count`。
``` clojure (defn read-item [stream] (let [count (read-count stream)] ;; 待完成 )) ```
3. 在Clojure中,循环通常最好使用`loop`和`recur`来处理。`loop`也像`let`一样绑定变量。`acc`用于累积读取的项,但请注意,它不是在原地修改的,而是每次迭代都重新绑定。
``` clojure (defn read-item [stream] (loop [count (read-count stream) acc ""] ;; 待完成 (recur (dec count) ;count的新值 (str acc c))))) ;acc的新值 ```现在我们需要在这个循环中做一些事情:将c绑定到下一个字符,但当count为0时返回acc(zero? count)(= count 0)是相同的。我为那些不熟悉它的人注释了一下if表达式。
(defn read-item [stream]
  (loop [count (read-count stream)
         acc ""]
    (if (zero? count)                  ; 条件
        acc                            ; 真
        (let [c (.read stream)]        ; \
          (recur (dec count)           ;  > 假
                 (str acc c)))))))     ; /
  • 现在我们只需要read-count函数。它使用了类似的循环。

    (defn read-count [stream]
      (loop [count 0]
        (let [c (.read stream)]
          (if (= c ":")
              count
              (recur (+ (* count 10)
                        (Integer/parseInt c)))))))
    
  • 在REPL上测试、调试和重构。 .read真的返回字符吗?有没有更好的解析整数的方法?

  • 我没有测试过,而且由于没有任何Clojure的经验或深入了解,我有点受限,但我认为它展示了如何以“lispy”方式处理这种问题。请注意,我不考虑声明或修改变量。


    如果我正确理解了您的建议,那么我应该尝试将问题尽可能地分解成单独的函数来解决,是吗? - Hamza Yerlikaya
    1
    我不会说尽可能多,而是尽可能合理---你找出可以命名的概念,然后将它们封装在该名称后面。然而,这只是上述问题的一个小方面。 - Svante

    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进行描述性命名。
    • 读取before获取其数值。
    • after中取出这么多字符,并将它们全部混合在一起成为一个字符串,使用(apply str)实现。
    Svante的答案是使用Clojure编写循环式代码的优秀示例;我希望我的回答是展示如何组装内置函数以使其达到所需效果的良好示例。显然,这两种方案都使C语言的解决方案不再“非常简单”!

    你总是提供如此出色的功能性答案!+1 - bmillare
    决定使用流而不是字符串也使函数更具可组合性。这是一个对初学者来说非常棒的Clojure代码片段,值得学习。 - event_jr

    6

    习惯性的 Clojure 很适合处理序列。在 C 语言中,我倾向于考虑变量或多次更改变量的状态。在 Clojure 中,我会用序列来思考。在这种情况下,我将问题分解为三个抽象层:

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

    流转字节:

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

    这是惰性评估的,所以每次需要下一个字符串时,它将从输入流中获取并构造。例如,您可以在网络流上使用它,而无需首先缓冲整个流或担心读取此流的代码如何构造它。
    注:我现在不在我的REPL上,因此请编辑以修复任何错误 :)

    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方面好运!


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接