Почему repl разработка не победила?
высокие издержки? Неуниверсальность применения - попробуй сделать REPL для встроенных контроллеров.
Какие?
[quote] Неуниверсальность применения - попробуй сделать REPL для встроенных контроллеров.[/quote]
В чем проблема?
Всякое пхп примерно так и работает же
Всякое пхп примерно так и работает жеНет, в ПХП стейт не сохраняется.
в sql-базе обычно сохраняют, и с базой тоже работают так как ты хочешь - без перезагрузок
в sql-базе обычно сохраняют, и с базой тоже работают так как ты хочешь - без перезагрузокОк, я согласен, что в stateless проге так можно. Допустим теперь, что я пишу FPS шутер. Я хочу менять код функции рендеринга сцены и сразу видеть, как это выглядит. Или добавлять через repl объекты в сцену. Как сейчас это делается?
ну вон для jvm например есть специальные отладочные интерфейсы чтоб такое делать
Допустим теперь, что я пишу FPS шутер. Я хочу менять код функции рендеринга сцены и сразу видеть, как это выглядит. Или добавлять через repl объекты в сцену. Как сейчас это делается?
код будет работать кучу времени наверно, из-за отсутствия встраивания и оптимизации
ну и ещё очень сложно с многозадачностью
все те лисп-машины afaik однозадачны
Почему REPL-разработка не победила в исторической перспективе?в mutable-окружении тяжело очистить текущий запуск от артефактов предыдущих запусков.
Т.е. предыдущий запуск где-то попортил данные и это приводит к некорректной работе текущего запуска -> из-за этого легко сделать неверный вывод, что ошибка в текущем коде и его надо поменять
Либо наоборот: предыдущие запуски сделали корректную инициализацию, в текущем коде такого такого уже нет, но он продолжает корректно отрабатывать -> легко сделать неверный вывод, что текущий код и есть правильный
Т.е. предыдущий запуск где-то попортил данные и это приводит к некорректной работе текущего запуска -> из-за этого легко сделать неверный вывод, что ошибка в текущем коде и его надо поменятьТы хочешь сказать, что испортится стейт программы?
Либо наоборот: предыдущие запуски сделали корректную инициализацию, в текущем коде такого такого уже нет, но он продолжает корректно отрабатывать -> легко сделать неверный вывод, что текущий код и есть правильный
В принципе, программист одновременно отлаживает один какой-то кусок программы. Например, мы пишем fps шутер и работаем над физикой. При это могут попортиться только положения объектов сцены. Ничто не мешает заскриптовать редактор, чтобы после замены функции вызывалась функция reset-scene, которая убирает все объекты и ставит их в начальные положения. При этом не надо рестартовать всю игру и ждать пока загрузятся модели и текстуры с диска.
ну и почему ты так не делаешь? лиспы никуда не исчезли
Например, я открываю консоль хрома прямо на этой странице с форумом, пишу getElementPosition и жму Enter. Мне выводится код этой функции и что она определена в файле decor.js на такой-то строке.
Или я пишу window. и после точечки мне выводится список полей в объекте windows.
При классическом подходе строчка кода пишется сразу там, где она должна быть и сразу согласованная по аргументам с предыдущим кодом и последующим.
При repl-е получается, что новая строчка кода остается непривязанной ко всей остальной программе
Вообще, в современных IDE возможность изменения кода без остановки дебаггера - вполне себе мини-REPL
Есть еще такой аспект, что реплы делаю ненужными IDE.Вебсторм это умеет внутри IDE. Как из этого следует, что IDE не нужна?
Например, я открываю консоль хрома прямо на этой странице с форумом, пишу getElementPosition и жму Enter. Мне выводится код этой функции и что она определена в файле decor.js на такой-то строке.
Или я пишу window. и после точечки мне выводится список полей в объекте windows.
Помню, работал в пакете Mathematica, всё время было неприятное ощущение – надо помнить какое сейчас состояние памяти у машины.
В принципе, программист одновременно отлаживает один какой-то кусок программы. Например, мы пишем fps шутер и работаем над физикой. При это могут попортиться только положения объектов сцены. Ничто не мешает заскриптовать редактор, чтобы после замены функции вызывалась функция reset-scene, которая убирает все объекты и ставит их в начальные положения. При этом не надо рестартовать всю игру и ждать пока загрузятся модели и текстуры с диска.Кстати, Controllable Query как раз входит в runtime, чтобы получать информацию о коде. В этом его основная изюминка, поскольку интегрировать язык запросов с языком общего назначения чисто на статической типизации пока не у кого хорошо не получилось.
вторая проблема: непрозрачен переход от истории команд, выполняемых в repl, к коду конечной программы.Нет, ты пишешь функцию в том же месте в коде, как при классическом подходе. Потом ты жмешь хоткей (в емаксе это С-с С-с) - у тебя компилится и загружается та функция, в которой стоит курсор.
При классическом подходе строчка кода пишется сразу там, где она должна быть и сразу согласованная по аргументам с предыдущим кодом и последующим.
При repl-е получается, что новая строчка кода остается непривязанной ко всей код программы
Потом ты жмешь хоткей (в емаксе это С-с С-с) - у тебя компилится и загружается та функция, в которой стоит курсор.а если это макрос/шаблон?
или без шаблонов даже
let a = ....... ;
let b = ..... (a) ;
ты обе строчки поправил, вторую загрузил, а первую забыл, функция a осталось старой - и дальше отлаживаешь, не заметив эту проблему
после перезагрузки ничего не работает
Потом ты жмешь хоткей (в емаксе это С-с С-с) - у тебя компилится и загружается та функция, в которой стоит курсор.имхо, если брать все множество изменений, которое делает программист за свой рабочий день, то доля изменений кода, когда меняется только одна функция очень невелика.
Обычно это лишь какая-то подгонка: типа верстки пользовательского интерфейса и т.д.
Основной класс изменений скорее другой: программист запустил код и увидел, что он забыл рассмотреть какой-то случай. И чтобы этот случай обработать необходимо внести множество изменений в несколько разнесенных мест:
- добавить поле в какую-нибудь структуру (очень плохо ложится на repl, если это касается уже загруженных данных);
- поменять код, который подготавливает данные для кода принимающего решение;
- поменять код, применяющий решение,
- поменять тестовое окружение.
в сухом остатке получается что REPL - это продвинутый дебаггер
а если это макрос/шаблон?При C-c С-с происходит компиляция текущей top-level формы. Файл - список топ-левел форм, при загрузке файла загружаются все топ-левел формы. Таким образом, механизм тот же, что и при загрузке программы с нуля.
ты обе строчки поправил, вторую загрузил, а первую забыл, функция a осталось старой - и дальше отлаживаешь, не заметив эту проблемуМожешь перезапускать программу целиком. Зависит от того, какие у тебя альтернативы. На джаве люди исправляют плюс на минус и потом минутами ждут рестарта.
Потом ты жмешь хоткей (в емаксе это С-с С-с) - у тебя компилится и загружается та функция, в которой стоит курсор.При этом выполнение кода не выходит из состояния этой функции?
т.е. условно в начале функции ставится breakpoint, а в конце функции goto на начало функции? и это образует repl-цикл?
Ведь иначе если выполнение кода выходит из функции, то состояние функции будет разрушено (освободится стек, вызовутся деструкторы) и в состояние на начало функции уже не получится вернуться.
Такой вариант REPL-а плохо сочетается с интерактивными программами: т.е. когда не просто хочется выполнить строчку кода, но и как-то с программой взаимодействовать: пощелкать кнопки, увидеть разные отображения, посмотреть как программа отвечает на запросы от других программ и т.д.
На джаве люди исправляют плюс на минус и потом минутами ждут рестарта.Пишут, что Eclipse такое умеет. Может эти люди просто используют недо-IDE или не знают возможностей своей IDE?
http://stackoverflow.com/questions/12661114/how-to-modify-ja...
Eclipse supports hot swapping code during debugging , out of the box.
While debugging, just change any code and save it, eclipse will automatically transfer the modified code to the target VM.
Note that you can't make structural changes to the code, like adding new methods, changing method signature or adding new fields. But you can change the code within a method.
EDIT: Note that changing the code during deubgging will make that method re-execute form the beginning, resetting the local variables in that method.
На джаве люди исправляют плюс на минус и потом минутами ждут рестарта.на плюсах можно собрать программу с опцией Edit&Continue
При этом выполнение кода не выходит из состояния этой функции?Disclaimer: я не лиспер и делаю выводы из поверхностного чтения доков.
т.е. условно в начале функции ставится breakpoint, а в конце функции goto на начало функции? и это образует repl-цикл?
Ведь иначе если выполнение кода выходит из функции, то состояние функции будет разрушено (освободится стек, вызовутся деструкторы) и в состояние на начало функции уже не получится вернуться.
Такой вариант REPL-а плохо сочетается с интерактивными программами: т.е. когда не просто хочется выполнить строчку кода, но и как-то с программой взаимодействовать: пощелкать кнопки, посмотреть разные отображения и т.д.
Никаких брейкпоинтов не ставится. Как я себе представляю repl разработку для js. В js когда ты пишешь
function foo{
..
}
это сахар для
globalObject.foo = function{
...
}
Когда ты жмешь на кнопку, у тебя происходит тупой eval, который просто заменяет в хэш-таблице один поинтер на другой.
Note that you can't make structural changes to the code, like adding new methods, changing method signature or adding new fields. But you can change the code within a method.Точно, я когда видел на джаве функцию строк в 700 длиной, то знал, что она была отлажена без единого рестарта - разбить ее на несколько функций было нельзя. Это не говоря уже о том, что этот дебаг был крайне хрупок и ненадежен - ты жал кнопку save, ide говорила что все ок, но при этом код не заменялся. Вокруг этого есть огромная инфраструктура кривых костылей, включая плагины к jvm и даже патченные jvm.
Когда ты жмешь на кнопку, у тебя происходит тупой evalвнутри какого контекста эта функция выполняется? контекст - это грубо говоря состояние стека
т.е. если A вызывает B, а B вызывает C, то для того, чтобы вызвать функцию C еще раз - ей нужно подсунуть контекст от функции B (состояние стека от функции B)
соответственно, если хочется функцию C вызывать многократно, то необходимо repl-кольцо организовать внутри функции C.
Как я себе представляю repl разработку для jsEdit & Continue для javascript-а поддерживается в chrome
http://stackoverflow.com/questions/10208922/editing-javascri...
Я не знаю как это работает, могу только высказать свои нубские догадки. Можно, например, сохранять текущее состояние стека, создавать новый стек с одним кадром, который отлавливает исключения, выполнять код, и возвращать старый стек на место.
На джаве люди исправляют плюс на минус и потом минутами ждут рестарта.слюшай, дарагой, если те кто с тобой работал идиоты - это не значит что все остальные такие же.
слюшай, дарагой, если те кто с тобой работал идиоты - это не значит что все остальные такие же.Расскажи, под какую платформу ты разрабатываешь, сколько времени занимает рестарт после типичного изменения?
Расскажи, под какую платформу ты разрабатываешь, сколько времени занимает рестарт после типичного изменения?1)я уменьшаю время рестарта в приложениях, которые разработываю, если оно меня не устраивает, сейчас типичное время - 10 секунд, и да, на замену плюса на минус рестарт вообще не нужен, конечно же.
2)если есть возможность не запускать часть приложения - я этим пользуюсь
3)если есть возможность написать тест - я его пишу и дебажу уже его
ipython, bash, sql
типа фигачишь задачку, потом (если нужно) собираешь из хистори программу, которая задачу решает
прототипы писать получается гораздо быстрее, чем на плюсах отлаживать код.
вообще там где данных больше чем логики такой подход существенно рулит
но всё это чревато как уже писали выше, проходом по граблям из-за того, что нужно держать в голове текущий стейт.
еще при javascript-разработке такое делал - по живому фигачишь, но тут тоже часто логики меньше, чем данных.
вообще там где данных больше чем логики такой подход существенно рулитВроде, когда много логики, получаются как раз хорошие чистые функции, как раз удобные для репла, разве нет?
но всё это чревато как уже писали выше, проходом по граблям из-за того, что нужно держать в голове текущий стейт.
Расскажи, под какую платформу ты разрабатываешь, сколько времени занимает рестарт после типичного изменения?Платформа ASP.NET MVC. Обычно подключено куча сорцов, но чаще всего изменения делаются только в текущем проекте. Делаешь билд-конфигурацию только из текущего проекта.
Если правиться модель или контроллер, то
билд 1.23s
плюс первый запрос в хроме 1.84s
Если правиться только представление, то билд не нужен
первый запрос в хроме 1.36s
В ASP.NET 2014 переделывают движок, явного билда не будет даже для контроллера. И билдиться будет в одном процессе в памяти. Сейчас компилятор вызывается фактически из командной строки, причем несколько раз, что вносит свои задаржки. Т.е. в итоге ждем, что будет еще быстрее.
При C-c С-с происходит компиляция текущей top-level формы. Файл - список топ-левел форм, при загрузке файла загружаются все топ-левел формы. Таким образом, механизм тот же, что и при загрузке программы с нуля.ну вот в emacs lisp насколько я знаю нет шаблонов и макросов, а если бы были, то макрос в топ-левел, а инстанциируют его где угодно, топ-левел меняешь, а остальное остаётся
где-то победила,да что там, спрашивали про игры
ipython, bash, sql
типа фигачишь задачку, потом (если нужно) собираешь из хистори программу, которая задачу решает
прототипы писать получается гораздо быстрее, чем на плюсах отлаживать код.
насколько я знаю, нередки случаи, когда в играх используется DSL или даже лисп для описания игрового мира, тогда всё как хочет автор
При этом выполнение кода не выходит из состояния этой функции?как раз с интерактивными программами всё отлично - заменяем функции, когда те находятся в состоянии между обработкой сообщений
т.е. условно в начале функции ставится breakpoint, а в конце функции goto на начало функции? и это образует repl-цикл?
Ведь иначе если выполнение кода выходит из функции, то состояние функции будет разрушено (освободится стек, вызовутся деструкторы) и в состояние на начало функции уже не получится вернуться.
Такой вариант REPL-а плохо сочетается с интерактивными программами: т.е. когда не просто хочется выполнить строчку кода, но и как-то с программой взаимодействовать: пощелкать кнопки, увидеть разные отображения, посмотреть как программа отвечает на запросы от других программ и т.д.
собственно REPL - это event loop, в котором event есть код
внутри какого контекста эта функция выполняется? контекст - это грубо говоря состояние стекаКак-то скромененько.
т.е. если A вызывает B, а B вызывает C, то для того, чтобы вызвать функцию C еще раз - ей нужно подсунуть контекст от функции B (состояние стека от функции B)
соответственно, если хочется функцию C вызывать многократно, то необходимо repl-кольцо организовать внутри функции C.
Вот вылетело у тебя исключение в стеке вызовов глубиной около 40. Ты посмотрел в этом месте - код правильный.
И вот что дальше делать в REPL - не понятно совершенно. А между тем у меня, например, это самый частый сценарий отладки.
Если, конечно, вся софтина не влазит в 50к строк.
Вот вылетело у тебя исключение в стеке вызовов глубиной около 40. Ты посмотрел в этом месте - код правильный.непонятно к чему эта жалоба
И вот что дальше делать в REPL - не понятно совершенно. А между тем у меня, например, это самый частый сценарий отладки.
тебе ведь для того и печатают полный дамп стека, чтоб ты проверил и другие функции
непонятно к чему эта жалобаИ как ты из REPL собираешься смотреть состояние переменных в этих функциях?
тебе ведь для того и печатают полный дамп стека, чтоб ты проверил и другие функции
например, если стектрейс - это структура данных, которое в себе содержит список фреймов и значений в них, то просто берёшь и смотришь туда
И как ты из REPL собираешься смотреть состояние переменных в этих функциях?Вставляешь в функцию отладочную печать, дергаешь ее через репл и смотришь.
Live coding in VR with the Oculus Rift, Firefox WebVR, JavaScript and Three.jsНа Lua Workshop, который недавно проходил, чувак тоже показывал свой live coding:
http://luaconf.ru/#Bucher
Он себе фреймворк какой-то налабал. Модули при изменении сами перегружаются и подменяются. Ну и он так примерно в таком же "интерактивном" режиме сцены свои прогает.
в mutable-окружении тяжело очистить текущий запуск от артефактов предыдущих запусков.Значит REPL провоцирует не писать говнокод, сильно зависящий от артефактов окружения.
Т.е. предыдущий запуск где-то попортил данные и это приводит к некорректной работе текущего запуска -> из-за этого легко сделать неверный вывод, что ошибка в текущем коде и его надо поменять
Либо наоборот: предыдущие запуски сделали корректную инициализацию, в текущем коде такого такого уже нет, но он продолжает корректно отрабатывать -> легко сделать неверный вывод, что текущий код и есть правильный
На Lua Workshop, который недавно проходил, чувак тоже показывал свой live coding:Это хорошо, но при старании можно сделать лайвкодинг и на голом C. Я хочу, чтобы лайвкодинг считался обязательной фичей среды разработки, для которой не надо писать специальные фреймворки.
Собственно, на создание темы меня сподвиг этот пост:
http://lisperator.net/blog/livenode-live-code-your-nodejs-ap...
Автор поста - лиспер - начал писать сервер на nodejs и сразу обнаружил, что его надо перестартовывать после каждого изменения. При этом прикрутить репл нельзя из-за специфического формата nodejs модулей. Это показалось ему настолько невыносимым, что он написал тулзу, которая при загрузке js кода некоторым хитрым костыльным образом трансформирует его, делая пригодным для лисп-стайл разработки.
В случае с nodejs, казалось бы, есть полностью динамический язык, можно было бы сделать сразу все нормально - но нет, опять наступили на те же грабли.
Edit & Continue для javascript-а поддерживается в chromeВ хроме все намного круче, чем edit & continue. Во-первых, там не надо ставить на брейкпоинт, чтобы выполнить замену. Во-вторых, функциональность доступна через api на вебсокетах. У меня в проекте есть скрипт на nodejs, который мониторит файловую систему и при изменении файла загружает его в хром. Таким образом, не требуется интеграция с текстовым редактором.
Вставляешь в функцию отладочную печать, дергаешь ее через репл и смотришь.И так 40 раз?
например, если стектрейс - это структура данных, которое в себе содержит список фреймов и значений в них, то просто берёшь и смотришь тудаИ много в каких реплах ты такое видел?
Можешь привести пример сессии с указанным сценарием?
В IDE ты просто активируешь нужный фрейм, а дальше - хоть репл, хоть watches.
Я хочу, чтобы лайвкодинг считался обязательной фичей среды разработки, для которой не надо писать специальные фреймворки.А при чём здесь среда разработки? Для лайвкодинга нужно, чтобы фреймворк подхватывал изменения.
Откуда среде разработки знать, что при обновлении кода Initialize нужно почти всё перезапускать, а при обновлении Render - вообще ничего, т.к. через 18мс оно само обновится?
Это хорошо, но при старании можно сделать лайвкодинг и на голом C. Я хочу, чтобы лайвкодинг считался обязательной фичей среды разработки, для которой не надо писать специальные фреймворки.Мне кажется, что описываемое в Эрланге в некотором виде есть. Есть REPL у VM и живое обновление кода без перезапуска.
Собственно, на создание темы меня сподвиг этот пост:
...
http://lambda-the-ultimate.org/node/1544#comment-18446
на самом деле непонятно, почему ты противопоставляешь IDE и REPL, когда это ортогональные концепции
IDE тебе может показать таблички с фреймами, чтоб не смотреть их по одному с помощью команд
я не могу, а в интернетах они естьНу это как-то тухловатенько: текущая функция не показывается, переход к конкретной функции не показан, не видно команды, которая все locals бы выводила вместе со значениями, чтобы по одному не перебирать.
http://lambda-the-ultimate.org/node/1544#comment-18446
Согласен, что это всё довольно легко реализуемо, просто странно, что из коробки не видно. Может поэтому и не используют.
на самом деле непонятно, почему ты противопоставляешь IDE и REPL, когда это ортогональные концепцииНу если Edit & Continue, о котором тут речь шла, и есть тот REPL, о котором был изначальный вопрос, тогда сам вопрос в корне не верен, т.к. Edit & Continue прижился.
IDE тебе может показать таблички с фреймами, чтоб не смотреть их по одному с помощью команд
Это отладка в командной строке плохо прижилась. Впрочем, как и разработка в vim/emacs подобных редакторах.
Ну если Edit & Continue, о котором тут речь шла, и есть тот REPL, о котором был изначальный вопрос, тогда сам вопрос в корне не верен, т.к. Edit & Continue прижился.ну если там можно определить функцию, вставить куда-нибудь её вызов и продолжить, то да, примерно оно и есть
только вот если эта волшебная штука внутри хрома, то что с серверным кодом?
только вот если эта волшебная штука внутри хрома, то что с серверным кодом?Ну это Node.js ещё не дорос просто. .NET, Java и плюсы вроде умеют.
Останавливать, правда, надо. Но это, вроде, решение разработчиков IDE. Им ничто не мешало приостановить-пропатчить-возобновить делать автоматически. Подозреваю, что в Visual Studio так сделано, чтобы случайно не сломать выполнение в результате непреднамеренного ввода в окно с кодом.
В IDE ты просто активируешь нужный фрейм, а дальше - хоть репл, хоть watches.Мне отладка в IDE с брейкпоинтами и рассматриванием локальных переменных всегда казалась второсортной по сравнению с отладкой принтами. Причина - с брейкпоинтами мы видим одно состояние за раз, с принтами мы видим историю выполнения программы. Плюс принты можно преобразовать в вызовы логгера - когда мы добавляем логгинг, мы добавляем в программу интроспекцию и работаем на будущее, брейкпоинты и вотчи подлежат удалению после текущей сессии отладки.
в этом случае отладка машиной времени ещё лучше, чем отладка принтами.
вы так много пишете об отладке, разве так сложно писать программы, которые не требуют длительной отладки?
вы так много пишете об отладке, разве так сложно писать программы, которые не требуют длительной отладки?трудозатратнее
вы так много пишете об отладке, разве так сложно писать программы, которые не требуют длительной отладки?Я давно уже перестал обожествлять свои навыки программирования и признал, что умею делать ошибки. А каждая ошибка требует отладки, не важно длительной или нет. Если ошибка обнаружится на стадии модульного тестирования - то это ещё легко отделался, локализовать её можно достаточно быстро, иногда даже метод пристального взгляда работает. А вот если ошибка вылезла уже на этапе тестирования всего приложения, то тут от ковыряния уже не спастись, и написание дополнительных модульных тестов лишь одно из средств отладки. Печать же стоит на первом месте.
Конечно, все люди, и все делают ошибки. Одно дело простые ошибки, типа невнимательность и т.п. Для таких ошибок дебагер самое то, по быстрому посмотрел, поправил и дальше пошел, оставлять принты на будущее это только захламлять код. Логирование это совсем для другого. Это для тех редких ситуаций, когда на продакшене происходит какая-то хрень, и не по базе, не пристальным взглядом на код не понятно, что происходит. Это редкие ситуации. А вот так чтобы сидеть и читать историю работы программы, типа код настолько не читаем, что остается читать только историю выполнения программы?
1)я уменьшаю время рестарта в приложениях, которые разработываю, если оно меня не устраиваетЭто возможно только если ты начальник. Большинство программистов работают над теми задачами, над которыми им скажут.
сейчас типичное время - 10 секунд, и да, на замену плюса на минус рестарт вообще не нужен, конечно же.10 секунд - это очень долго. Сейчас операционные системы стартуют быстрее. В 2014 году, на современном железе, если сугубо прикладные программы работают не мгновенно, то это провал и позор software индустрии. Нет никаких фундаментальных причин, чтобы разработка веб/энтерпрайз приложений не выглядела, как этом видео выглядит разработка игры - чтобы мы могли менять код, который выдает данные для веб-страницы или рисует эту страницу и в реальном времени видеть в браузере как эта страница меняется.
типа код настолько не читаем, что остается читать только историю выполнения программыкод обычно прекрасно читаем, и в нём кристально ясно видно, что ошибок нет. А они есть, во что бы то не верил программист. А в последнее время, когда стало принято использовать огромное множество сторонних библиотек вера стала особенно зыбка, потому что документация к ним редко показывает все тонкости работы и приходится читать и отлаживать ещё и их исходники.
ставлять принты на будущее это только захламлять кодПоэтому принты я ставлю по мере необходимости и после найденной ошибки добавляю модульный тест и вычёркиваю принты. А если у тебя под рукой есть дебаггер класса "машина времени", то и расставлять принты не нужно.
Это возможно только если ты начальник. Большинство программистов работают над теми задачами, над которыми им скажут.это возможно, даже если ты не начальник, но у тебя не загружен на 100% твой рабочий день, у большинства программистов это так.
10 секунд - это очень долго. Сейчас операционные системы стартуют быстрее.это если у тебя цикл разаботки "написать/запустить/вызвать только что написанное" занимает меньше минуты, а если ты иногда думаешь над тем что надо написать, то он больше, а если меньше, то ты не думаешь, а тупо подбираешь последовательность операторов, при которых программа выдаст подходящий результат.
Нет никаких фундаментальных причин, чтобы разработка веб/энтерпрайз приложений не выглядела, как этом видео выглядит разработка игры - чтобы мы могли менять код, который выдает данные для веб-страницы или рисует эту страницу и в реальном времени видеть в браузере как эта страница меняется.именно подбором подходящих параметров в этом видео и занимаются, а ещё там "машина времени" в движок встроена, про которую тут уже говорили.
это если у тебя цикл разаботки "написать/запустить/вызвать только что написанное" занимает меньше минуты, а если ты иногда думаешь над тем что надо написать, то он больше, а если меньше, то ты не думаешьты думаешь над строчками кода десятками минут или часов?
ты думаешь над строчками кода десятками минут или часов?На, собственно, написание кода я трачу меньше трети рабочего дня) и цикл запуска, пусть даже в 10 секунд не выглядит таким большим.
чисто REPL-задачи, не решающиеся тестами (пиксел-хантинг) - выносятся либо в скрипты, либо вообще не очень часто возникают
... прекрасно читаем, и в нём кристально ясно видно, что ошибок нет. А они есть...Отсюда очевидный вывод, что твоё понимание "хорошая читаемость кода" неадекватно реальности. При адекватном понимании, если кристально ясно видно, что ошибок нет, то их, действительно, нет. А до тех пор совершенствуйте своё понимание читаемости кода.
А в последнее время, когда стало принято использовать огромное множество сторонних библиотек вера стала особенно зыбка, потому что документация к ним редко показывает все тонкости работы и приходится читать и отлаживать ещё и их исходники.
Кем принято? За конечный продукт отвечаешь ты. Использовать сторонние библиотеки и какие это твой выбор и твоя ответственность.
На, собственно, написание кода я трачу меньше трети рабочего дня) и цикл запуска, пусть даже в 10 секунд не выглядит таким большим.вы забываете главное:
http://www.youtube.com/watch?v=GfIDO4o334U#t=29m35s
http://appleinsider.ru/eto-interesno/stiv-dzhobs-v-sssr-kak-...
Вы, что вот так просто сидите и думаете без компьютера?
Очевидно, что после начального периода программист начинает понимать следующее. Создание software это интеллектуальная деятельность. Компьютеры — это инструмент усиления интеллектуальных возможностей. Компьютер должен вживляться в сам процесс создания software человеком. Расскажу о своём опыте создания enterprise software. Значительную часть мыслительной деятельности мне удается переложить на плечи ReSharper, компилятора и dbForge. .
Я так понимаю, топикстартер сейчас находится на этапе прохождения начального периода и осознания потребности в инструменте усиления интеллектуальных возможностей. Но тут есть некоторое разочарование для динамических языков. Код на динамических языках плохо поддается машинной обработке.
Привер.Мне код показался переусложненным, возможно я плохо понял задачу?
Вот твой код
static double EstimatePi(int trials)
{
return Math.Sqrt(6/MonteCarlo(trials, new CesaroTest(0;
}
static double MonteCarlo(int trials, IExperiment experiment)
{
return MonteCarloIter(trials, trials, 0, experiment);
}
static double MonteCarloIter(int trials, int trialsRemaining, int trialsPassed,
IExperiment experiment)
{
if (trialsRemaining == 0) return (double) trialsPassed/trials;
var tuple = experiment.Do;
var passed = tuple.Item1 ? trialsPassed + 1 : trialsPassed;
return MonteCarloIter(trials, trialsRemaining - 1, passed, tuple.Item2);
}
public interface IExperiment
{
Tuple<bool, IExperiment> Do;
}
public class CesaroTest : IExperiment
{
private readonly int x;
public CesaroTest(int x)
{
this.x = x;
}
public Tuple<bool, IExperiment> Do
{
var x1 = RandUpdate(x);
var x2 = RandUpdate(x1);
IExperiment item2 = new CesaroTest(x2);
return Tuple.Create(Gcd(x1, x2) == 1, item2);
}
}
который эквивалентен моему коду на кофескрипте:
estimatePi = ({trials}) ->
Math.sqrt 6 / monteCarlo {trials, test: cesaro {seed:0}}
monteCarlo = ({trials,test}) =>
trialsRemaining = trials
trialsPassed = 0
while trialsRemaining-- > 0
if test then trialsPassed++
trialsPassed/trials
cesaro = ({seed}) ->
->
x1 = randupdate seed
x2 = randupdate x1
seed = x2
gcd(x1,x2) is 1
Если я правильно понял кофескрипт, твой подход в учебнике sicp называют object-based approach. Это так?
в учебнике sicp называют object-based approach
This will force us to abandon our old substitution model of computation (section 1.1.5) in favor of a more mechanistic but less theoretically tractable environment model of computation.
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-19.html...
По сути, манипуляции с кодом (которые я описал) являются отражением именно подстановочной модели. Тем самым, сейчас в 2014 году theoretically tractable превратилась уже в практически ценную вещь. Именно поэтому сейчас народу начинает приходить просветление, что с ООП что-то не так, и растет интерес ко всяким immutable вещам.
Исторически ООП это времена Smalltalk-а, который динамический. Код на динамическом языке плохо поддается машинной обработке, поэтому в те времена не удавалось извлечь практическую пользу от свойств подстановочной модели. ООП расцвел бурным цветом, поскольку конкурирующая модель вычислений еще не имела реализованных инструментов. Сейчас инструменты уже набрали достаточный объем функционала.
Манипуляции с кодом могут сочетаться с ООП. Но если у двух реализаций одной задачи разное количество изменяемого состояния, то перевести с помощью преобразований кода один вариант в другой часто невозможно (либо сложно).
Если я правильно понял кофескрипт, твой подход в учебнике sicp называют object-based approach. Это так?Там cesaro - функция, которая меняет переменную seed попавшую ей в замыкание. Если нельзя пользоваться мутабельным стейтом, то возня с таплами слишком низкоуровневая, напрашиваются монады:
import Control.Monad.State
estimatePi trials random seed = 6.0 / (sqrt $ monteCarlo trials (cesaro random) seed)
monteCarlo trials test seed =
let testList = evalState (sequence $ replicate trials test) seed
in fromIntegral(length testList) / fromIntegral(length $ filter id testList)
cesaro random = do
x1 <- random
x2 <- random
return gcd x1 x2) == 1)
Генератор случайных чисел можно передавать как параметр, как в этой твоей версии
http://gist.github.com/Test20130521/42d423948b51ecb1c92b/7d...
но 8 строк кода против 31
Что там с сишарпом и решарпером? Есть ли там рефакторинг introduce monads?
возня с таплами слишком низкоуровневаявозвратить из функции два значения это низкоуровневая вещь?
но 8 строк кода против 31
C# код можно записать в одну строчку.
Что там с сишарпом и решарпером? Есть ли там рефакторинг introduce monads?
В .NET пока нет общепринятой библиотеки монад, поэтому Jetbrains такой рефакторинг не поддержала еще. Если есть желание поддержать свою библиотеку в ReSharper-е, то можешь написать плагин. Лично я не вижу положительного практического выхлопа от этого.
Монады это попытка увести разговор в сторону?
слишком низкоуровневая,Очень типичная реакция, когда при росте кодой базы на динамическом языке программист понимает, что теряет контроль над кодом. И он начинает применять мега парадигмы: датабайдинг, монады, декларативный подход, MVC, MVP, MVVM, MVW, и т.д. Потом выясняется, что это абстракции, протекающие на реальных задачах. А поскольку смена парадигмы проходила не постепенным преобразованием кода, программист не может понять, где он уже борется с accidental complexity неверно выбранной парадигмы, а где complexity реальной задачи. И соответственно не может внятно объяснить заказчику, в чем сложность, почему нужны время и деньги.
С помощью статической типизации код контролируется в мелочах, поэтому не возникает ощущения потери контроля.
возвратить из функции два значения это низкоуровневая вещь?Разумеется - в хаскеле монады и do-нотация нужны специально для того, чтобы не таскать стейт через цепочку вызовов, потому что иначе код абсолютно нечитабелен, например:
return Tuple.Create(Gcd(tuple.Item1, tuple1.Item1) == 1, item2);
C# код можно записать в одну строчку.
В твоем коде очень много необязательных конструкций - если писать в ФП стиле, то получается короче намного. Причина в том, что "рефакторинги" решарпера ориентированы на язык уровня джавы и на типичный джава код. Например, в ФП не надо объявлять интерфейс и класс с одним методом - достаточно простого замыкания. Затем, не надо объявлять типы - в моем коде функция cesaro имеет тип Monad Integer -> Monad Boolean. Таким образом, используется библиотечный тип. У тебя тип Cesaro
IRandom -> IExperiment where
public interface IRandom
{
Tuple<int, IRandom> Do;
}
public interface IExperiment
{
Tuple<bool, IExperiment> Do;
}
Как видишь, в хаскелле нет всей этой мешанины из таплов и интерфейсов, а у тебя она кишками наружу. Затем, хаскельный код оказался намного более полиморфным - функция cesaro может работать с любой монадой, а не только с монадой State. В случае монады IO мы получим ситуацию, когда случайные числа читаются с диска, а в случае монады List - когда уже есть сгенеренный список случайных чисел.
код контролируется в мелочах
Это называется IDE код - когда кода много, но когда открываешь его в произвольном месте, не можешь понять намерения автора. Оказывается, что полезная логика занимает несколько строк, а все остальное - бесполезный мусор, который существует только потому, что с IDE его можно не замечать, до определенного момента.
при росте кодой базы на динамическом языке программист понимает, что теряет контроль над кодом. И он начинает применять ... монадыЩито? Давно хаскель стал динамическим?
http://gist.github.com/Test20130521/42d423948b51ecb1c92b/2c...
этот код с делегатом был еще в марте 2013, т.е. еще раньше того, который цитируешь ты, который чисто для пошаговой демонстрации был приведен.
Короче, опять я тебе про одно, а ты с кофескрипта перепрыгнул на хаскель. Ты на хаскеле пишешь продакшен код? Нет? Тогда зачем ты перевел разговор в сторону вещей неприменимых в реальной жизни? Сам же напоминаешь тех Java-архитекторов, которых описывал.
Ты очень странно свой отрицательный опыт на Java раздуваешь до вселенских масштабов. Во-первых, большую часть твоего отрицательного опыта это особенность команды, в которую ты попал. Во-вторых, java насквозь пронизана ООП идеологией. Они даже extension методы умудрились сделать в ООП идеологии. А как раз практическая сила статической типизации – это преобразования кода. Как я уже писал, объектный подход часто ставит необоснованные границы для преобразований кода. Т.е. java своей идеологией фактически убивает преимущества статической типизации.
Тут вспомнил к предыдущему посту, ReSharper уже сейчас переводить код через циклы в монадный код, для Enumerable монады.
я думал 31 строчки ты имел ввиду вот эти этот код с делегатом был еще в марте 2013, т.е. еще раньше того, который цитируешь ты, который чисто для пошаговой демонстрации был приведен.
Короче, опять я тебе про одно, а ты с кофескрипта перепрыгнул на хаскель. Ты на хаскеле пишешь продакшен код? Нет? Тогда зачем ты перевел разговор в сторону вещей неприменимых в реальной жизни? Сам же напоминаешь тех Java-архитекторов, которых описывал.
Ты очень странно свой отрицательный опыт на Java раздуваешь до вселенских масштабов. Во-первых, большую часть твоего отрицательного опыта это особенность команды, в которую ты попал. Во-вторых, java насквозь пронизана ООП идеологией. Они даже extension методы умудрились сделать в ООП идеологии. А как раз практическая сила статической типизации – это преобразования кода. Как я уже писал, объектный подход часто ставит необоснованные границы для преобразований кода. Т.е. java своей идеологией фактически убивает преимущества статической типизации.
Тут вспомнил к предыдущему посту, ReSharper уже сейчас переводить код через циклы в монадный код, для Enumerable монады.
Щито? Давно хаскель стал динамическим?изначально был кофескрипт, и автор сразу перепрыгнул на монады
Это называется IDE код - когда кода много, но когда открываешь его в произвольном месте, не можешь понять намерения автора. Оказывается, что полезная логика занимает несколько строк, а все остальное - бесполезный мусор, который существует только потому, что с IDE его можно не замечать, до определенного момента.Давай попробуем не отделываться избитыми фразами из интернета, а разберем код. Покажи, какие символы тебе кажутся мусором, вот в этом коде? http://gist.github.com/Test20130521/42d423948b51ecb1c92b/2c...
Могу сам начать:
1. К сожалению, в C# нет полноценных локальных функций, приходится лепить неуклюжую MonteCarloIter.
2. К сожалению, в C# до сих пор не сделали полноценных таплов с именованными полями.
Вместо
Tuple.Create(Gcd(tuple1.Item1, tuple2.Item1) == 1, tuple2.Item2)
было бы вот так
new {Value = Gcd(tuple1.Value, tuple2.Next) == 1, tuple2.Next}
3. Для методов не выводятся типы аргументов и типы возвращаемых значений.
Да это всё недостатки. Но, во-первых, язык развивается. Причем развивается не отягощённых ООП идеологией. Во-вторых, какие у нас есть другие варианты? Хаскель, который сейчас нереально использовать в продакшен проектах?
Ты выбираешь, брюзжать и ругать
Как видишь, в хаскелле нет всей этой мешанины из таплов и интерфейсов, а у тебя она кишками наружу. Затем, хаскельный код оказался намного более полиморфным - функция cesaro может работать с любой монадой, а не только с монадой State. В случае монады IO мы получим ситуацию, когда случайные числа читаются с диска, а в случае монады List - когда уже есть сгенеренный список случайных чисел.Обманываешь. Ты не дочитал до вот этого абзаца?
Интерфейсы IExperiment и IRandom очень похожи. Их можно заменить на один интерфейс с generic параметром IStream<T>. См. код.
Естественно, в моем коде функция MonteCarlo может работать с любыми случайными числами, в том числе, с диска.
Такое ощущение, что ты читаешь, но не воспринимаешь написанное. Воспринимаешь только те фрагменты текста, которые хочешь.
Такое ощущение, что ты читаешь, но не воспринимаешь написанное. Воспринимаешь только те фрагменты текста, которые хочешь.Занятное замечание. Тем, что оно от тебя.
Занятное замечание. Тем, что оно от тебя.Перед этим я привел пример, где человек явно не дочитал до конца. У тебя есть такой пример, или это твои фантазии?
Перед этим я привел пример, где человек явно не дочитал до конца.Это фантазии. То есть заключение из предположения.
У тебя есть такой пример, или это твои фантазии?
Есть. Но поскольку ты не отделяешь мух от котлет, то есть личного отношения к языкам от личного отношения к их пользователям, развивать тему с тобой нет никакого желания.
Есть. Но поскольку ты не отделяешь мух от котлет, то есть личного отношения к языкам от личного отношения к их пользователям, развивать тему с тобой нет никакого желания.Приводи пример, не развивая тему.
позволяет мне максимально быстро выразить то, о чем я думаю.
Ну, да. Мыслительный понос => говно код.
Иногда добраться быстрей от точки А до точки Б — это не гнать на огромной скорости, а пройти по более короткому пути. Но кто-то будет воспринимать, что это обязательно "гнать", затем упорно развивать тему в ключе "ты — самоубийца".
А давно монады стали означать хаскель?
позволяет мне максимально быстро выразить то, о чем я думаю.
Иногда добраться быстрей от точки А до точки Б — это не гнать на огромной скорости, а пройти по более короткому пути.
Т.е. еще одна проблема коммуникации, думал одно, а написал другое.
В ситуации выше человек просто не дочитал до конца. С твоей ситуацией мало общего.
Т.е. еще одна проблема коммуникации, думал одно, а написал другое.Нет. Написано одно, а прочитано другое. С проблемой коммуникации согласен.
Ну, да ладно. Не до полемик.
я думал 31 строчки ты имел ввиду вот эти http://gist.github.com/Test20130521/42d423948b51ecb1c92b/2c...
Я эту версию вообще не вижу в твоем посте с описанием рефакторинга. Впрочем, все равно, так как, на мой взгляд, она хуже чем та, на которую я ссылался - в функцию estimatePi попала работа с таплами.
Естественно, в моем коде функция MonteCarlo может работать с любыми случайными числами, в том числе, с диска.
Покажи, как работать со случайными числами, которые читаются с диска async функцией.
estimatePi random trials = do
fraction <- monteCarlo (cesaro random) trials
return (sqrt (6.0 /(fromRational fraction
monteCarlo test trials = do
testList <- (sequence $ replicate trials test)
return (fromIntegral (length(filter id testList / fromIntegral(length testList
cesaro random = do
x <- random
y <- random
return (gcd x y == 1)
Впрочем, все равно, так как, на мой взгляд, она хуже чем та, на которую я ссылался - в функцию estimatePi попала работа с таплами.Она туда не "попала". Я ее там написал, поскольку есть такая возможность, а именно, в C# есть анонимные лямбды. А вот реализовать интерфейс анонимным классом в C# нельзя, поэтому в коде с интерфейсом пришлось делать именованный класс. Естественно, всегда можно сделать именованным метод там, где лямбда.
Смахивает на то, что ты код не понимаешь, но уже готов его критиковать.
В условии задачи метод Монте-Карло сформулирован так:
The Monte Carlo method consists of choosing sample experiments at random from a large set and then making deductions on the basis of the probabilities estimated from tabulating the results of those experiments.
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-20.html
Это реализует функция MonteCarlo.
В условии задачи указан следующий алгоритм вычисления числа пи:
For example, we can approximate using the fact that 6/2 is the probability that two integers chosen at random will have no factors in common; that is, that their greatest common divisor will be 1.7 To obtain the approximation to , we perform a large number of experiments. In each experiment we choose two integers at random and perform a test to see if their GCD is 1. The fraction of times that the test is passed gives us our estimate of 6/2, and from this we obtain our approximation to
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-20.html
Его реализует функция EstimatePi.
Т.е. код разбит по функциям согласно условию задачи. К тому же, на статическом языке разбить код на функции по каким-либо другим критериям не проблема.
А ты демонстрируешь практически не формализуемое желание разбить код по некоторым "папочкам" согласно своему видению. Видение у каждого своё, и как разбивать на "папочки" тоже своё, поэтому каждый программист считает код другого говнокодом. Обычно таким страдают ООП-шники. А, на самом деле, проблемы просто нет.
Сейчас заметил, что запостил промежуточную версию кода на хаскелле, которая неправильно считает пи. Вот конечная версия:А на кофескрипте можешь написать без изменяемого состояния и без монад? В учебнике такой вариант есть.
Разумеется - в хаскеле монады и do-нотация нужны специально для того, чтобы не таскать стейт через цепочку вызовов, потому что иначе код абсолютно нечитабелен, например:Стримы и монады это разные вещи http://stackoverflow.com/questions/10489584/streams-vs-monad...
So, a stream is something that has an operation next: streamType -> (valueType streamType) to get the next value and the remaining stream.
И никто это не называет низкоуровневым.
А на кофескрипте можешь написать без изменяемого состояния и без монад? В учебнике такой вариант есть.У тебя кстати в коде монады есть - вот это например
public delegate Tuple<T, Stream<T>> Stream<T>
static class ChainExtensions
{
public static Stream<TResult> Chain<T, TResult>(this T it, Func<T, Tuple<TResult, T>> func)
{
return => {
var tuple = func(it);
return Tuple.Create(tuple.Item1, Chain(tuple.Item2, func;
};
}
}
является ad-hoc реализацией функции bind в монаде State.
является ad-hoc реализацией функции bind в монаде State.почему ad-hoc?
реализацией функции bind в монаде Stateоткуда ты это взял? Даже по сигнатуре метода нет совпадения.
А Stream это не Sate монада.
Чтобы тебя не смущал этот метод заинлайню его:
static double EstimatePi(int trials)
{
return Math.Sqrt(6/MonteCarlo(trials, Cesaro(Random(0;
}
static Stream<int> Random(int x)
{
return => Tuple.Create(x, Random(RandUpdate(x;
}
static Stream<bool> Cesaro(Stream<int> random)
{
return => {
var tuple1 = random;
var tuple2 = tuple1.Item2;
return Tuple.Create(Gcd(tuple1.Item1, tuple2.Item1) == 1, Cesaro(tuple2.Item2;
};
}
static double MonteCarlo(int trials, Stream<bool> experiment)
{
return MonteCarloIter(trials, trials, 0, experiment);
}
static double MonteCarloIter(int trials, int trialsRemaining, int trialsPassed,
Stream<bool> experiment)
{
if (trialsRemaining == 0) return (double) trialsPassed/trials;
var tuple = experiment;
var passed = tuple.Item1 ? trialsPassed + 1 : trialsPassed;
return MonteCarloIter(trials, trialsRemaining - 1, passed, tuple.Item2);
}
Код вычисления числа пи без обобщенного метода Монте-Карло:
function estimatePi(trials) {
return Math.sqrt(6 / randomGcdTest(trials, 11;
}
function randomGcdTest(trials, initialX) {
function iter(trialsRemaining, trialsPassed, x) {
if (trialsRemaining === 0) return trialsPassed / trials;
var x1 = randUpdate(x);
var x2 = randUpdate(x1);
return iter(trialsRemaining - 1, gcd(x1, x2) == 1 ? trialsPassed + 1 : trialsPassed, x2);
}
return iter(trials, 0, initialX);
}
function gcd(a, b) {
while (b != 0)
b = a % (a = b);
return a;
}
function randUpdate(x) {
return (27 * x + 26) % 127;
}
Код без состояния и с обобщенным методом Монте-Карло:
function estimatePi(trials) {
function random(x) {
return function {
var value = randUpdate(x);
return { value: value, next: random(value) };
};
}
function cesaro(rand) {
return function {
var result1 = rand;
var result2 = result1.next;
return {
value: gcd(result1.value, result2.value) == 1,
next: cesaro(result2.next)
};
};
}
return Math.sqrt(6 / monteCarlo(trials, cesaro(random(11;
}
function monteCarlo(trials, experiment) {
function iter(trialsRemaining, trialsPassed, next) {
if (trialsRemaining === 0) return trialsPassed / trials;
var result = next;
return iter(trialsRemaining - 1, result.value ? trialsPassed + 1 : trialsPassed, result.next);
}
return iter(trials, 0, experiment);
}
function gcd(a, b) {
while (b != 0)
b = a % (a = b);
return a;
}
function randUpdate(x) {
return (27 * x + 26) % 127;
}
Это называется IDE код - когда кода много, но когда открываешь его в произвольном месте, не можешь понять намерения автора.
Перевел js код на статический TypeScript, см. ниже. Читаемость только улучшилась. Теперь глядя на сигнатуру метода monteCarlo сразу видно, как пользоваться этим методом, что нужно передать в аргумент experiment.
interface Stream<T> {
: { value: T; next: Stream<T>; }
}
function estimatePi(trials: number) {
function random(x) {
return => {
var value = randUpdate(x);
return { value: value, next: random(value) };
};
}
function cesaro(rand: Stream<number>) {
return => {
var result1 = rand;
var result2 = result1.next;
return {
value: gcd(result1.value, result2.value) == 1,
next: cesaro(result2.next)
};
};
}
return Math.sqrt(6 / monteCarlo(trials, cesaro(random(11;
}
function monteCarlo(trials: number, experiment: Stream<boolean>) {
function iter(trialsRemaining: number, trialsPassed: number, next: Stream<boolean>) {
if (trialsRemaining === 0) return trialsPassed / trials;
var result = next;
return iter(trialsRemaining - 1, result.value ? trialsPassed + 1 : trialsPassed, result.next);
}
return iter(trials, 0, experiment);
}
откуда ты это взял? Даже по сигнатуре метода нет совпадения.
Да, я ошибся
Оставить комментарий
luna89
В настоящее время разработка большинства программ выглядит так:1)программист вносит изменения в код
2)возможно, компилирует его
3)перезапускает программу, смотрит на полученные изменения
4)goto 1
При этом, с давних времен, известна REPL-разработка. Насколько мне известно, она выглядит так: разрабатываемая программа никогда не перезапускается. Можно подключиться к процессу, в котором запущена программа, и выполнить в нем eval кода. Текстовый редактор подключен к этому процессу. Программист пишет функцию, жмет хоткей, и в программе выполняется горячая замена кода этой функции.
Такой подход был распространен в лиспе. Я где-то читал, что в лисп-машинах можно было работать над ядром прямо в той же ОС, где был запущен текстовый редактор.
Если сравнивать два подхода, то второй кажется более интерактивным. Например, в емаксе можно написать плагин, ни разу его не перезапустив. В IDE Eclipse для разработки плагинов нужно два эклипса - в одном мы будем писать плагин, в другом запускать и тестировать.
Почему REPL-разработка не победила в исторической перспективе?