LINUX.ORG.RU

Кто хотел лисп, компилирующийся в C++?

 , ,


0

3

Нашёл на просторах Интернета: https://bitbucket.org/ktg/l/src/337c13802c5e?at=master

Умеет макросы

(define-syntax sum
  (syntax-rules ()
    [(sum) 0]
    [(sum a) a]
    [(sum a b) (+ a b)]
    [(sum a b ...) (+ a (sum b ...))]))

(define-syntax-rule (infix a op b) (op a b))

(define-syntax-rule (ret a) (return a))

(defmacro unless (pred a b)
  `(if (not ,pred) ,a ,b))

(main
  (prn (sum 1 2 3 4))
  (prn (infix 1 + 2))
  (unless false (prn "Will print") (prn "Will not print"))
  (ret 1))

Примеры смотреть в https://bitbucket.org/ktg/l/src/337c13802c5e/ex/?at=master

Хвалите и критикуйте!

★★★★★

ура! наконец-то! как мы раньше жили без этого?..

anonymous
()

вроде было уже. с тех пор нужность вряд-ли повысилась

dib2 ★★★★★
()

Забавно, но:

1. Чтобы взлетело (пусть чуть-чуть) надо чтобы популярность была «хотя бы» как у ракета.

2. Неужели кому-то нужна «компиляция» именно в С++? Вот в натив (но это уже есть ведь) и без ГЦ (лучше опционально) - другое дело.

3. Наверняка, в итоге потеряна как часть возможностей лиспа, так и плюсов?

DarkEld3r ★★★★★
()

Да, при Гитлере за такое посадили бы

esandmann
()
; (fn ("int a" "int b") (return (+ a b))) => [&](int a, int b) {return a + b;}
[(fn) (format "[&](~a) {\n~a;}" (string-join (second e) ",") (string-join (map compile-expr (drop e 2)) ";\n"))]

Так нечестно! Это должно быть тестом пригодности макросов (не пригодны).

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

Предотвращает GC от перемещения объекта, конечно.

Дык, освобождаться память всё-таки будет в произвольный момент? В С++ всё-таки дофига на «своевременный» вызов деструкторов завязано.

Или к чему был выкрик про отключаемый GC?

Это я скорее немного о другом. В D GC формально «не обязателен к использованию», но по факту, много к чему прибит гвоздями. Для «замены С++» это не всегда удобно. Впрочем, к сабжу это относится весьма опосредованно.

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 1)
(defmacro unless (pred a b)
  `(if (not ,pred) ,a ,b))

И как это работает? Всмысле если я определю какую-нибудь функцию и потом вызову ее в макросе, то что будет?

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

Дык, освобождаться память всё-таки будет в произвольный момент? В С++ всё-таки дофига на «своевременный» вызов деструкторов завязано.

ну тут или шашечки, или ехать. Тот же CL (да и любой лисп, наверное) так любит тратить память, что никаких деструкторов не напасешься. У SBCL гранулярность GC - страница, меньше он не чистит. Это позволяет нааллоцировать много мелких объектов и быстро их подчистить.

Это я к тому, что для большинства задач GC не мешает, а его отключение приведет быстро к замусориванию памяти. Хотя наверняка можно придумать задач, где GC (или его конкретные реализации) будет во вред

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

Автор явно не осилил printf.

https://bitbucket.org/ktg/l/src/337c13802c5e9a64b8cadee2440df4e87816d185/ex/e...

(include "cstdio")

(defn "int" main ()
  (for (def a 1) (< a 1000) (++ a)
    (for (def b (+ a 1)) (< b 1000) (++ b)
      (def c (- 1000 a b))
      (when (< c 1) (continue))
      (when (== (+ (* a a) (* b b)) (* c c))
        (std::printf "%d * %d * %d = %d\n" a b c (* a b c))))))
monk ★★★★★
() автор топика
Ответ на: комментарий от esandmann

Если region analysys может доказать, что gc не нужен, то почему бы и не отключить? Mlkit так умеет.

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

И как это работает? Всмысле если я определю какую-нибудь функцию и потом вызову ее в макросе, то что будет?

Если определишь как

(define-for-syntax (foo a b) ...)

то в макросе будет работать. Внутри (define-for-syntax ...) полнофункциональный Racket с GC, библиотеками и т.д.

Если ты про defn, то он определяет функции только для результирующего кода. Хотя в нормальном Racket то же самое: define-for-syntax для макросов, а define для выполняемого кода (runtime).

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

Так нечестно! Это должно быть тестом пригодности макросов

Зачем? Всё-равно должна получиться лямбда в скомпилированном коде. Можно, конечно, этот однострочник через макрос выразить, но зачем?

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

Вырази. Нельзя ведь.

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

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

1. Чтобы взлетело (пусть чуть-чуть) надо чтобы популярность была «хотя бы» как у ракета.

Там можно использовать весь Racket во время компиляции и весь C++ во время выполнения.

2. Неужели кому-то нужна «компиляция» именно в С++?

Например, надо писать на C++ (или Qt). FFI для C++ отстутсвует, значит с другими языками подружить почти никак. С другой стороны хочется нормального чистого кода без многокилометрового бойлерплейта и метапрограммирования на препроцессорое и темплейтах.

Вот есть возможность использовать вcю мощь Racket'а на стадии компиляции, но на выходе получать чистый C++ код.

3. Наверняка, в итоге потеряна как часть возможностей лиспа, так и плюсов?

От плюсов не потеряно ничего, так как L++ просто надстройка на генерацией C++ кода. На крайний случай есть команда (code ...) куда можно просто воткнуть произвольный текст для выходного файла.

С учётом возможности использовать (code ...) в макросах можно написать обёртки к любой вариации C++ (Qt, Java, etc)

От лиспа потеряна библиотека во время выполнения (так как С++ не умеет вызывать лисповые функции), но это в данном случае часть постановки задачи.

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

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

«Ты не умничай, ты пальцем покажи» (с)

(fn («int a» «int b») (return (+ a b))) как по твоему должно представляться в C++?

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

Если определишь как

А, глянул исходники, теперь понятно все. Но это все-таки костыльно. Можно было сделать так, чтобы результат компиляции был результатом исполнения 0-фазы => с-но в 1+ фазах мы имеем полный доступ ко всему racket. То есть

(define-syntax-rule (return x) (~a «return » x))

x в свою очередь тоже раскрывается в какой-то ~a и так далее.

При этом в 0-фазе (генерация си-кода) тоже доступен весь racket и можно искаробки делать хуки к компилеру.

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

std::cout в середине языковых конструкций выглядит как костыль.

В C++ есть другой стандартный способ вывести строку?

Можно, конечно не делать pr языковой конструкцией, а писать что-то вроде (<< std::cout a b c), но (pr a b c) выглядит более аккуратно.

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

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

ну если так приспичило, есть (был?) же этот smoke. только закоренелые гомосексуалисты будут использовать это ваше поделие для Qt.

но это в данном случае часть постановки задачи.

ну да, родили нежизнеспособного монстра

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

Ну в C++ так. В Си сложнее, но тоже понятно как.

Это:

int main() {
	int a = 4;
	float b = 7;
	
	auto f = [a, &b] (float c) -> float {
		return (a + b) / c;
	};
	
	cout << f(3) << endl;
	
	return 0;
}
Раскрывается в это:
struct __Lgensym {
	int a;
	float& b;
	__Lgensym(int a, float& b) 
			: a(a), b(b) {}
	
	float operator ()(float c) {
		return (a + b) / c;
	}
};

int main() {
	int a = 4;
	float b = 7;
	
	__Lgensym f(a, b);
	
	cout << f(3) << endl;
	
	return 0;
}

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

x в свою очередь тоже раскрывается в какой-то ~a и так далее.

(define-syntax-rule (foo y) (+ y 1))
(define-syntax-rule (ret x) (return (foo x)))

нормально из одного макроса вызовет другой

в 0-фазе (генерация си-кода) тоже доступен весь racket и можно искаробки делать хуки к компилеру

Так и так можно

(defmacro (create-hash name type)
  `(code ,(format "std::map<~a> ~a;" type name)))

(defmacro (orm-class name db) ; псевдокод
  (define handle (open-db db))
  `(code
    ,(format "class ~a {public: ~a};"
      (string-join 
        (for/list ([field (fields db)])
          (field-description field)) ";\n"))))

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

В Си сложнее, но тоже понятно как

Раскрывается в это

В Си нет operator ().

А в остальном, я так понимаю, претензия к тому, что макрос не может изменить уже написанный ранее код?

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

А в остальном, я так понимаю, претензия к тому, что макрос не может изменить уже написанный ранее код?

Ну, он не может банально определить тип переданной ему переменной, например (даже изменять ничего не надо).

Я уже писал в другом треде все это.

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

только нафейхоа на лор об этом постить

Когда-то (полгода назад) была высказана мысль, что в лиспе хороши макросы, но плохо то, что из-за GC нельзя компилировать в бинарный код с производительностью сравнимой с C/C++.

А тут красивый пример как сотней строк объединить маросы лиспа (Racket) и оптимизацию C++.

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

Так и так можно

Можно, но формы обрабатываются костыльно, «руками», что может теоретически привести к проблемам в некоторых местах. А можно сделать чтобы полностью обрабатывался средствами самого racket.

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

Ну, он не может банально определить тип переданной ему переменной

Без вывода типов, опять же, тривиально делается.

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

Ну, он не может банально определить тип переданной ему переменной,

#define тоже не может определить тип переменной. Да что там, в лиспе до запуска программы тип переменной в символе тоже не определён.

Или ты про литералы? Тогда может. Строку от числа и от символа (имени переменной) макрос отличает.

Если от типа что-то должно реально зависеть, можно нагенерить template'ов.

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

формы обрабатываются костыльно, «руками», что может теоретически привести к проблемам в некоторых местах. А можно сделать чтобы полностью обрабатывался средствами самого racket

В смысле, руками? Ты про конструирование списков через defmacro?

Ну можешь заменить на syntax-case, суть от этого не поменется.

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

ого! Так это я хоть щас на CL напишу код, сопоставимый с C. И GC тут не при чём совершенно. Смотря что за задачи. А сабж просто мутант для игры, не более того.

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

Ну можно и тут так же сделать через lift-expression.

Именно тут нельзя. Эти макросы не выполняются в среде Racket, а раскрываются через (expand-to-top-form (eval `(syntax ,lst) ns))

Впрочем, никто не мешает добавить экспортную переменную prologue (или как-то так) в L++ и разрешить её менять в макросах. А результат выводить после инклюдов, но до всего остального.

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

в лиспе до запуска программы тип переменной в символе тоже не определён.

В C++ определен, статически проверяется перед компиляцией.

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

я хоть щас на CL напишу код, сопоставимый с C. И GC тут не при чём совершенно.

http://benchmarksgame.alioth.debian.org/u32/performance.php?test=revcomp#about

Давай. Сможешь сделать хотя бы не более, чем на 50% медленнее, чем C++?

А то люди стараются, но на C работает 0.74 секунды, а на SBCL 2.71. про остальные и говорить нечего...

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

В C++ определен

Знаю. В принципе, задача по запоминанию, какой тип какой переменной отдал несложная. Но тебе ведь потом и другого захочется: а пусть sizeof работет, а пусть угадывает, если auto определение было. Например что-то типа «auto a = MyClass<1>::iterator_length»... и прикручивай полный парсер с учётом типов и шаблонов.

Поэтому здесь просто генератор текста программы. Без анализа семантики.

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

Зачем C++, если есть Си. Зачем адский крестопарсер, если есть s-выражения.

Но интерпретатор, я полагаю, таки понадобится.

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

какой нафиг region analysis в таком супер-пупер динамичном лиспе?

С какого лешего это он динамичный? Это ж не javascript и не педон. Совершенно статический язык, особенно если это Схема.

К тому же программа может быть многопоточная.

И с каких это пор это стало проблемой?

Посмотри на Harlan, например. Вполне себе лишп, совсем без GC, чистый region inference.

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

Зачем C++, если есть Си.

С обычным Си Racket и так замечательно дружит. Вплоть до компиляции в одном файле:

#lang planet jaymccarthy/superc

@c{
#include <stdio.h>
 
int main(void)
{
    int x = 0;
    printf("hello, world\n");
    scanf("%d", &x);
    printf("you typed: %d\n", x);
    return 1;
}
}

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

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

А вот C++ — вещь в себе. Но в нём есть (иногда незаменимые) библиотеки. А с такой обвязкой (L++) программа на треть короче. Если макросы использовать, то и бойлерплейт уйдёт (вcе эти Q_OBJECT, нпример).

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

А нужно именно в C++? Или в C тоже сгодится? Тогда давно есть ECL - полноценный Common Lisp, компилируется в C.

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

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

То подход Common Lisp: в образе всё должно иметь возможность переопределеяться и программу надо разрабатывать без останова образа. В Racket (и вроде вообще в Sсheme) среда компиляции и среда выполнения жёстко разделены.

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