Почему код как данные? - PullRequest
       55

Почему код как данные?

36 голосов
/ 10 ноября 2010

Что такое код как данные?Я слышал, что он превосходит "символы кода как символы", но почему?Лично я нахожу философию «код-как-данные» немного запутанной на самом деле.

Я баловался со Схемой, но никогда не понимал, что такое «код-как-данные», и задавался вопросом, что именно это означает?

Ответы [ 6 ]

60 голосов
/ 10 ноября 2010

Это означает, что код вашей программы также является данными, которыми может манипулировать программа. Возьмите простое выражение Scheme вроде

(+ 3 (* 6 7))

Вы можете рассматривать это как математическое выражение, которое при оценке дает значение. Но это также список, содержащий три элемента, а именно +, 3 и (* 6 7). цитируя список,

 '(+ 3 (* 6 7))

Вы говорите схеме, чтобы она рассматривалась как последняя, ​​а именно просто список, содержащий три элемента. Таким образом, вы можете манипулировать этим списком с помощью программы и , а затем оценивать его. Мощь, которую он дает вам, огромна, и когда вы «понимаете» идею, вам нужно сыграть несколько очень крутых трюков.

43 голосов
/ 10 ноября 2010

Код как данные на самом деле только одна сторона медали.Другой - data-as-code .

Возможность встраивать произвольные данные в код Lisp и загружать и перезагружать их на лету делает их (данные)очень удобно обрабатывать, потому что это может устранить любое потенциальное несоответствие импеданса между способом представления данных и способом работы кода.

Позвольте мне привести вам пример.

Допустим, вы хотитенаписать какую-то компьютерную игру с различными классами монстров.В основном у вас есть два варианта: моделировать классы монстров в вашем языке программирования или использовать подход, основанный на данных, когда описания классов считываются, скажем, из XML-файла.

Выполнение моделирования в языке программирования имеетПреимущества простоты использования и простоты (что всегда хорошо).Также легко указать пользовательское поведение в зависимости от класса монстра по мере необходимости.Наконец, реализация, вероятно, довольно оптимизирована.

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

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

Кстати, это не относится к Лиспу.Между Lisp и, скажем, C ++ существуют различные оттенки серого, эквивалентного кодовым данным.Например, Ruby облегчает встраивание данных в приложение, чем Python, а Python делает это проще, чем Java.И данные как код, и код как данные - это скорее континуум, чем вопрос или вопрос.

18 голосов
/ 10 ноября 2010

Как программист на Лиспе вы учитесь думать об источнике программы как о данных. Это уже не статический текст, а данные. В некоторых формах Lisp сама программа является той структурой данных, которая выполняется.

Тогда все инструменты ориентированы таким образом. Вместо текстового макропроцессора Лисп имеет систему макросов, которая работает с программами как с данными. Преобразование программ в и из текста также имеет свои инструменты.

Давайте подумаем о добавлении двух элементов вектора:

(let ((v (vector 1 2 3)))
   (+ (aref v 0)
      (aref v 1)))

В этом нет ничего необычного. Вы можете скомпилировать и запустить его.

Но вы также можете сделать это:

(let ((v (vector 1 2 3)))
   (list '+
         (list 'aref v 0)
         (list 'aref v 1)))

Возвращает список с символом плюса и двумя подсписками. Эти подсписки имеют символ aref, затем значение массива v и значение индекса.

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

EVAL затем оценивает программу как данные.

CL-USER 17 > (setf *print-circle* t)
=>  T

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

CL-USER 18 > (let ((v (vector 1 2 3)))
               (list '+
                     (list 'aref v 0)
                     (list 'aref v 1)))
=>  (+ (AREF #1=#(1 2 3) 0) (AREF #1# 1))

Теперь давайте проверим данные как программу на Лиспе:

CL-USER 19 > (EVAL (let ((v (vector 1 2 3)))
                     (list '+
                           (list 'aref v 0)
                           (list 'aref v 1))))

=>  3

Если ваш компилятор ожидает, что текст является исходным, вы можете создать эти тексты, но они никогда не смогут напрямую ссылаться на данные. Для этих текстовых исходных текстов было разработано много инструментов, но многие из них, как правило, работают поэтапно. В Лиспе функциональность манипулирования данными может быть непосредственно применена для манипулирования программами, и эта функциональность непосредственно встроена и является частью процесса оценки.

Итак, Лисп дает вам дополнительную степень свободы и новые способы мышления.

12 голосов
/ 10 ноября 2010

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

Одним из преимуществ является то, что метапрограммирование является почтитак же, как обычное программирование.При использовании символов as-ascii-символов вам часто приходится выполнять какой-то серьезный синтаксический анализ для выполнения каких-либо мета-действий, и вы пропускаете эти неприятные биты с помощью Lisp.

11 голосов
/ 10 ноября 2010

В Scheme (или любом Лиспе) вы можете объявить литералы списка следующим образом:

> '(1 2 3)
=> (1 2 3)

Это похоже на многие другие языки высокого уровня, за исключением небольших различий в нотации. Например, вот как некоторые другие языки представляют литералы списка:

[1, 2, 3] # Python
#(1 2 3) "Smalltalk. This is in fact an array in Smalltalk. Let us ignore that for now."

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

> '(+ 2 3)
=> (+ 2 3)

Одиночная кавычка (') идентифицирует литерал списка. (Так же, как # в Smalltalk). Что будет, если мы удалим цитату? Тогда интерпретатор Scheme обработает этот список специально. Первый элемент будет рассматриваться как функция (или процедура), а остальные элементы - как аргументы этой функции. Функция выполняется (или оценивается):

> (+ 2 3)
=> 5

Возможность представления исполняемого кода с использованием структуры данных на языке открывает новую возможность - мы можем писать программы, которые пишут программы. Это означает, что расширения, которые требуют изменений в компиляторе и системе времени выполнения в других языках, могут быть реализованы в Лиспе, как несколько строк самого Лиспа. Представьте, что вам нужна новая структура управления на вашем языке под названием when. Это похоже на if, но в некоторых ситуациях чтение кода становится немного более естественным:

 (when this-is-true do-this)

Вы можете расширить свою систему Lisp для поддержки when, написав короткий макрос:

 (defmacro when (condition &rest body)
    `(if ,condition (progn ,@body)))

Макрос - это не что иное, как список, который расширяется во время компиляции. С помощью таких списков к базовому языку можно добавить более сложные языковые структуры или даже целые парадигмы. Например, CLOS , объектные системы Common Lisp - это, по сути, набор макросов, написанных на самом Common Lisp.

7 голосов
/ 10 ноября 2010

Если вы не используете что-то вроде старого Harvard Mark I , ваш код хранится в том же месте и в том же порядке, что и ваши данные - просто (как вы заметили), вероятнов форме символов ASCII, так что с этим действительно сложно что-либо делать.Скорее всего, большинство Java-программистов никогда не анализировали Java-код самостоятельно.

Посмотрите на любую программу - в самом исходном коде закодировано огромное количество информации (ну, в зависимости от программы!).Это его причина для существования!Не используя гомоиконический язык, вы неявно говорите, что у вас все в порядке, если вы не можете прочитать это из другой программы, которую вы пишете (или что все в порядке, что это так сложно, что никто этого не сделает).По сути, единственная программа на вашем компьютере, которая может прочитать его, - это компилятор, и единственное, что он может делать после чтения, - генерировать объектный код и сообщения об ошибках.

Представьте, что вам приходилось работать с каким-то другим источником данных каждый день.как XML-файлы или СУБД, и единственный способ получить доступ к этим данным - это запустить их через «компилятор», который преобразует их в формат, который вы можете прочитать.Я не думаю, что кто-то будет утверждать, что это хорошая идея.: -)

Я действительно не знаю, куда я иду с этим, поэтому я попытаюсь обобщить мои вышеупомянутые рассуждения:

  • Я вижу код как данныеВ качестве логического следующего шага от Гарвардской архитектуры к Архитектуре фон Неймана
  • у нас уже есть X-as-данные практически для каждого другого X, поэтому кажется странным исключить один вид данных, которые программисты тратят весь деньманипулируя
...