Пользователь
0,0
рейтинг
8 января 2013 в 18:19

Разработка → Изменение кода программы во время ее выполнения на примере Common Lisp из песочницы


Введение


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

Так давайте попытаемся написать программу, которую проблематично будет создать на других языках. Как следует из названия статьи, эта программа будет редактировать собственный код по мере своего выполнения. Для ее создания я использую Common Lisp, а точнее его интерпретатор SBCL.


Примечание: если вы возьмете чистый SBCL, то у него есть свойство не флашить поток stdio после каждого вывода, я сам пользуюсь SBCL через Emacs и Slime, там этой проблемы нет. Адекватного решения пока не нашел.

Каким образом?


Lisp обладает замечательными свойствами, среди которых хочется отметить очень простой синтаксис и однородность кода. Самое классное то, что любой код на Lisp является данными для него же, и многие данные могут являться кодом. В частности, это дает возможность писать автоаппликативные (применимые сами к себе) и авторепликативные (воспроизводящие себя) функции. Чуть-чуть о них написано здесь; информация в свою очередь взята из книги «Мир Лиспа» (Э. Хювёнен, И. Сеппянен) том 1, страница 280. Например, квайн, который является одновременно и автоаппликативной, и авторепликативной функцией, записывается так:

((lambda (x) (list x (list 'quote x))) 
	'(lambda (x) (list x (list 'quote x))))

При интерпретации, естественно, выводит себя же.
А почему бы не написать функцию, возвращающую не точную копию себя, а какую-то новую версию, которая в последующем будет снова исполнена, чтобы составить еще более мутировавший вариант? Так и поступим.

Командный процессор


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

Вся соль будет в том, что исходный (во всех смыслах) код будет очень минималистичен. Как только мы его скормим интерпретатору, мы более не вернемся к непосредственной работе с REPL-ом. Наша программа будет непрерывно выполняться и развиваться (или деградировать) со временем благодаря нашим командам.

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

(defun start-program ()
	'(lambda (program) 
		(princ "$> ") 
		(let ((input (read)))
			(when (listp input)
				(let ((command (car input)))
					(cond
						((eq command 'code)  ; если команда = code,
							(setf program 	 ; то обновим программу
								(funcall     ; результатом того, что ввел пользовтаель
									(eval (list 'lambda '(program input) (cadr input))) 
								program input) 
							)
						)
						(t (format t "Unknown command ~A ~%" command))
					)
				)
			)
			program
		)
	)
)

Наш начальный код мало чего умеет. Он выводит приглашение для пользователя, считывает с клавиатуры s-выражение, если оно список, то первый элемент — имя команды, остальные — ее аргументы. Командный процессор «из коробки» умеет обрабатывать только одну единственную команду «code». Эта команда берет свой первый аргумент и создает из него лямда-функцию, применяемую к program и input. Зачем так сложно? Потому что eval делает свои грязные делишки в нулевом лексическом окружении, а значит локальные переменные program и input нам не доступны, хотя могут потребоваться. Нужно найти способ их передать внутрь eval; ничего лучше лямды с параметрами мне в голову не пришло. Не исключаю, что есть и более изящные способы. Результат внутреннего eval присваивается переменной program. Вообще можно было б просто присвоить program остаток от input, но в этом случае мы не сможем исполнять дополнительные функции при использовании команды code. О них чуть позже.
Следующая функция main организует цикл работы программы. Вся магия таится именно в ней.

(defun main ()
	(let ((program (start-program))) ; задаем начальную программу
		; цикл, пока программа не пустая, т.е. не nil
		(loop while program do 
			; своеобразный try - catch, увеличивает стабильность программы, 
			; если итерация дает ошибочный код
			(handler-case 
				; присвоим переменной program результат выполнения фукции, 
			        ; содержащейся в переменной program с параметром program
				(setf program (funcall (eval program) program))
				; в случае ошибки, сообщим о ней
				(error (c1) (format t "Error! ~A~%" c1) )
			)
		)
	)
)


Итак, цикл идет, пока программа (переменная program) не пуста. Программа же на нулевой итерации — результат функции start-program, а на ненулевой итерации мы присваиваем ей (переменной program) результат выполнения функции, содержащейся в ней (переменная program), с параметром program (опять она же). Это не тавтология, и важно понять как оно работает, прежде чем двигаться дальше. Разобрались? Теперь запускаем в REPLmain и видим приглашение:

CL-USER> (main)
$> 

Можно творить. Если надоест и захочется назад в REPL, достаточно выполнить нашу единственную пока команду code в параметром nil. Она заменит исходный текст на nil, и цикл воспримет это как условие завершения. Но мы, конечно, не будем пока этого делать. Отныне все команды вводятся уже в нашу работающую программу. Я буду часто опускать "$> " для удобства копирования.

На текущем этапе программировать наш командный процессор сложно и неудобно. Нам нужно вызвать команду code и передать ей новый исходный код целиком. Но выхода нет. Давайте создадим команду eval, которая начнет решать проблемы. Она позволит нам исполнить любой код, в частности задать новые функции.

(code
	'(lambda (program)
		(princ "$> ") 
		(let ((input (read)))
			(when (listp input)
				(let ((command (car input)))
					(cond
						((eq command 'code) 
						    (setf program 
						        (funcall 
						            (eval (list 'lambda '(program input) (cadr input))) 
						         program input)))
						((eq command 'eval) (eval (cadr input))) ; НОВОЕ
						(t (format t "Unknown command ~A ~%" command))
					)
				)
			)
			program
		)
	)
)

Возможно, оно выдаст STYLE-WARNING, это не страшно. Проверим:

$> (eval (print (+ 3 2)))

5 $>

Вуаля, работает!
Приправим систему с помощью трех функций (rsubs, rrem, rins). rsubs (Recursive substitution) заменяет рекурсивно старую форму (первый параметр old) на новую (второй параметр new) в форме (третий параметр form). rrem (Recursive remove) удаляет форму (первый параметр what) из формы (второй параметр form) тоже рекурсивно. Наконец, rins (Recursive insert) вставляет рядом в формой (первый параметр where) форму (второй параметр what) в форме (третий параметр form), причем если указан ключ :before t, то вставка выполняется перед формой where, иначе — после нее. Придется выполнить три команды.

Посмотреть три команды
(eval 
	(defun rsubs (old new form)
		(cond
			((atom form) (if (equal form old) new form))
			((equal form old) new)
			(t (loop for el in form collect (rsubs old new el)))
		)
	)
)

(eval 
	(defun rrem (what form)
		(cond
			((atom form) (if (equal what form) nil form))
			(t (loop for el in form 
				if (not (equal what el))
				collect (if (listp el) (rrem what el) el)
			))
		)
	)
)

(eval
	(defun rins (where what form &key before)
		(cond 
			((atom form) form)
			(t (loop for el in form append 
				(if (equal el where) 
					(if before (list what el) (list el what))
					(if (listp el) 
						(list (rins where what el :before before))
						(list el)
					)
				)
			))
		)
	)
)

Уже можно добавлять новые команды в наш процессор более красивым способом, как раз благодаря тому, что code не просто замещает код на свой аргумент, а предварительно исполняет его. Добавим команду view, которая выведет нам содержание переменной program. Весьма полезная команда для отслеживания изменений кода. Как видно, здесь происходит вставка нового кода после атома cond.

(code (rins 'cond '((eq command 'view) (progn (format t "---code---") (print program) (terpri))) program))

Тестируем, должно получиться что-то вроде этого:

Пример вывода команды (view)
$> (view)
---code---
(LAMBDA (PROGRAM)
  (PRINC "$> ")
  (LET ((INPUT (READ)))
    (WHEN (LISTP INPUT)
      (LET ((COMMAND (CAR INPUT)))
        (COND
         ((EQ COMMAND 'VIEW)
          (PROGN (FORMAT T "---code---") (PRINT PROGRAM) (TERPRI)))
         ((EQ COMMAND 'CODE)
          (SETF PROGRAM
                  (FUNCALL
                   (EVAL (LIST 'LAMBDA '(PROGRAM INPUT) (CADR INPUT)))
                   PROGRAM INPUT)))
         ((EQ COMMAND 'EVAL) (EVAL (CADR INPUT)))
         (T (FORMAT T "Unknown command ~A ~%" COMMAND)))))
    PROGRAM)) 
$> 

Отлично! Теперь нам несложно добавить команду, которая будет добавлять команды. Ну, как иначе это назвать? Синтаксис команды будет таков: (add-cmd имя-новой-команды что-она-будет-делать). Но тут нас ожидает пикантная ситуация. В предыдущий раз мы вставляли код новой команды после атома cond, потому что это было проще всего. Имя это распространенное, и если в тексте появится еще одно его упоминание, то вставка будет выполнена и там, что нарушит работу тех частей, которые мы не собирались трогать. Решить эту проблему можно многими способами, например, ввести уникальный маркер и вставлять новые команды после него. Маркер будет представлять собой невыполнимое условие, которое мы добавим после cond:

(code (rins 'cond '((eq t nil) 'secret-marker) program))


Готово. Теперь будем передавать в rins местоположение вставки с помощью данного маркера. Громоздкость маркера ничего не значит, ведь мы создадим команду, которая будет его знать, и нам будет уже необязательно его помнить. Кстати, нельзя, чтобы код команды add-cmd использовал определение маркера, иначе rins найдет и поломает его. Можно попытаться обмануть rins, исказив маркер, но гораздо проще просто вынести в отдельную внешнюю функцию (rins по ним не ищет). Функция add-command-to-program принимает первым параметром программу program и возвращает ее обновленной, дополнив ее новой командой command, выполняющей действие action:

(eval
	(defun add-command-to-program (program command action)
		(rins '((eq t nil) 'secret-marker) ; после маркера   
			`((eq command ',command) ,action) ; применим квазицитирование
			program
		)
	)
)

Собственно создаем команду add-cmd.

(code 
	(rins '((eq t nil) 'secret-marker) 		; вставляем после маркера следующее
		`((eq command 'add-cmd)     	; имя новой команды add-cmd
			(setf program (add-command-to-program program (cadr input) (caddr input)))
		 ) 
		program
	)
) 

Замечательно! Теперь нет ничего проще, чем добавить новые команды (две последние из них лучше пока не запускать):

(add-cmd hi (princ "Hi, "))
(add-cmd quit (setf program nil))
(add-cmd reset (setf program (start-program)))

Более полезными окажутся возможности по сохранению в файл программы и последующей загрузки ее из файла. Определим соответствующие команды save и load:

(add-cmd save (with-open-file (stream (cadr input) :direction :output :if-exists :overwrite :if-does-not-exist :create) (print program stream)))
(add-cmd load (setf program (with-open-file (stream (cadr input)) (read stream))))

Теперь мы можем сохранять наши наработки в любой текстовый файл и загружать их оттуда. Но следует помнить, что мы сохраняем и загружаем только содержимое program; все функции, определенные нами командой eval + defun, в этих файлах не сохраняются, они хранятся в памяти интерпретатора. Исправить это досадное недоразумение можно, но мы не будем сейчас этого касаться.

$> (save "1.txt")
$> (load "1.txt")


Кастомизация



Для разнообразия займемся кастомизацией нашего диалога. Например, добавим забавные приветствия функцией:

(eval
	(defun greeting ()
		(let 
			((sentences (vector
				"My life for Ner'zhul. "
				"I wish only to serve. "
				"Thy bidding, master? "
				"Where shall my blood be spilled? "
				"I bow to your will. "
			)))
			(elt sentences (random (length sentences)))
		)
	)
)

Теперь применим их в командном процессоре:

(code (rsubs '"$> " '(greeting) program))

Получится нечто вроде:

I bow to your will. (hi)
Hi, I wish only to serve. 

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

(code (rsubs '(read) '(read-from-string (concatenate 'string "(" (read-line) ")")) program))

Результат:

Thy bidding, master? hi
Hi, Where shall my blood be spilled?

Давайте напоследок еще раз взглянем на код:

Окончательный результат

Thy bidding, master? view
---code---
(LAMBDA (PROGRAM)
  (PRINC (GREETING))
  (LET ((INPUT
         (READ-FROM-STRING (CONCATENATE 'STRING "(" (READ-LINE) ")"))))
    (WHEN (LISTP INPUT)
      (LET ((COMMAND (CAR INPUT)))
        (COND ((EQ T NIL) 'SECRET-MARKER)
              ((EQ COMMAND 'LOAD)
               (SETF PROGRAM
                       (WITH-OPEN-FILE (STREAM (CADR INPUT))
                         (READ STREAM))))
              ((EQ COMMAND 'SAVE)
               (WITH-OPEN-FILE
                   (STREAM (CADR INPUT) :DIRECTION :OUTPUT :IF-EXISTS
                    :OVERWRITE :IF-DOES-NOT-EXIST :CREATE)
                 (PRINT PROGRAM STREAM)))
              ((EQ COMMAND 'RESET) (SETF PROGRAM (START-PROGRAM)))
              ((EQ COMMAND 'QUIT) (SETF PROGRAM NIL))
              ((EQ COMMAND 'HI) (PRINC "Hi, "))
              ((EQ COMMAND 'ADD-CMD)
               (SETF PROGRAM
                       (ADD-COMMAND-TO-PROGRAM PROGRAM (CADR INPUT)
                                               (CADDR INPUT))))
              ((EQ COMMAND 'VIEW)
               (PROGN (FORMAT T "---code---") (PRINT PROGRAM) (TERPRI)))
              ((EQ COMMAND 'CODE)
               (SETF PROGRAM
                       (FUNCALL
                        (EVAL
                         (LIST 'LAMBDA '(PROGRAM INPUT) (CADR INPUT)))
                        PROGRAM INPUT)))
              ((EQ COMMAND 'EVAL) (EVAL (CADR INPUT)))
              (T (FORMAT T "Unknown command ~A ~%" COMMAND)))))
    PROGRAM)) 
My life for Ner'zhul. 

С этой штукой можно развлекаться сколько угодно! Но на сегодня хватит.
Максим Кольцов @tirinox
карма
3,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (23)

  • +15
    я понимаю, почему на момент написание этого моего комментария здесь не было ни одного другого.
  • +7
    лисп – он как коммунизм, его время ещё не пришло, но он всегда на горизонте, а мы к нему дружно идём.
  • +1
    Решить эту проблему можно многими способами, например, ввести уникальный маркер и вставлять новые команды после него. Маркер будет представлять собой невыполнимое условие, которое мы добавим после cond

    Заведомо невыполнимую ветку для cond проще записать как

    (nil 'secret-marker)

    Экономится один вызов eq при вызове, да и читать проще.
    • 0
      Согласен. Спасибо за замечание, упустил как-то эту очевидную деталь.
    • +1
      Хотя нет, при вызове ничего не экономится, компилятор же эту строчку все равно выбросит как заведомо невыполнимую.

      И вот неточность еще заметил:
      Итак, начнем с функции, возвращающей лямда-функцию, которая представляет собой начальный код. Эта лямда-функция принимает, как единственный параметр, свой же исходный код и отдает в качестве результата также какой-то код…

      Возвращает-то она, по сути, не лямбда-функцию, а s-выражение, являющееся лисп-кодом. Возврат лямбда-функции — это, например

      (defun return-adder (n)
        (lambda (x) (+ x n)))
      
      (defun add3 (x)
        (funcall (return-adder 3) x))
      

      Здесь return-adder возвращает лямбда-функцию, которая используется в функции add3.

      Соответственно, вы можете start-program вполне определить через defconstant или простым setf.
      • 0
        Хотя нет, при вызове ничего не экономится, компилятор же эту строчку все равно выбросит как заведомо невыполнимую.

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

        Возвращает-то она, по сути, не лямбда-функцию, а s-выражение, являющееся лисп-кодом.

        Да. Quote там нужен, чтобы выводился именно код, а не что-то вроде #<FUNCTION (LAMBDA (X)) {100A741ECB}>.

        Соответственно, вы можете start-program вполне определить через defconstant или простым setf.

        Изначально подразумевались еще некоторые параметры для выбора варианта начального кода, поэтому осталось defun…
        • 0
          Все равно, ему как минимум придется рассмотреть многократно эту ситуацию во время выполнения программы, даже если фактически он не будет выполнять эту операцию. Ведь Лиспу совершенно неизвестно, что скормят ему в eval.

          Да, получается, компилятору больше работы на понимание того, что (eq t nil) и просто nil есть одно и то же. Т.е. экономия все-таки есть. Вот ведь жаркий спор с самим собой вышел.
          Quote там нужен, чтобы выводился именно код, а не что-то вроде #<FUNCTION (LAMBDA (X)) {100A741ECB}>

          А до меня это дошло только после того, как запустил (start-program) в toplevel. До этого никак не мог понять, как это переменная инициируется значением, получаемым после применения функции к этой самой переменной. Картинка с Уроборосом, конечно, очень в тему тут.
  • +1
    (Не холиваров ради, правды для) Я бы не стал так категорично:
    Так давайте попытаемся написать программу, которую проблематично будет создать на других языках.
    Я вам назову дюжину языков на которых это можно написать, кстати не только не «проблематично» а много проще, красивее и что главное удобочитаемо
    На лямбдах например на питоне, на чистых процедурах (или лямбдах) на тикле, да на том же javascript.

    Кстати, кому интересно, набросал подобное на tcl, развернуть ...
    Программа на tcl ...

    proc program {args} {
      ## save current :
      set self [info body program]
      ## create each new code, evaluate it :
      foreach code $args {
        proc program {args} $code
        program {*}$args
      }
      ## restore self (if needed):
      ## proc program {args} $self
      ##TO_UNCOMMENT puts " !!!! it was a comment ..."
    }
    
    # draw code of program:
    puts start------code-----[info body program]-----------\n
    program {
      #1st program: i=1, print somthing with i
      set i 1
      puts "Program No. $i\n"
    } {
      #2nd program: i=2, print somthing with i, modify self again - print current body, execute and modify again :
      set i 2
      puts "Now No. $i"
      # expand previous self :
      set code [string map {{##TO_UNCOMMENT } {}} [uplevel set self]]
      append code {
      puts " !!!! added code ..."
      }
      proc program {args} $code
      puts ...------code-----[info body program]-----------\n
      program {
        #third program: i=3, print somthing with i
        set i 3
        puts "No. equal $i\n"
      }
    }
    # draw last code of program:
    puts \nend------code-----[info body program]-----------\n
    

    Результат исполнения:

    start------code-----
      ## save current :
      set self [info body program]
      ## create each new code, evaluate it :
      foreach code $args {
        proc program {args} $code
        program {*}$args
      }
      ## restore self (if needed):
      ## proc program {args} $self
      ##TO_UNCOMMENT puts " !!!! it was a comment ..."
    -----------
    
    Program No. 1
    
    Now No. 2
    ...------code-----
      ## save current :
      set self [info body program]
      ## create each new code, evaluate it :
      foreach code $args {
        proc program {args} $code
        program {*}$args
      }
      ## restore self (if needed):
      ## proc program {args} $self
      puts " !!!! it was a comment ..."
    
      puts " !!!! added code ..."
      -----------
    
    No. equal 3
    
     !!!! it was a comment ...
     !!!! added code ...
    
    end------code-----
        #third program: i=3, print somthing with i
        set i 3
        puts "No. equal $i\n"
      -----------
    

    И это все jit компилится в байт код.

    А добавив функцию save, пользовательский ввод и загружая это через "source 1.txt" получим тоже что и у вас.


    А вообще такие вещи используются как правило для расщирения примитивов… (например написать собственный «while» или «foreach» работающие в текущем scope подпрограммы).

    Lisp я тоже уважаю, но конкретно вашу задачу, я бы делал на чем-то другом…
    • 0
      Слабо знаком с tcl, честно говоря, поэтому ваш пример для меня тяжело воспринимается. Постараюсь разобраться на досуге.
      Почему Lisp? Тут у нас возникает задача, когда нужно собрать рекурсивно код для исполнения из мельчайших элементов, взятых из БД по определенным критериям. Тут очень спасает простота синтаксиса Лиспа: фактически одни скобки. Рассматривал вариант на Lua и на JS, мне они показались значительно менее пригодными.
  • 0
    Заранее прошу прощения за офтоп.

    Никак не пойму, как количесво проголосовавших за статью может быть дробным? например «всего 28: 25.5 + и 2.5 -»?
    • 0
      read-only голоса считаются по 0.5 скорее всего.
      • 0
        Рид онли — это рид онли. +-0.5 это просмотр результата без голосования.
        • 0
          тогда в каком случае прибавляются, а в каком вычитаются эти 0.5 при просмотре без голосования?
          • 0
            Одновременно. Бал делится и туды и сюды.
  • 0
    Второй блок кода (начинается с "(defun start-program ()") совершенно нечитаем. Наверное, его надо по-другому отформатировать, — сейчас создается ощущение, что кто-то в перловом однострочнике наставил переводов строк. :)
  • +4
    Увидел две страшных неправды в тексте, которые отбили всё желание читать:

    1. «SBCL — интерпретатор»
    2. Форматирование!!!111


    Правда заключается в следющем:

    1. SBCL — компилятор! Ком-пи-ля-тор. В машинные коды!
    2. Лисп так форматировать нельзя!!!11 Нель-зя!!1 Закрывающие скобки нужно оставлять на той же строчке!!11


    Можно было хотя бы основы то выучить, прежде чем писать статью на популярный ресурс?

    Дальше не читал, и другим не советую. По крайней мере до тех пор, пока автор не подтянет знания.
    • +1
      Интерпретатор компилирующего типа тогда. В данном случае SBCL используется как интерпретатор. И называю я его так не по глубинному смыслу, а чисто по внешней функции.

      Форматирование я намеренно оставил свое.
      (я
          (знаю
               (что
                    (принято
                         (так)))))
      

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

      P.S. Я знаю, что вы спец в Лиспе, и рассчитывал от вас на более конструктивную критику.
      • +2
        Нет, SBCL НЕ интерпретатор. Еще раз. Это компилятор. (хотя в этой реализации CL и есть интерпретатор, он отключен по дефолту).

        Функция интерпретатора в том чтобы выполнять программу по ходу разбора текста. А SBCL, даже в eval, сначала всё компилирует. Это компилятор и точка. Выполняются — машкоды, и процессором, а не самим SBCL.

        Семантика лиспа, связанная с генерацией кода на лету, называется не интерпретация, а «метациклический вычислитель».

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

        Вышесказанное относится и к стилю кода.
        Писать на лиспе, расставляя скобки как на Си — нельзя. Это очень плохой стиль. Это надо понять, и не надо вести себя как первокурсник, который противится форматированию кода лесенкой и пишет без отступов.

        Язык может много что позволять, но это не значит, что это надо делать(Си, например, позволяет разыменовывать невалидные указатели и не освобождать динамическую память)
  • 0
    С интерпретатором получается нечестно, задача интересней выглядит для компилируемых языков. Сможет ли какой-нибудь компилятор лиспа переварить эту программу? Получается, что с собой надо таскать компилятор и специальный «загрузчик» который будет заменять обновленные части программы в dll/so модулях. Мало того, такая программа будет вынужденно open-source, так как в памяти придется держать все исходники.
    • 0
      Честно, когда писал свой коммент, коммента выше не было.
    • 0
      Насколько мне известно SBCL вот таскает за собой себя же целиком. При компиляции в исполняемый файл, последний даже для «helloworld» получается размером около 50 Мбайт, без компромиссов.

      Часть исходников может загржуться из зашифрованных файлов и сразу компилироваться, не болтаясь в памяти в виде текстов/списков. Потом обфускация и все такое. JS на сайтах тоже фактически OpenSource, как не крути.

      Да, конечно, совокупность факторов в нашем безумном денежном мире результирует в то, что эта технология не распостранена, но рано или поздно, ей суждено будет явиться, когда программы будут анализировать сами себя и корректировать свою работу без участия человека.
      • 0
        А шифрование и обусфикация может быть отключена (настроена), чтобы если я и так open-source, не получать замеделение из-за шифрования и выполнения обусфицированного кода?
  • +1
    Может читал невнимательно, но мне показалось, что пример для самоизменяющейся программы какой-то неудачный. По сути все завязано на eval, который есть в куче других языков. Хотелось бы что-то самобытное на лиспе посмотреть.

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