Могу ли я использовать Clojure макрос для обращения строки? - PullRequest
4 голосов
/ 07 декабря 2011

Это продолжение моего вопроса "Рекурсивная обратная последовательность в Clojure" .

Можно ли изменить последовательность с помощью макроса Clojure «for»? Я пытаюсь лучше понять ограничения и варианты использования этого макроса.

Вот код, с которого я начинаю:

((defn reverse-with-for [s] 
    (for [c s] c))

возможно?

Если это так, я предполагаю, что решение может потребовать обернуть макрос for в какое-то выражение, которое определяет изменяемый var, или что тело-выражение для макроса for каким-то образом передаст последовательность следующей итерации (аналогично 1013 *).

Ответы [ 3 ]

7 голосов
/ 07 декабря 2011
Макрос

Clojure for используется с произвольными последовательностями Clojure.

Эти последовательности могут предоставлять или не предоставлять произвольный доступ, как это делают векторы. Таким образом, в общем случае у вас нет доступа к последнему элементу последовательности Clojure, не пройдя весь путь до него, что сделало бы невозможным выполнение прохода в обратном порядке.

Я предполагаю, что вы имели в виду нечто подобное (псевдокод, подобный Java):

for(int i = n-1; i--; i<=0){
   doSomething(array[i]);
}

В этом примере мы заранее знаем размер массива n и можем получить доступ к элементам по его индексу. С последовательностями Clojure мы этого не знаем. В Java имеет смысл делать это с массивами и ArrayLists. Последовательности Clojure, однако, гораздо больше похожи на связанные списки - у вас есть элемент и ссылка на следующий.

Кстати, даже если бы существовал (возможно, не идиоматический) * способ сделать это, его временная сложность была бы чем-то вроде O (n ^ 2), который просто не стоил бы усилий по сравнению с Гораздо более простое решение в связанном посте: O (n ^ 2) для списков и намного лучшее O (n) для векторов (и это довольно элегантно и идиоматично. Фактически, официальная реализация reverse имеет такую ​​реализацию).

EDIT:

Общий совет: не пытайтесь заниматься императивным программированием в Clojure, оно не было предназначено для этого. Хотя многие вещи могут показаться странными или нелогичными (в отличие от общеизвестных идиом из императивного программирования), когда вы привыкнете к функциональному способу выполнения вещей, это намного, и я имею в виду намного проще.

Специально для этого вопроса, несмотря на одно и то же имя Java (и другие C-подобные) for и Clojure for не одно и то же! Первый - это реальный цикл - он определяет управление потоком. Второй - понимание - концептуально рассматривайте его как высшую функцию последовательности и функцию f , которую нужно выполнить для каждого ее элемента, который возвращает другую последовательность f (элемент) с. Java for - это утверждение, оно ни к чему не приводит, Clojure for (как и все остальное в Clojure) - это выражение - оно вычисляется в последовательности f (element) s. .

Вероятно, самый простой способ получить идею - поиграть с библиотекой функций последовательности: http://clojure.org/sequences. Кроме того, вы можете решить некоторые проблемы на http://www.4clojure.com/. Первые проблемы очень просты, но постепенно они усложняются по мере прохождения через них.

* Как показано в ответе Александра, решение проблемы на самом деле идиоматическое и довольно умное. Слава за это! :)

5 голосов
/ 08 декабря 2011

Вот как можно перевернуть строку с помощью for:

(defn reverse-with-for [s] 
    (apply str
        (for [i (range (dec (count s)) -1 -1)]
            (get s i))))

Обратите внимание, что этот код не содержит мутаций.Это так же, как:

(defn reverse-with-map [s] 
    (apply str
        (map (partial get s) (range (dec (count s)) -1 -1))))

Более простое решение будет:

(apply str (reverse s))
3 голосов
/ 08 декабря 2011

Прежде всего, как сказал Горан, for - это не утверждение - это выражение, а именно понимание последовательности.Он строит последовательности путем итерации по другим последовательностям.Таким образом, в той форме, в которой оно предназначено для использования, это чистая функция (без побочных эффектов).for можно рассматривать как расширенный map, насыщенный filter.Из-за этого его нельзя использовать для хранения состояния итерации, например, reduce do.

Во-вторых, вы можете выразить изменение последовательности, используя for и изменяемое состояние, например, используя атом, который является грубым эквивалентом (нес учетом его свойств параллелизма) переменной Java.Но при этом вы сталкиваетесь с несколькими проблемами:

  1. Вы нарушаете парадигму основного языка, поэтому вам определенно будет хуже выглядеть и вести себя код.
  2. Поскольку все изменяемые ячейки clojure предназначены длябыть потокобезопасными, все они используют какую-то незаконную защиту от одновременных изменений, и нет возможности удалить ее.Следовательно, вы получите худшие характеристики производительности.
  3. В этом конкретном случае, как сказал Горан, последовательности являются одной из широко используемых абстракций Clojure.Например, существуют ленивые последовательности, которые могут быть потенциально бесконечными, поэтому вы просто не можете пройти их до конца.У вас наверняка возникнут трудности при попытке работать с такими последовательностями с помощью императивных техник.

Так что не делайте этого, по крайней мере, в Clojure:)

РЕДАКТИРОВАТЬ: я забыл упомянуть об этом,for возвращает ленивую последовательность, поэтому вы должны каким-то образом ее оценить, чтобы применить все изменения состояния, которые вы делаете в ней.Еще одна причина не делать этого:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...