LINUX.ORG.RU

Реализация рестартов из Common Lisp в C++

 ,


1

3

https://github.com/BerserkerTroll/restart-pp

Пример использования:


#include "restartable.h"
#include <iostream>
#include <cmath>
using namespace std;


struct bad_my_sqrt_parameter { double param; }; // condition

struct my_sqrt_use_value { double val; }; // restart
struct my_sqrt_recalculate { double val; }; // restart


double my_sqrt(double d)
{
	if (d < 0)
		restartable {
			signal_condition bad_my_sqrt_parameter{ d };
		} restart_case (my_sqrt_use_value& v) {
			return v.val;
		} restart_case (my_sqrt_recalculate& v) {
			return my_sqrt(v.val);
		};

	return sqrt(d);
}


int main()
{
	with_handlers {
		cout << my_sqrt(+4) << endl;
		cout << my_sqrt(-4) << endl;
	} handler_bind (bad_my_sqrt_parameter&) {
		invoke_restart my_sqrt_use_value{ -1 };
	};

	with_handlers {
		cout << my_sqrt(+4) << endl;
		cout << my_sqrt(-4) << endl;
	} handler_bind (bad_my_sqrt_parameter& p) {
		invoke_restart my_sqrt_recalculate { -p.param };
	};
}

А можно для быдла разжевать что делает сей код?

pon4ik ★★★★★ ()

Не люблю такие дефайны: они создают ложную уверенность, что после with_handlers/handler_bind стоит составной оператор (или любой), а грамматика не требует точки с запятой после составного оператора. Явная реализация такого вида была бы на порядок лучше, универсальнее, читабельнее и понятнее:

WithHandlers([&]() { cout << ... ;})
    .HandlerBind([](bad_my_sqrt_parameter&) { return my_sqrt_recalculate(-p.param); })
    .Run();

vzzo ★★★ ()

И зачем это простым смертным?

FIL ★★★★ ()

Хотел сам притащить на ЛОР, но решил, что не хочу деанона, поэтому рекламировал только на reddit-е.

Уже обновил реализацию и пример. Теперь никаких std::function, теперь прямо храним ссылки на лямбды и memcpy-руем их, во имя UB.

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

Тогда уж

WithHandlers([&]() { cout << ... ;})
    .HandlerBind<bad_my_sqrt_parameter>([](auto& p) { return my_sqrt_recalculate(-p.param); })
    .Run();
т.к. из лямбды тип аргумента хрен вытащишь.

Я думал сперва так делать, но потом, вдохновившись докладом «Declarative Control Flow» Александреску решил делать макросами. Да, от точки с запятой не избавиться, но если её забудешь, компиляторы об этом очень аккуратно умеют напоминать, что мол нет точки с запятой после лямбды. Никаких простыней ошибок.

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

Но кому и зачем нужно делать CL из Си++?

извращенцам

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

т.к. из лямбды тип аргумента хрен вытащишь.

Если писать вот так, то тип аргумента лямбды вытаскивается. Это не так сложно, как оказалось.

WithHandlers([&]() { cout << ... ;})
    .HandlerBind([](bad_my_sqrt_parameter& p) { return my_sqrt_recalculate(-p.param); })
    .Run();

eao197 ★★★★★ ()

Осталось придумать, как красиво multiple dispatch прикрутить и можно алгоритмы с CL на C++ переносить не извращаясь с паттерном Visitor.

monk ★★★★★ ()

Вот это годно, хотя мне и не нужны лисп и его приблуды но поставлю лайку. И сделано красиво.

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

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

(defmethod collide ((x asteroid) (y spaceship))
  ;;астероид сталкивается с космическим кораблем
  )

(defmethod collide ((x spaceship) (y asteroid))
  ;;космический корабль сталкивается с астероидом
  )
альтернатива собычным полиморфизмом
asteroid.collide = method(spaceship){
 //астероид сталкивается с космическим кораблем
}
spaceship.collide = method(asteroid){
 //космический корабль сталкивается с астероидом
}
Ну, и в чем прикол?

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

теперь прямо храним ссылки на лямбды и memcpy-руем их, во имя UB

Какой кошмар, хорошо что меня уберегло от изучения CL.

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

Ну, и в чем прикол?

прикол в твоём куцем наборе asteroid -> spaceship и spaceship ->asteroid

распиши для «пар» asteroid -> asteroid, spaceship -> spaceship, asteroid -> spaceship, spaceship ->asteroid.

Можешь еще набор расширить планетой или звездой :)

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

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

 asteroid.collide = spaceShip.collide = method(arg1, arg2, /*code*/)

Не будет никакой разницы. Даже в некоторых случаях будет короче и выразительней

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

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

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

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

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

Ну как не прибавит? Вот у меня есть (виртуально :)) один defgeneric и 4 (или 9 для 3-х типов) defmethod-ов. И я и близко не могу представить количество аналогичного кода на языке, не поддерживающем мультидиспетчеризацию (про visitor-а не вспоминать)

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

Вот у меня есть (виртуально :)) один defgeneric и 4 (или 9 для 3-х типов) defmethod-ов. И я и близко не могу представить количество аналогичного кода на языке, не поддерживающем мультидиспетчеризацию (про visitor-а не вспоминать)

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

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

ну вот все visitor-ы можно переписать мультиметодами не создавая лишних классов. Ни когда не использовал этот «паттерн»?

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

... Допустим, надо сконфигурировать поведение двух объектов, в отношении друг друга.

(defmethod collide ((x asteroid) (y spaceship))
  ;;астероид сталкивается с космическим кораблем
  )

(defmethod collide ((x spaceship) (y asteroid))
  ;;космический корабль сталкивается с астероидом
  )

В лиспе одно и тоже не надо повторять дважды ;)

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

К C++ можно прикрутить что угодно, правда, после этого он будет тормозить так же, как CL. Так что нафиг нам такое счастье.

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

Не понял куда и что протаскивать.

Для определения типа аргумента лямбды используется тот факт, что ламбда — это экземпляр некого типа с operator(), через это и можно шаблонами определить тип аргумента.

На stackoverflow есть описание этого способа за авторством Энтони Уильямса, если не ошибаюсь.

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

Для определения типа аргумента лямбды используется тот факт, что ламбда — это экземпляр некого типа с operator(), через это и можно шаблонами определить тип аргумента.

Кагбэ я об этом и написал. Не тормози.

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

на Хабре

Не, Хабр — это зашквар.

на https://www.reddit.com/r/cpp

Там уже заминусовали с комментариями «ЯННП» и «что такое Лисп?».

https://news.ycombinator.com/

Насколько там имеет смысл? Я понимаю, что лисп там любят, но это всё же код на плюсах.

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

Не, Хабр — это зашквар.

Тем не менее, аудитория там ну самая большая.

Насколько там имеет смысл?

ХЗ. По опыту, с HN меньше народу по ссылкам ходит, чем с reddit-а. Но ходят. Тут, наверное, больше от удачного заголовка все зависит.

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

Тем не менее, аудитория там ну самая большая.

95% собрались там, ага.

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

К C++ можно прикрутить что угодно, правда, после этого он будет тормозить так же, как CL.

Паттерн Visitor тоже небыстрый. Более того, есть дописки к компилятору, позволяющие multiple dispatch с константным временем диспатча. На макросах тоже можно http://www.eptacom.net/pubblicazioni/pub_eng/mdisp.html , но пользоваться очень неудобно.

monk ★★★★★ ()

10-ое правило Гринспена работает) Мозг работает, фичи добавляются. Криво, конечно, как и гласит 10-ое правило. lambda (крестовикам перекреститься) уже в стандарте.

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

Смайлодауна настолько привлёк запах лишпа, что он решил зарегаться, лол.

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

Если из C++ сделают CL без сборщика мусора, компилируемый в нативный код, я всеми семью конечностями за!

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

А каким местом он тогда CL будет?

Достоинства CL: CLOS, рестарты, макросы, динамические переменные.

Макросы к C++ можно прикрутить внешние (L++ и подобные), к тому же необходимость уменьшилась: вычисления времени компиляции уже внесли в стандарт, на родных тоже можно создавать сложные вещи (этот топик как пример).

Рестарты — теперь реализованы. Читается не хуже, чем CL.

CLOS — добавление новых методов к существующим классам в C++ де-факто есть (свободные функции-друзья). Нет только нормального виртуального диспатчинга по аргументам. Костылями делается, но очень коряво.

Динамические переменные — Скорее всего можно сделать через RAII. Только с многопоточностью надо как-то разобраться.

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

Свободные функции-друзья таки требуют модификации класса, а это вряд ли предполагалось.

Разве .so надо перекомпилировать? Вроде только .h меняешь и всё.

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

Строго говоря, платформозависимо, но вообще да. Я думал, речь про сторонние классы, которые нельзя трогать.

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

Только никто этим набором костылей не будет пользоваться. Зачем, если C++ и так хорош?

fixed

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

Рестарты — теперь реализованы.

find-restart, например, нет.

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

Зачем, если можно взять сразу CL?

Аргумент ровно один: скорость выполнения программы. В несколько раз.

monk ★★★★★ ()

Вот это да :-) Кто-то решил продемонстрировать нужность деструкторов и что из них можно исключения кидать :-) Из временных объектов... :-) Однако потуга запретить копирование with_handlers успехом не увенчалась, поэтому стоит только написать вот так:

int main()
{
    auto oops = with_handlers {
        cout << my_sqrt(+4) << endl;
        cout << my_sqrt(-4) << endl;
    } handler_bind (bad_my_sqrt_parameter&) {
        invoke_restart my_sqrt_use_value{ -1 };
    };

    with_handlers {
        cout << my_sqrt(+4) << endl;
        cout << my_sqrt(-4) << endl;
    } handler_bind (bad_my_sqrt_parameter& p) {
        invoke_restart my_sqrt_recalculate { -p.param };
    };
}
как оно уже работает в обратном порядке :-) Цепепе он такой, Лиспом не станет :-)

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

Если из C++ сделают CL без сборщика мусора, компилируемый в нативный код, я всеми семью конечностями за!

Ты серьёзно считаешь, что настанет такой час, когда в программе на C++ в рантайме будет доступен компилятор C++, который будет подгружать сгенеренный код в образ? :-) Лол :-)

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

на родных тоже можно создавать сложные вещи (этот топик как пример)

Сама по себе вещь элементарная :-) Она сложная в реализации на цепепе :-)

Рестарты — теперь реализованы.

А ещё, когда то давным давно, в C with Classes можно было определять функции call() и return(), которые являлись аналогами :before и :after :-) Вот бы ещё их реализовать, правда? :-)

Читается не хуже, чем CL.

Ты имеешь в виду реализацию? :-) Что, прям сходу понял как что сделано? :-)

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

Ты серьёзно считаешь, что настанет такой час, когда в программе на C++ в рантайме будет доступен компилятор C++, который будет подгружать сгенеренный код в образ? :-) Лол :-)

https://root.cern.ch/cling

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

Аргумент ровно один: скорость выполнения программы. В несколько раз.

Много ли требуется решать задач, где где функция должна отработать не больше чем, скажем, за 0.003 секунды? :-) И что, тебе для решения таких задач прям необходим стиль CL? :-)

С другой стороны, так ли критично в твоих задачах, что функция отработает за 0.03 секунд, а не за 0.003? :-)

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

когда в программе на C++ в рантайме будет доступен компилятор C++, который будет подгружать сгенеренный код в образ?

Это-то как раз тривиально

  f << generate_code();
  f.close();
  system("g++ tmp.cpp");
  tmp = dlopen("tmp.so");
  obj->fun = dlsym(tmp, "generated");

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