Переменные Clojure и циклы - PullRequest
7 голосов
/ 28 июня 2009

Поискав, я обнаружил, что использование while циклов или использование переменных не рекомендуется.

Теперь я реализовал очень простой алгоритм, который будет считывать символы из входного потока и соответственно анализировать: если input равен 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 голосов
/ 28 июня 2009

То, что вы пишете, - это код C с похожим на lisp синтаксисом (без обид). Определение стиля тем, что вы не делаете , очень важно, но не очень полезно, если вы не знаете «хорошо, тогда как еще?»

Кстати, я не знаю, что должен делать indicator.

Вот как я бы подошел к этой проблеме:

  1. Задача состоит из двух частей: найдите количество символов для чтения, а затем прочитайте столько символов. Поэтому я написал бы две функции: read-count и read-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 обычно лучше всего обрабатывается с использованием loop и recur. loop также связывает переменные, как let. acc предназначен для накопления прочитанных элементов, но обратите внимание, что он не изменяется на месте, а перепривязывает каждую итерацию.

    (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, когда count равно 0. (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, отладке, рефакторинге. .read действительно возвращает символы? Есть ли лучший способ разобрать целое число?

Я не проверял это, и мне немного мешает то, что у меня нет ни опыта, ни глубоких знаний о Clojure (в основном я использую Common Lisp), но я думаю, что он показывает, как подходить к такого рода проблемам в «недоверчивом» " путь. Обратите внимание, что я не думаю об объявлении или изменении переменных.

10 голосов
/ 04 сентября 2011

Я полагаю, что немного поздно для этой стороны, но проблема гораздо проще, если вы просто обрабатываете строку как последовательность символов и используете примитивы обработки последовательности 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")

Резюме:

  • Преобразуйте уродливый поток с побочными эффектами в ленивую последовательность символов, чтобы мы могли притвориться, что это просто строка для остальной части этой операции. Как видите, из потока не читается больше символов, чем необходимо для вычисления результата.
  • Разделить строку на две части: первая половина символов перед первым двоеточием, а вторая половина - все, что осталось.
  • Используйте деструктурирование, чтобы связать эти части с местными жителями с именами before и after, и выделите :, пока мы на нем, связав его с неиспользуемым локальным именем colon для наглядности. *
  • Считайте before, чтобы получить его числовое значение
  • Возьмите столько символов из after и объедините их вместе в строку с (apply str)

Ответ Сванте - отличный пример того, как написать код зацикливания с помощью Clojure; Я надеюсь, что мой хороший пример сборки встроенных функций, чтобы они делали то, что вам нужно. Конечно, оба из них заставляют решение C выглядеть совсем не «очень просто»!

6 голосов
/ 02 февраля 2010

Idomatic Clojure действительно позволяет работать с последовательностями. В Си я склонен думать в терминах переменных или изменения состояния переменной несколько раз. В 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))

символы в строки [символы]

(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 голосов
/ 28 июня 2009

Я изучаю Clojure сам, поэтому примите это не как совет гуру, а как совет сокурсников.

Clojure - это функциональный язык программирования. Функциональное программирование означает отсутствие циклов, переменных и побочных эффектов. Если вы когда-либо отклоняетесь от этих трех правил, вам нужны очень веские причины для этого, и веские причины встречаются довольно редко.

Вы, очевидно, очень опытный программист, поэтому взгляните на эту информацию и, надеюсь, вы должны получить более полное представление о том, чем функциональный дизайн отличается от объектно-ориентированного дизайна.

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

Кроме того, я бы порекомендовал взглянуть на некоторый код clojure, вот пример программы, размещенной на github.com, который был написан, чтобы быть частью учебника по скрытным экранам.

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

Скринкаст-учебник, для которого предназначен код, можно найти здесь, но он не бесплатный:

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

(В любом случае я не связан с peepcode.com).

Удачи с Clojure!

...