LINUX.ORG.RU

Метапрограммирование и лисп

 , , , ,


1

5

Почему-то лисп весьма прочно ассоциируется с сабжем, при этом метапрограммирование тут трактуется весьма однобоко: как генерация программ, их исходников.

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

Во-вторых, обычно умалчивается, что метапрограммирование — это не только, и даже не столько генерация, сколько возможности интроспекции, рефлексии и самомодификации/самогенерации в рантайме. А как обстоят дела с этими, наиболее мощными фишками меты, в этих ваших лиспах?



Последнее исправление: avtoritetniy-expert (всего исправлений: 2)

Ответ на: комментарий от esandmann

Это всё хорошо при интерактивной разработке. А сколько программ ты назовешь, где это происходит при нормальном выполнении программы? Поэтому я считаю,что всё это фигня, как и выкрики «ололо, у нас код как данные».

Ну надо же! Наши позиции совпадают в кои-то веки. Я согласен с этим на все 100.

anonimous
()
Ответ на: комментарий от esandmann

сколько возможности интроспекции, рефлексии и самомодификации/самогенерации в рантайме

ТС предъявил предъяву, что в лиспе этого всего мол нет. Ему намекнули что оно таки есть, но почти не нужно т.к. есть нормальные макры. Теперь ты скандируешь «Не нужно! Не нужно! Код как данные не нужен! Лишп не нужен!».

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Лишп не нужен!

Господин, вам нужно к окулисту!

т.к. есть нормальные макры

Ололо! И причём тут интроспекция, рефлексия и самомодификация в рантайме?

Я как раз против господ выступаю, которые считают макросы чем-то божественным и уникальным

esandmann
()
Ответ на: комментарий от esandmann

В CL совсем хорошо: можно в любой момент создать/изменить/удалить тип, класс, переменную, функцию, символ, пакет.

Это всё хорошо при интерактивной разработке. А сколько программ ты назовешь, где это происходит при нормальном выполнении программы? Поэтому я считаю,что всё это фигня, как и выкрики «ололо, у нас код как данные»

«А сколько программ ты назовешь, где это происходит при нормальном выполнении программы?» относится к «можно в любой момент создать/изменить/удалить тип, класс, переменную, функцию, символ, пакет»

Поэтому я считаю,что всё это фигня, как и выкрики «ололо, у нас код как данные»

Из заявления о ненужности рантаймомагии, ты делаешь вывод о ненужности «код как данные».

Я хотел намекнуть, что «код как данные» нужен для макросов, которые нужны. А не только для «интроспекции, рефлексии и самомодификации в рантайме».

Я как раз против господ выступаю, которые считают макросы чем-то божественным и уникальным

И что же останется от лиспа такого уникального, если убрать макросы?

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Я хотел намекнуть, что «код как данные» нужен для макросов, которые нужны. А не только для «интроспекции, рефлексии и самомодификации в рантайме».

Макросы лиспа тут на «выгодном положении» в том плане, что они преобразуют s-выражения в s-выражения, которые потом скомпиляются в код. Но никто не мешает делать код на C по каким-нибудь шаблонам с помощью специального препроцессора. Ничего в этом нет невозможного, просто в лиспе это особенно легко.

И что же останется от лиспа такого уникального, если убрать макросы?

Ну в CL, например, CLOS, хорошо интегрированный в систему типов, REPL, ещё можно что-нибудь припомнить. Может щас уже и не уникально, но это основные фичи

esandmann
()
Ответ на: комментарий от esandmann

А сколько программ ты назовешь, где это происходит при нормальном выполнении программы

В основном биндинги к базам данных и, напрмиер, GTK.

Подключился с БД — получил типы, соответствующие базе. Отключился — всё собрал сборщиком мусора.

Ну и Erlang-style. Работает какой-то длительный процесс и его можно поправить (обновить версию) без остановки. Эдакий kexec.

Ну и реально эта возможность помогает при отладке. Или в длительных вычислениях, где не сразу очевидно, какие краевые условия учесть (вычислительная математика и физика). Наткнулся на расхождение — поменял алгоритм запустил с последней успешной итерации.

monk ★★★★★
()
Ответ на: комментарий от monk

GTK

Я вот не в курсе уже, что там с этим сейчас. В cl-gtk2, который я видел, все эти типы и классы вроде были в исходниках (может и не вручную туда внесены, я хз, но не суть). Интеграция с CLOS там была хорошая, конечно.

А то, что ты говоришь, по идее можно сделать через gobject introspection, но я хз, делал ли кто-то это или нет.

esandmann
()
Ответ на: комментарий от esandmann

И что же останется от лиспа такого уникального, если убрать макросы?

В Racket макросы не любят. Без макросов основные фичи: лямбда-функции, замыкания, продолжения. На них строится всё остальное: ООП с traits и mixins, обработка исключений c возможностью продолжения прерванной программы, сопрограммы.

В CL ещё можно вспомнить special переменные и сигнальный протокол.

monk ★★★★★
()
Ответ на: комментарий от esandmann

по идее можно сделать через gobject introspection

Так вот через него и делаем https://github.com/andy128k/cl-gobject-introspection. Только пока объекты самопальные на лямбдах, но к следующей версии скорее всего сделаем on-demand создание CLOS классов.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 2)
Ответ на: комментарий от monk

Ух ты, супер! С CLOS-ом было бы воще супер-пупер. А то cl-gtk2 уже совсем протух

esandmann
()
Ответ на: комментарий от esandmann

Но никто не мешает делать код на C по каким-нибудь шаблонам с помощью специального препроцессора. Ничего в этом нет невозможного, просто в лиспе это особенно легко.

Как? В случаи с Си нужно будет определить все конструкции языка с помощью типов и написать интерпретатор. Макрос, соответственно, это функция, которая принимает конструкцию языка и возвращает ее же. Еще нужны магические операторы: преобразовывающие код в данные (разных типов) и данные (разных типов) в код.

Псевдокод:


struct statment { // выражение разделенное ';'
   /* ... */
};

statement macro(statment code) {
    statment newStatment;
    /* обычный Си код, что-то там делает с newStatment */

    return newStatment;
}

int main() {
    statment_to_code(macro(code_to_statment(int a = 5))) ; 
    /* *_to_code раскрывается во время компиляции. */
     
    return 0;
}

Выглядит это не юзабельно, оно будет выглядить лучше, если *_to_code и code_to_* будут вызываться неявно (как тогда отделить compile time вызовы от run time вызовов?). Но все равно, как тут изменять синтаксис я не знаю.

Вариант с «код - строка», мне кажется крайне сложным для использования. В случае с си - синтаксис будет сильно мешать.

Ничего в этом нет невозможного, просто в лиспе это особенно легко.

Жизненно важно легко.

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

интерпретатор

Транслятор.

как тогда отделить compile time вызовы от run time вызовов?

Ну а как обычный препроцессор определяет?

Ну макросы и макросы - хрен бы с ними. ТС'а-то интересует совершенно другое. А на деле, «метапрограммированием» на лиспе местные теоретики называют несчастные макросы. Почти всегда это так

esandmann
()
Ответ на: комментарий от esandmann

Транслятор.

Как ты собрался macro(code_to_statment(int a = 5)) без интерпретатора выполнять? Можно конечно: генерация программы => компиляция => выполнение.

Ну а как обычный препроцессор определяет?

Никак. Он с текстом программы работает, а не с данными.

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Вариант с «код - строка», мне кажется крайне сложным для использования. В случае с си - синтаксис будет сильно мешать.

На tcl «код - строка» успешно работает.

А вообще есть SuperC:

#lang planet jaymccarthy/superc

@c{
 #include <stdio.h>
 #include <math.h>
}

(define-for-syntax y 6)

@c{
int main(void) {
 printf("The Scheme program contains: @|y|\n");
 return cos(M_PI);
}
}

(define main (get-ffi-obj-from-this 'main (_fun -> _int)))

(printf "The C program returned: ~a~n"
        (main))

Вся мощь макросов Scheme и производительность C

monk ★★★★★
()
Ответ на: комментарий от monk

http://planet.racket-lang.org/package-source/jaymccarthy/superc.plt/2/0/

Ты про это? Оно точно так же не сможет определить тип переменной, например.

Есть еще всякие скобчатые Си: http://www.cliki.net/s-exp syntax

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

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Оно точно так же не сможет определить тип переменной

Если нужен макрос, зависящий от типа переменной, то можно сделать макрос, определяющий переменную. То есть вместо

@c{
int i;
char *s;
}
будет
(ctype int i)
(ctype (ptr char) s)

Хотя таким образом просто приходим к типизированному лиспу, компилирующемуся в С.

monk ★★★★★
()
Ответ на: комментарий от monk

Как?

(ctype int i)
(ctype char s)

(macro-print-type i)
(macro-print-type s)
(macro-print-type 3.14)

Как сделать macro-print-type, раскрывающийся в printf-ы? Нужен typeof, макросом его не сделать => нужна специальная магия. А если макрос принимает структуру и ему нужна информация о том, какие поля есть в структуре? Нужен fields возвращающий... список... кортежей типов и имен? То есть куча дополнительного compile-time инструментария, при этом весь этот инструментарий будет заниматься превращением декларативных частей Си в s-выражения (которых в рантайме нет).

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Как?

(define-for-syntax types (make-hash))

(define-syntax-rule (ctype type var)
   (begin-for-syntax
      (hash-set! types 'var 'type))
   @c{@type @var;})

(define-syntax-rule (macro-print-type var)   
   @c{printf("%s\n", "@(begin-for-syntax (hash-ref types 'var))")})

Для литералов действительно понадобится немножко магии — проверка того, что var — не символ и ручной (через case) выбор правильного типа С.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 2)
Ответ на: комментарий от monk

Ты не учел область видимости. С областью видимости, для того что бы это работало, нужно неявно передавать в макрос окружение (с доступными типами, доступными константами и типами переменных) и возможно тип окружения (если нужно что бы одни и те же макросы работали, например в телах функций и в телах структур). Или для каждого блока { /* ... */ }, генерить уникальный символ и заносить его в дерево с хеш-таблицами в узлах. Вообщем заменять/парсить декларативные части все равно придется.

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Вообщем заменять/парсить декларативные части все равно придется.

Или блок должен соответсвовать блоку Scheme. Тогда биндинги разрулятся так же как в Си.

Вообщем заменять/парсить декларативные части все равно придется.

Ну так я и говорю, что для нормальной интроспекции придётся делать почти Си-со-скобками. Ну или (что то же самое) лисп с типами...

И получится что-то вроде https://github.com/tonyg/pi-nothing

(define (newline)
  (%%write 1 (data #"\n") 1))

(define (strlen p)
  (let ((mutable p p)
	(mutable count 0)
	(mutable ch (?byte p)))
    (while (<> ch 0)
      (set! count (+ count 1))
      (set! p (+ p 1))
      (set! ch (?byte p)))
    count))

(define (puts s)
  (%%write 1 s (strlen s)))

(define (buf)
  (data #"aa\0"))

(define (main)
  (newline)
  (puts (data #"Hello, world!\n\0"))
  (puts (buf))
  (newline)
  (let ((addr (+ (buf) 1)))
    (!byte addr (+ 1 (?byte addr))))
  (puts (buf))
  (newline)
  0)

Это компилируется в ассемблер.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

Ну так я и говорю, что для нормальной интроспекции придётся делать почти Си-со-скобками.

Получится что runtime-язык и compiletime-язык - два разных языка (почти с++, лол). А вот тут, все конструкции, кроме *_to_code доступны в рантайме, в том числе «макросы», которые просто Си функции без доступа к не константному scope-у (constexpr-функции из C++ или pure-функции из D).

Если очень нужны s-выражения (для изменяемого синтаксиса нужны), то их можно определить через структуры Си. А code_to_sexpr будет аналогом цитирования из лиспа. При таком подходе не будет два разных языка, просто *_to_code будут форсировать вычисления в compile-time.

Kuzy ★★★
()
Ответ на: комментарий от monk

Это компилируется в ассемблер.

Раскрытием макросов? Вроде нет же? Не могу в racket.

(define (compile-toplevel form global-env)
  (match form
    [`(define (,proc ,argname ...)
,body ...)
     (write `(compiling ,proc ...)) (newline)
     (define-values (code data) (compile-procedure md argname `(begin ,@body) global-env))
     (values (cons (label-anchor proc) code) data)]
    [`(struct ,_ ...)	(values '() '())]
    [`(const ,_ ...)	(values '() '())]
    [_
     (error 'compile-toplevel "Cannot compile toplevel form: ~v" form)]))
Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Раскрытием макросов? Вроде нет же?

Это просто функция. читает s-exp (define ...) --- вызывает compile-procedure с телом дефайна + запоминает адрес.

monk ★★★★★
()
Ответ на: комментарий от Kuzy

в том числе «макросы», которые просто Си функции без доступа к не константному scope-у (constexpr-функции из C++ или pure-функции из D).

так весь смысле лиспа в том, что во время компиляции есть свой scope (в CL тот же, что и runtime, в Racket — независимый). И, соответственно, во время компиляции можно хранить метаинформацию. Как в моём примере: тут запомнили тип, далее по тексту его прочитали и подставили.

Для C можно было бы сделать на уровне языка аналог (begin-for-syntax ...).

@syntax{
   map(char*, char*, strcmp) types;
   char* ctype(char* type, char *var) {
      char *res = malloc(strlen(type) + strlen(var) + 3);
      sprintf(res, "%s %s;" type var);
      set_map(var, type);
      return res;
   }
   char *my_type_of(char *var) {
      return get_map(var);
   }

Соответственно компиляция двухпроходная: сначала компилируется @syntax, затем остальной текст обрабатывается подстановкой macroname(...) => результат выполнения macroname. Затем компилируется остальное.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)

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

Принципиальная разница есть. Одно дело, когда для метапрограммирования есть специальные инструменты в языке (в случае, лиспа - это обычные языковые конструкции) и совсем другое, если взять какой-нибудь С, когда ты через принтф «генеришь» текст на другом языке (ты ведь это подразумевал?).

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

В общем, разница в удобстве и простоте.

Я не лиспер, если что.

интроспекции, рефлексии и самомодификации/самогенерации в рантайме

Вроде, всё есть.

DarkEld3r ★★★★★
()
Ответ на: комментарий от esandmann

Ты ещё в теги добавь «sbcl» «clisp» «common lisp», «scheme», «racket», чтобы сбежалось больше экспертов

Зачем много экспертов? Достаточно одого авторитетного.

naryl ★★★★★
()
Ответ на: комментарий от monk

Что-то я не понял. Тогда получится что в @syntax не доступны обычные функции (объявленные не в @syntax)? Откуда тогда внутри всякие sprintf-ы?


//runtime 
int fact(int n) {
  return n == 0 ? 1 : fact(n - 1);
}

@syntax {
  //compile-time
  int staticFact(int n) {
    // не может ничего знать о fact, 
    // т.к. компилируется раньше.
    return fact(n);
  }
}

int main() {
  int t = @staticFact(5);
  
  printf("%d\n", t);
  return 0;
}
Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Тогда получится что в @syntax не доступны обычные функции

Доступны все, кроме тех, что компилируются в текущем файле.

// не может ничего знать о fact, 
    // т.к. компилируется раньше.

Здесь всё верно

monk ★★★★★
()
Ответ на: комментарий от esandmann

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

anonymous
()
Ответ на: комментарий от esandmann

Макросы лиспа тут на «выгодном положении» в том плане, что они преобразуют s-выражения в s-выражения, которые потом скомпиляются в код. Но никто не мешает делать код на C по каким-нибудь шаблонам с помощью специального препроцессора. Ничего в этом нет невозможного, просто в лиспе это особенно легко.

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

anonymous
()
Ответ на: комментарий от Kuzy

Ты не учел область видимости.

Надо просто юзать id-table вместо обычного хеша и все автоматом разрулится. Или в данном случае вместо хеша лучше переменную struct-макросом и получать значение через syntax-local-value, т.к. многократные объявления в одном контексте невозможны.

anonymous
()
Ответ на: комментарий от anonymous

Я знаю, можете не объяснять. Только ТС'у явно нужны более мощные средства

esandmann
()
Ответ на: комментарий от anonymous

Я понимаю, я хотел подчеркнуть тот факт, что с таким подходом compile-time язык и runtime язык будут различаться на столько сильно, что использовать на прямую конструкции runtime языка в compile-time языке не получится. Только написав на compile-time языке интерпретатор runtime языка. По этому мне кажется логичнее использовать в макросах тот же язык что и в runtime-е.

Kuzy ★★★
()
Ответ на: комментарий от esandmann

Нет, я пытаюсь понять что вы понимаете под баззвордами.

Вы считаете, что много кричат о структурах данных? О тексте? Ну да, много. О них «кричат» все программисты с тех пор как программы начали писать текстом, а не перепаиванием проводов.

naryl ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.