Clojure vs Python vs ...?

Несколько недель назад я почти потерял всякий интерес к Clojure. И правда, что в ней может быть такого интересного? Она простая, функциональная и в ней есть транзакционная память, персистентные структуры, метаданные и удобные макросы, но я изучал её около года, и в общем, не скажу что её фичи убийственнее фич других языков. В конце концов, я скажу лишь, что это чертовски хороший лисп, на котором приятно писать многопоточные программы: это продукт эволюции, но не революции.

Я пишу эту заметку с небольшим опозданием, всего каких-то пару-тройку недель, да к тому же это еще и злостный самобоян с моей стороны, но да ладно; так вот, недавно в ленте гугл-ридера мне попалась пара постов в ЖЖ — автор показал свою реализацию игры «жизнь» на Clojure — всего 6 строк кода.

(def nbr-deltas [[-1 -1][-1 0][-1 1][0 -1][0 1][1 -1][1 0][1 1]])

(defn nbr-cells [[x y]]
  (map (fn [[a b]] [(+ x a)(+ y b)]) nbr-deltas))

(defn cell-table [cell]
  (apply conj {cell 10} (map #(vec [% 1]) (nbr-cells cell))))

(defn all-table [cells]
  (apply merge-with + (map cell-table cells)))

(defn next-gen [cells] 
  (keys (filter #(#{3 12 13} (second %)) (all-table cells))))

(defn nth-gen [n cells]
  (if (== n 0) cells (recur (- n 1) (next-gen cells))))

Я подумал «почему бы не сравнить „функциональность“ Clojure с „функциональностью“ Python?»

Ну и сравнил: не вдаваясь в подробности и особо не раздумывая на красóтами результата, передрал код один-в-один.

nbr_deltas = (-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)

def nbr_cells(c):
  return ret[(c[0] + d[0], c[1] + d[1]) for d in nbr_deltas]

def cell_table(cell):
  return dict((c, v) for c, v in [(cell, 10)] + [(nbr, 1) for nbr in nbr_cells(cell)])

<<define-function-merge-with>>

def all_table(cells):
  return merge_with(lambda x,y: x+y, [cell_table(cell) for cell in cells])

def next_gen(cells):
  return [cell for cell, val in all_table(cells).iteritems() if val in (3, 12, 13)]

def nth_gen(n, cells):
    gen = cells
    for i in range(0, n):
        gen = next_gen(gen)
    return gen

Фактически, те же самые 6 строк. Единственные отличия — в библиотеке питона нет функции merge-with для слияния словарей, и функция nth-gen реализована императивно из-за отсутствия оптимизации хвостовой рекурсии в питоне.

def merge_with(f, maps):
    result = {}
    for map in maps:
        for key, val in map.iteritems():
            if key in result:
                result[key] = f(result[key], val)
            else:
                result[key] = val
    return result

Автор Clojure-версии протестировал программу на своём ноутбуке:

Ура! Все работает. Расчет занял 12 секунд на МacBook Pro (2.53 GHz Intel Core 2 Duo, 4GB RAM), то есть около 100 поколений в секунду.

> (count (nth-gen 1103 [[0 1][1 1][2 1][1 2][2 0]]))
116

Я протестировал свою версию на своём безымянном нетбуке российской сборки, с процессором Atom N450 1.4 и 2G RAM под Jolicloud 1.0; и у меня расчёт занял те же 12 секунд.

>>> print len(nth_gen(1103, ((0, 1), (1, 1), (2, 1), (1, 2), (2, 0))))
116

P.S. Уже довольно долго у меня зреют сумбурные мысли по поводу использования goto, рекурсии, циклов, и DSL (предметно-ориентированных языков) для решения одних и тех же задач. Буквально месяц назад к этому прибавились еще и размышления о литературном программировании, но сейчас я не смогу связать всё это в что-то осмысленно-структурированное, поэтому предлагаю вам взглянуть на решение одной маленькой задачи, а именно реализации функции nth-gen в четырех парадигмах, и высказать здесь первое, что пришло в голову:

  • Рекурсия, Clojure
    (defn nth-gen [n cells] 
      (if (== n 0) 
          cells
          (recur (- n 1) (next-gen cells))))
    
  • Цикл, Python
    def nth_gen(n, cells):
        gen = cells
        for i in range(0, n):
            gen = next_gen(gen)
        return gen
    
  • DSL, Common Lisp
    (defun nth-gen (n, cells)
      (loop repeat n for gen = cells then (next-gen gen)
            finally return gen))
    
  • Литературное программирование, любой язык
    <<Получить n-ое поколение клеток применив функцию
      'next-generation' к поколению 'cells' n раз>>
    
    <<Получить n-ое поколение клеток применив функцию
      'next-generation' к поколению 'cells' n раз>>=
    Реализация этого псевдокода на чем угодно и как угодно, с
    оптимизацией, без оптимизации, рекурсией, через GOTO, на асме...
    
    Здесь мог бы быть любой из приведенных выше кусков кода.
    @