2010-10-03 3 views
7

я увидел следующий пример в видео Рича на последовательностях http://blip.tv/file/734409 около 33-36 минут в него:Об исполнении `first` функции Clojure в

(first "abcd") => \a 

Теперь он говорит, что это расширяется (вроде):

(first "abcd") => (first (seq "abcd")) => (first '(\a \b \c \d)) 

Таким образом, это выглядит как O(N) операции, так как полная копия строки делается. Прежде всего, если значение String является неизменным, то почему оно копируется? (Редактирование: на основе ответа, вероятно, это не так, просто выглядели так, когда печатались.) Во-вторых, предположим, что first работает на чем-то другом в Java, который изменен, скажем, связанный список целых чисел. Должно ли first действовать ленивым способом (например, сначала создать постоянную последовательность)? Разве это не имеет смысла оценивать это сразу и сохранять его? Это был бы какой-то хак, который сломал бы хорошую абстракцию, но, я думаю, быстро проделайте эту работу. Когда вы вызываете (seq "abcd"), вы не знаете, как он будет использоваться. Когда вы вызываете first на seq, вы знаете, что делать. Но, когда вы вызываете first на "abcd", я думаю, что выполнение хакерских и быстрых «захватов и сохранения», подход лучше, чем захватить последовательность, а затем позвонить first.

Я что-то упустил? Разве Рик Хики пропустил несколько шагов?

Дайте мне знать, если у меня есть вопросы. Благодаря!

ответ

4

Другие ответы верны, но я воспользуюсь этим шансом, чтобы указать на интересный эффект философии неизменности Clojure.

Таким образом, это выглядит как операция O (N), потому что выполняется полная копия строки.

Строки, как и другие структуры данных в Clojure, неизменяемы. (Это особый случай, который реализуется в JVM, а не в Clojure, но это несущественно.)

Копии неизменяемых объектов: . Правильно, бесплатно.Потому что вам совсем не нужно копировать. Тот же выделенный объект в памяти можно просто использовать повторно, потому что он всегда будет таким же, как когда он был «скопирован».

Так что функция, подобная seqникогда должна фактически что-то копировать. Он просто работает над тем, что передается напрямую, и возвращает (возможно, ленивую) последовательность, которая предоставляет абстрактный интерфейс к тому, что вы назовете seq.

А так, seq всегда O (1).

+0

Спасибо, какой-нибудь дополнительный ввод при работе с изменяемыми объектами Java, которые можно превратить в последовательность? –

+1

IIRC, вызывающий seq возвращает ленивую последовательность Clojure, поддерживаемую итератором Java по коллекции. Так как ленивая последовательность реализуется, она должна следовать всем правилам, которые относятся к итераторам (насколько мутирует базовый объект). Однако, когда это было реализовано, это неизменная последовательность Clojure. – levand

+0

@Hamish - если вы хотите превратить изменяемый объект Java в последовательность, тогда вам нужно либо сделать защитную копию с стоимостью O (n), либо риск того, что базовые данные могут быть изменены (что нарушит нормальные ожидания seq поведение и, возможно, вызывают тонкие ошибки). Первый подход (копирование), вероятно, предпочтительнее, последний очень опасен, если вы абсолютно не уверены, что ничего не изменится в неподходящий момент. – mikera

6

Из этого не вытекает полная копия строки. (Но это хороший вопрос.)

В источнике для clojure.lang.RT вы заметите, что среда выполнения использует CharSequence для создания последовательности:

static ISeq seqFrom(Object coll){ 
    if(coll instanceof Seqable) 
     return ((Seqable) coll).seq(); 
    else if(coll == null) 
     return null; 
    else if(coll instanceof Iterable) 
     return IteratorSeq.create(((Iterable) coll).iterator()); 
    else if(coll.getClass().isArray()) 
     return ArraySeq.createFromObject(coll); 
    else if(coll instanceof CharSequence) 
     return StringSeq.create((CharSequence) coll); 
      . 
      . 
      . 

Так на самом деле, это ява вопрос, не clojure вопрос. Я не проверял, но я уверен, что CharSequence делает «правильную вещь».

+0

Спасибо за ответ, Роб. Предположим, что 'CharSequence' делает правильные вещи, потому что это возможно, потому что' String' является неизменным. Теперь, если бы у меня был мутируемый связанный список из целых 999 целых чисел, определенных в Java-коде, и я хотел захватить только первый элемент. Могу ли я сделать лучше, чем сначала копировать 999 предметов, или это штраф, который я должен был бы заплатить за то, что делал «не-clojure»? Например. Я должен был сделать класс, который может содержать эти неизменные? Мне кажется, что бывают случаи, когда несовершенная Java-библиотека должна использоваться из Clojure. Я понимаю, что первый элемент может быть большим .. –

+0

(продолжение), поэтому вызов 'first' может занять некоторое время - скажем, например, мы сначала вызываем измененный связанный список измененных связанных списков. Я понимаю, почему мы хотели бы превратить самый первый внутренний измененный связанный список в постоянную последовательность посредством копирования, но зачем делать то же самое с «отдыхом» этого, с вещами, которые никогда не будут рассматриваться в данном конкретном случае ? –

+2

Нет необходимости копировать 999 элементов в seq, так как seq просто использует базовую структуру данных по мере необходимости, а по соображениям производительности и неизменности кэширует то, что находит. Если вы вызываете 'first' в какой-либо коллекции (например, ваш связанный список), то единственным элементом этой коллекции, которую должен найти seq, является первый. Все еще незаметные элементы базовой коллекции могут быть изменены без уведомления. Только когда seq на самом деле читает элемент, этот элемент будет «постоянным» в seq. –

3

Вы можете посмотреть на seq, как если бы он создавал ленивую последовательность на основе строки.

Это фактически не создает ленивую последовательность, это на самом деле короткие замыкания на Java-реализацию CharSequence, но это деталь реализации.

С точки зрения сложности, first является O(1) на seq, и вы можете создать seq постоянное время от чего-либо итерацию.

Смежные вопросы