четверг, 14 июня 2012 г.

Простое и сложное в Common Lisp

Сегодня развлекался с моим любимым Common Lisp и обнаружил интересную деталь.

Допустим, мы хотим написать простую функцию для определения среднего арифметического набора заданных чисел. Чтобы она работала вот так:

(average 1 2 3 4)
5/2

...ну хорошо, хорошо:

(coerce (average 1 2 3 4) 'float)
2.5

Заметьте то, что я не хочу передавать в average список, я хочу кортеж чисел. Интересная деталь вот в чём: самая простая (в смысле, лаконичная) реализация, которая пришла мне в голову, это такая:

(defun average (&rest args)
  (/ (reduce #'+ args) (list-length args)))

Дело в том, что определение среднего арифметического — это задачка для средней школы, и мы вполне можем её дать в качестве задания по программированию на второй паре (на первой мы изучим весь синтаксис лиспа вместе с макросами defun и defvar). Но на самом деле мы не можем её дать, потому что у нас есть вот эта форма в решении: (reduce #'+ args), и, чтобы студенты смогли ей бегло пользоваться, нужно, чтобы они знали как минимум:

  • о разделении пространства имён переменных и пространства имён функций;
  • как передавать в функцию имя другой функции;
  • о назначении и работе reduce, что для не знакомого с функциональными методами, вообще говоря, стопроцентная магия (особенно если их уже обработали алголоподобными языками);
  • о том, что параметры, переданные через &rest, оборачиваются в список.

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

Конечно, мы можем соорудить нечто работающее из let и dotimes, но тогда зачем нам Common Lisp вообще? :)