LINUX.ORG.RU

Lisp: Где применимы cons?

 cons,


1

6

Для начала небольшой «бенчмарк», С без всех оптимизаций в 7406000 раз быстрее.

(defun main ()
  (declare (optimize (speed 3)))
  (let ((n 99999) (l '()) (sum 0))
    (loop for i from 0 to n
	  do (setq l (append l (list i))))
    (setq sum 0)
    (loop for i from 0 to n
	  do (setq sum (+ sum (nth i l))))
    (format t "~d~%" sum)))
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	long n = 99999, *l = NULL, count = 0, sum = 0;
	for (long i = 0; i <= n; i++) {
		l = realloc(l, (count + 1) * sizeof(long)); 
		l[count++] = i;
	}
	for (long i = n; i >= 0; i--) sum += l[i];
	printf("%ld\n", sum);
}

Для чего же нужны cons? В качестве универсального строительного блока, я считаю это одна из самых худших структур. Все ее преимущества заканчиваются на быстром добавлении в начало. Добавление в конец уже нежелательно, разрез в произвольном месте тоже, так как нету даже быстрого доступа к случайному элементу. Она медленная и неудобная, можно придумать кучу более быстрых и удобных структур. Даже JS на световые годы опережает Lisp со своим JSON, и его частое использование лишь подтверждает его удобство.

Так почему же cons из языка-ассемблера IPL 1956 года считается важным? Да, это неплохая структура для AST, если ваша машина имеет 16 кб памяти, но она распространилась по языку слишком широко.

★★★★★

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

но ты идею мне кажется не так понял

Понял. Я просто указал, что этими же словами, но с другими переносами можно увидеть совсем другую программу. Типа

CreateFileDescriptor desc = new CreateFileDescriptor("C:\ya-i-kot.jpeg"); 
Mode mRead = new Mode("READ");
Mode mBinary = new Mode("BINARY");
mBinaryUTF= new Encoding(mBinary, "UTF-8");
FILE f = OpenFile(desc, mRead, mBinaryUTF);
monk ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Вызов here до начала создания дерева вернет адрес начала массива, here после создания дерева вернет адрес конца массива

А если что-то ещё аллоцировали, а потом в дерево элемент добавили, тогда это что-то тоже попадёт в «массив дерева».

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

в языках типа Си без отдельного булевого типа

Так добавили, лет 30 назад.

Про Форт не скажу, но мне кажется, Форт - прежде всего для оптимизации по размеру, этакий ассемблер.

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

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

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

Кстати, пока читал про Lisp, понял что там хвостовая рекурсия намного более применима из за GC, не надо вызывать деструкторы в конце функции, после финального вызова который можно оптимизировать. Правда я так и не понял, почему все еще требуется превращать обычные рекурсии в хвостовые руками, где сила редактирования AST?

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

В таком случае надо заранее аллоцировать

Так смысл дерева именно в том, что заранее неизвестно количество элементов.

Либо вести массив указателей на ноды.

Это можно.

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

Согласен.

почему все еще требуется превращать обычные рекурсии в хвостовые руками, где сила редактирования AST?

Как только кто-нибудь напишет оптимизатор, который AST с рекурсией преобразует в AST с хвостовой, так сразу.

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

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

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

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

А по моему сильно, и еще понятно стоит ли передавать через регистр или через указатель.

А какая программисту разница? Для программиста надо знать, откуда это значение получить и куда можно положить. То, что к дескриптору можно применить арифметические функции, ему никак не поможет.

но проверку на тип в JavaScript видел. Функции начинаются с if typeof …

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

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

А какая программисту разница?

Ну если структура большая, то будет не только тормозить, но и возможно крашить стек.

Там, где есть тип, всё равно в начале функции проверяют, какое именно значение прилетело.

В JavaScript невозможно указать тип.

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

Ну если структура большая, то будет не только тормозить, но и возможно крашить стек.

Если это структура, то на стек будет возвращён указатель на неё.

В JavaScript невозможно указать тип.

Сам же писал, что есть typeof. И принципиальной разницы в проверке

int f(int x)
{
  if (x < 42 || x > 69) return -1;
  ...
}

и

function f(x)
{
  if (typeof(x) != "number" || x < 42 || x > 69) return -1;
  ...
}

нет.

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

Если это структура, то на стек будет возвращён указатель на неё.

Если возвращается указатель то да, а если нет то в С/C++ это будет копирование по стеку.

Сам же писал, что есть typeof.

typeof на неизвестную переменную как раз противоречит статической типизиации о которой я говорил.

И принципиальной разницы в проверке

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

type 
  HumanAge = 0 .. 150;

function f(x: HumanAge);
begin
  ...
end;

function f2(x: HumanAge);
begin
  ...
end;

Типы и работа с Enum, массивами, множеством и дипазонами это то чего мне не хватает в С. И провоцирует программистов на эпичные костыли как в Windows с количеством процессоров, когда они сделали битовую маску из uint64_t и не смогли ее расширить.

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

В С/C++ это будет копирование по стеку.

Посмотри, как передаётся структура в fopen/fprintf/fclose.

как раз противоречит статической типизации о которой я говорил

Слова «статический» не было. У статической типизации свои проблемы: как статически описать

function f(a, n)
{
  if (!Array.isArray(a) || typeof(x) != "number" || x < 0 || x >= n.length()) return -1;
  ...
}

?

Или какой-нибудь mapcar c типом «function — a designator for a function that must take as many arguments as there are lists.»

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

Посмотри, как передаётся структура в fopen/fprintf/fclose.

По указателю, как я и сказал.

Слова «статический» не было. У статической типизации свои проблемы: как статически описать

Мы уже обсуждали эту тему, надо как можно раньше преобразовывать свои данные в статические.

Или какой-нибудь mapcar

Да, ты его в прошлый раз приводил в пример.

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

Для этого придумали C++.

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

Я не читал стандарт, но по опыту программирования на С не знаю чем отличается тип enum от типа который его предоставляет, int например, оба вмещают INT_MIN..INT_MAX, имеют тот же размер, принимают разные значения вне зависимости от определенных констант.

Работы с множеством там нету кроме плохого класса std::set, и нельзя задать индексом enum, с массивом тоже самое, std::array даже не проверяет неправильные индексы. В Ada легко создать массив из boolean с индексом который равен enum, и он будет преобразован в битовую маску с проверкой значений на момент компиляции.

Работы с диапазонами просто нету, как и в Rust, в Rust выдумывают новые типы на уровне компилятора, например NonZero, вместо возможности указать 1..INT_MAX.

А все это было уже очень давно в Ada, Pascal.

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

А все это было уже очень давно в Ada, Pascal.

В Ada всё, что можно, сделали в компиляторе. В Си++ пошли по пути лиспа и форта: вот вам доступ к настройке компилятора, сварганьте себе сами чего-нибудь.

Там тоже enum это лишь набор констант

Там нельзя в enum присвоить число.

нельзя узнать следующую константу после текущей, нельзя их перебрать, преобразовать в строку

Можно как-то так:

namespace MyEnum
{
  enum Type
  {
    a = 100,
    b = 220,
    c = -1
  };

  const Type All[] = { a, b, c };
  const string Names[] = { "a", "b", "c" };
}
monk ★★★★★
()
Ответ на: комментарий от monk

Там нельзя в enum присвоить число.

Можно, если конверсию типов вставить, а она нужна для множеств (битовых маск) на основе Enum.

В Ada неправильная конверсия проверяется, в С++ нет.

Можно как-то так:

И как получить имя по Type::c? Делать линейный поиск по массиву All сначала? Неудобненько.

Вторая проблема, если это будет в .h файле, то будет ругаться на множественную декларацию, а если спрячешь в .cpp то нельзя будет взять размер массива через sizeof.

Третья проблема, если Enum длинный, то добавление значения в середину может быть ошибочным, и будет неприятная ошибка когда значения в таблицах будут сбиты. Ну и в целом строк больше в 3 раза.

Четвертая проблема, это никак не помогает в создании массива с индексами из Enum, тех же битовы маск из boolean.

В Ada всё, что можно, сделали в компиляторе.

Сделали необходимый минимум, и то не все учли.

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

Делать линейный поиск по массиву All сначала? Неудобненько.

Это достаточно быстро для того количества элементов.

Вторая проблема, если это будет в .h файле, то будет ругаться на множественную декларацию

Можно static добавить. Заодно будет выкидываться, если не используется.

массива с индексами из Enum

Массивы всё равно с числовыми индексами. Из Enum порядковый номер ищется.

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

Сделали необходимый минимум, и то не все учли.

Вот поэтому в Си++ решили даже этим не заморачиваться. Сколько ни сделай, будет не хватать.

Хотя нет, был один язык, куда добавили всё: PL/1 назывался. Из-за этого и умер.

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

Массивы всё равно с числовыми индексами. Из Enum порядковый номер ищется.

А нужно что бы массив принимал только enum для индекса.

Вот поэтому в Си++ решили даже этим не заморачиваться. Сколько ни сделай, будет не хватать.

Лучше иметь нехватающие А, Б, Ц, чем только А.

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

В С++ принцип если тебе что-то нужно то сделай это вручную. Заодно проконтролируешь важные для тебя нюансы. Соответственно на это тратится очень много времени. И часто можно сделать быстрее задачу на другом языке с батарейками.

Мне понравился вариант как сделали Enumeration в языке Swift. Много, что вы хотели бы видеть там из коробки.

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

Мне понравился вариант как сделали Enumeration в языке Swift. Много, что вы хотели бы видеть там из коробки.

В PHP пошли еще дальше и разделили Enum на pure (обычный) и baked с запеченными индексами кейсов, причем индексы могут быть не только int, но и string. Обращение к индексу идет через value:

enum Users: string
{
    case Monk      = 'lisp';
    case Morkovka  = 'cpp';    
    case Necromant = 'swift';
    case Obezyan   = 'php';
}

print Users::Necromant->value;
// swift
Obezyan
()
Последнее исправление: Obezyan (всего исправлений: 3)
Ответ на: комментарий от Obezyan

В свифте даже оператор ‘=’ можно опустить, автоматом имя преобразуется в строковый литерал.

enum Users: String, CaseIterable {
    case Monk
    case Morkovka
    case Necromant
    case Obezyan
}

print(Users.Necromant)

for user in Users.allCases {
    print("\(user)");
}

При этом enum является фактически классом и в него можно напихивать методы и расширять его как вздумается.

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

Заставить макрос влиять на другой макрос почти невозможно.

Но тут и не надо макросом на макрос влиять, проверки-то рантаймовые.

(defvar *python-condition* nil)

(defmacro python (&rest body)
  `(let ((*python-condition* nil))
     ,@body))

(defmacro py-if (test &rest body)
  (let ((cnd '*python-condition*))
    `(if ,test
         (progn
           (setq ,cnd nil)
           (let ((,cnd nil))
             ,@body))
       (setq ,cnd t))))

(defmacro py-elsif (test &rest body)
  (let ((cnd '*python-condition*))
    `(if ,cnd
         (if ,test
             (progn
               (setq ,cnd nil)
               (let ((,cnd nil))
                 ,@body))
           nil)
       nil)))

(defmacro py-else (&rest body)
  (let ((cnd '*python-condition*))
    `(if ,cnd
         (progn
           (setq ,cnd nil)
           (let ((,cnd nil))
             ,@body))
       nil)))

(defmacro def (name arglist docstring &rest body) ; simple case
  (unless (stringp docstring)
    (error "missing docstring"))
  (unless body
    (error "empty body"))
  `(defun ,name ,arglist
     ,docstring
     (python
      ,@body)))



(def test-cond (n)
  "Pythony"
  (py-if (< n 0)
    (format t "Negative~%"))
  (py-elsif (> n 0)
    (format t "Positive~%"))
  (py-else
    (format t "Zero~%")))

(defun main ()
  (test-cond -3)
  (test-cond 42)
  (test-cond  0))
korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

(defmacro def

Я это и имел в виду. Чтобы макросы влияли друг на друга, их надо упаковать в другой макрос.

А потом кто-то пишет аналогичный py-while и defmacro def2. И тебе надо выбирать функцию или с while (определять через def) или с if (определять через def2).

А на форте IMMEDIATE слова могут влиять на последующие где угодно.

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

А потом кто-то пишет аналогичный py-while и defmacro def2. И тебе надо выбирать функцию или с while (определять через def) или с if (определять через def2).

Ты невнимательно смотрел.

Макросы def и python — это просто синтаксический сахар для инициализации «контекста» (переменная *python-condition*), а «пользовательские» py-if, py-while, py-for, whatever просто используют контекст, им def и python не нужны по сути.

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

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

Вот пример, например:

(defpackage control-flow
  (:nicknames cf)
  (:use common-lisp)
  (:export define-control-variable begin def))

(in-package control-flow)

(defvar *control-flow-dict* (make-hash-table))

(defmacro define-control-variable (var)
  (progn
    (setf (gethash var *control-flow-dict*) nil)
    `(defvar ,var nil)))

(defmacro begin (&rest body)
  (let ((bindings (maphash #'(lambda (k v) (list k v)) *control-flow-dict*)))
    `(let ,bindings
       ,@body)))

(defmacro def (name arglist docstring &rest body) ; simple case
  (unless (stringp docstring)
    (error "missing docstring"))
  (unless body
    (error "empty body"))
  `(defun ,name ,arglist
     ,docstring
     (begin
      ,@body)))

; ----------------------------------------------------------------

(defpackage control-flow-else
  (:nicknames cf-else)
  (:use cl control-flow)
  (:export *python-else-condition* py-else))

(in-package control-flow-else)

(define-control-variable *python-else-condition*)

(defmacro py-else (&rest body)
  (let ((cnd '*python-else-condition*))
    `(if ,cnd
         (progn
           (setq ,cnd nil)
           (let ((,cnd nil))
             ,@body))
       nil)))

; ----------------------------------------------------------------

(defpackage control-flow-if
  (:nicknames cf-if)
  (:use cl cf cf-else)
  (:export py-if py-elsif test-cond))

(in-package control-flow-if)

(defmacro py-if (test &rest body)
  (let ((cnd '*python-else-condition*))
    `(if ,test
         (progn
           (setq ,cnd nil)
           (let ((,cnd nil))
             ,@body))
       (setq ,cnd t))))

(defmacro py-elsif (test &rest body)
  (let ((cnd '*python-else-condition*))
    `(if ,cnd
         (if ,test
             (progn
               (setq ,cnd nil)
               (let ((,cnd nil))
                 ,@body))
           nil)
       nil)))



(def test-cond (n)
  "Pythony"
  (py-if (< n 0)
    (format t "Negative~%"))
  (py-elsif (> n 0)
    (format t "Positive~%"))
  (py-else
    (format t "Zero~%")))

; ----------------------------------------------------------------

(defpackage control-flow-while
  (:nicknames cf-while)
  (:use cl cf cf-else)
  (:export py-while test-while))

(in-package control-flow-while)

(defmacro py-while (test &rest body)
  (let ((cont  (gensym "WHILE-CONT"))
        (break (gensym "WHILE-BREAK"))
        (cnd   '*python-else-condition*))
    `(tagbody
      ,cont
      (unless ,test
        (setf ,cnd t)
        (go ,break))
      (let ((,cnd nil))
        (labels ((continue () (go ,cont))
                 (break    () (go ,break)))
          ,@body
          (go ,cont)))
      ,break)))

(defun test-while ()
  (let ((*python-else-condition* nil)
        (i 1))
    (py-while (< i 6)
      (format t "~a~%" i)
      (incf i))
    (py-else
      (format t "i is no longer less than 6~%"))))
     
; ----------------------------------------------------------------

(defpackage test
  (:use cl cf cf-else cf-if cf-while)
  (:export main))

(in-package test)

(def test-while+if ()
  "Testing while + if"
  (let ((i 1))
    (py-while (< i 6)
      (format t "~a~%" i)
      (py-if (= i 3)
        (break))
      (incf i))
    (py-else
      (format t "else. i = ~a~%" i))))

(defun main ()
  (format t "~%TEST COND:~%")
  (test-cond -3)
  (test-cond 42)
  (test-cond  0)
  (format t "~%TEST WHILE:~%")
  (test-while)
  (format t "~%TEST WHILE+IF:~%")
  (test-while+if))

Вывод:

CL-USER 6 > (test:main)

TEST COND:
Negative
Positive
Zero

TEST WHILE:
1
2
3
4
5
i is no longer less than 6

TEST WHILE+IF:
1
2
3
NIL

CL-USER 7 > 

Пробуем Питон (я смотрел онлайн тут: https://www.w3schools.com/python/python_while_loops.asp ) и видим:

1:

i = 1
while i < 6:
  print(i)
  i += 1
else:
  print("i is no longer less than 6")
=>
1
2
3
4
5
i is no longer less than 6

2:

i = 1
while i < 6:
  print(i)
  if i == 3:
  	break
  i += 1
else:
  print("i is no longer less than 6")
=>
1
2
3

Один-в-один, только без NIL.

Конечно, в этом примере на CL есть нюансы: простой if, без else оставит в переменной *python-else-condition* значение T, если условие будет ложным. И какой-нибудь одинокий else может потом выполниться, если его написать. Да и в целом, else можно где угодно написать.

Но я так понимаю, такое поведение как раз ближе к Forth, чем (к) Python. Собственно, что вам мешает в этой condition-переменной хранить стек, а функциями манипулировать с ним, то есть сделать этакий embedded-интерпретатор Forth и делать то, что вы хотели (могли) делать на Forth'е, но не могли на CL?

И заметьте, функция test-while определена через обычный defun и без begin.

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

Но я так понимаю, такое поведение как раз ближе к Forth, чем (к) Python.

Нет, в Forth есть метки, goto, и слово goto-if-true, которое срабатывает только если значение true. А if-else-then помечены как immediate слова, и компилируются в этот набор goto. Никаких переменных и проверки else в рантайме.

Было:

: example 
  if 
    ." true" 
  else 
    ." false" 
  then ;

После компиляции стало

: example 
  not #1 goto-if-true
    ." true"
    #2 goto
#1:
    ." false"
#2:
  ;
Но нужно учесть, что когда я пишу #2 goto, на деле это будет в ассемблере x86 jmp #2, то есть ровно одна инструкция.

Все другие конструкции условий или циклов тоже можно через goto определить и сделать immediate словами.

Сам goto-if-true преобразуется в ассемблерную инструкцию (смотря как определяется false)

jnz #addr
Ну а goto и так понятно, в
jmp #addr

Кстати, что goto, что goto-if-true определяются через сам Forth в одну строчку.

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

Ну и как итог ассемблерный выхлоп

SBCL: https://pastebin.com/raw/7vtuYNUJ (высшая оптимизация)

(def inc-or-dec (n b)
  "Missing docstring"
  (declare (optimize (speed 3)))
  (py-if b
    (1+ n)
  (py-else
    (1- n))))

Forth: https://pastebin.com/raw/tZD6WxMw (без оптимизаций)

: inc-or-dec ( n b -- n )
  if 
    1+
  else 
    1-
  then ;

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

Сам goto-if-true преобразуется в ассемблерную инструкцию (смотря как определяется false)

Ну не совсем. Там же шитый код. :) Слову 0BRANCH нужно со стека данных снять значение и если там 0, то к IP прибавить смещение скомпилированное после слова 0BRANCH или пропустить смещение, если не 0.

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

это просто синтаксический сахар для инициализации «контекста»

А я про что? Контекст и есть средство общения между макросами. Причём, если контекст является макросом, то можно сделать и запрет на выполнение команд между ветками «то» и «иначе».

А вот сделать, чтобы работало

(defun test-cond (n)
  "Pythony"
  (py-if (< n 0)
    (format t "Negative~%"))
  (py-elsif (> n 0)
    (format t "Positive~%"))
  (py-else
    (format t "Zero~%")))

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

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

Ну не совсем. Там же шитый код. :)

Где то используется шитый код, а где то нет. Коммерческие форты и SP-Forth компилируются в нативный код. Я думаю шитый код уже отходит в прошлое, не знаю реализаций кроме gforth которые его используют.

Поэтому показал как работает if в знакомом мне, нативном коде. С шитым кодом дела не имел.

VFX и SwiftForth работают так же.

Слову 0BRANCH нужно со стека данных снять значение и если там 0,

Долго искал его в https://forth-standard.org/standard/words, но видимо оно нестандартное, и зависит от реализации.

Лисперам бы дизайн https://forth-standard.org/ скопировать, вместо этих справок которые выглядят как сайты-закосы под ретро.

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

Нет, в Forth есть метки, goto, и слово goto-if-true, которое срабатывает только если значение true. А if-else-then помечены как immediate слова, и компилируются в этот набор goto. Никаких переменных и проверки else в рантайме.

Ну вот, какие-то специальные immediate-слова.

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

immediate-слова, это просто функции, у которых выставлен один бит, с названием immediate. Когда компилятор встречает такую функцию в коде, он ее не «вставляет» (генерацией call 0x...), а немедленно (immediate) выполняет, после чего продолжает компиляцию.

Пример:

: f1 ." hello1" ; immediate
: f2 ." hello2" f1 ;
f2
Вывод:
hello1
hello2

Так что никаких «специальных слов» для реализации if, это основа Forth. Такие слова могут вмешиваться в процесс компиляции, и вставлять всякие goto, так что это аналог макросов из Lisp.

Вообще любую функцию можно сделать immediate, а само ключевое слово immediate лишь выставляет бит последнему определенному слову.

Но в скомпилированном коде от них ничего не остается, только если они сами что то не добавили конечно.

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

Что мешает подменить defun через :shadow в своём пакете?

Например то, что помимо defun есть ещё куча кода, который позволяет определять функции, методы и прочее. То есть, придётся подменять ещё как минимум lambda и defmethod и запрещать пользоваться любыми макросами, которые могут раскрываться в cl:lambda, cl:defmethod и cl:defun. Очень протекающая абстракция.

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

в некотором смысле в zig ща immediate есть ибо что сам код что препроцессор синтаксически одно и тоже

Там разве есть препроцессор? Насколько я понял, сделать код, который бы генерировал код zig при компиляции, нельзя. Можно только делать функции, которые возвращают типы (и, естественно, выполняются при компиляции).

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

Как только кто-нибудь напишет оптимизатор, который AST с рекурсией преобразует в AST с хвостовой, так сразу.

А почему не написали? А где то написали? Выглядит не слишком сложно, относительно сложности некоторых других оптимизаций, но в ФП коде полезно.

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

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

Еще у Lisp и Forth есть преимущество что среду можно потянуть в скомпилированное приложение, и применять встроенный jit к месту.

и ченить сломать им там.

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

А почему не написали? А где то написали?

Не видел.

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

Напиши.

Интересно еще, применима ли где то оптимизация нерекурсивных вызовов

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

выносом постдействий из А в вызываемую функцию Б, но тогда ее можно вызывать только из функции А

И тогда логичнее всё упаковать в функцию А. Это называется подстановка (inline). Есть много где.

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

А смысл? Из всех вызываемых функций в хвостовую позицию можно переместить только одну.

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

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

В Racket же встроенный отладчик.

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

Это скорее проблема жирных сред типа SBCL который выдает 50 мб бинарник, ANS Forth будет ну сотню килобайт с оптимизатором, загрузку и не заметишь.

Скомпилированное приложение с большим набором библиотек тоже долго может запускаться.

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

В Racket же встроенный отладчик.

Его могут считать отладчиком только те, кто не видел отладчика в Common Lisp. Даже в 1С отладчик мощнее, чем в Racket.

Я когда переходил с SBCL на Racket, пытался в переписке продвинуть идею сделать нормальный отладчик. Получил ответ, что отладчики зло. И идея отдавать пользователям код с отключёнными для скорости проверками (на тестах производительности SBCL проверяют в режиме (declare (optimize (safety 0) (debug 0) (speed 3)))), также зло. Потому что при тестировании такой код бесполезен, так как любая ошибка приводит к UB (неопределённому поведению), а отдавать код, который фактически отличается от протестированного и делает UB при ошибке, просто безответственно. Поэтому в Racket нет возможности отключить проверки безопасности.

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

Это скорее проблема жирных сред типа SBCL который выдает 50 мб бинарник, ANS Forth будет ну сотню килобайт с оптимизатором, загрузку и не заметишь.

Простая утилитка может быть несколько сотен байт.

Скомпилированное приложение с большим набором библиотек тоже долго может запускаться.

Когда приложение достаточно большое, тогда объём среды выполнения уже почти неважен.

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

Получил ответ, что отладчики зло.

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

Его могут считать отладчиком только те, кто не видел отладчика в Common Lisp.

den73 писал что его там нету, учитывая что картинки по запросу в гугле нету, я ему поверю. А чем плох отладчик 1С? Там я вижу хотя бы дерево объектов есть, а я отладчик часто запускаю просто что бы осмотреть структуры «в работе».

Есть возможность в отладчике Common Lisp (кстати, как он называется?) сделать break в функции А при входе из определенной функции Б? При изменении поля при N проходе?

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

Смешная статья, преподаватель читающий максимум лабы студентов рассказывает что отладчик это ненужно…

Вот чуть под другим углом: https://www.thinkingmuchbetter.com/main/debugging-bad-detective/

Фактически, наличие отладчика позволяет идти в отладчик вместо того, чтобы подумать. В 1С это оправдано: там программист сопровождает систему на миллион строк кода, по которой нет документации. Поэтому возможность получить данные о контексте ошибки и влепить заплатку без понимания системы полезна.

В остальных случаях от наличия отладчика больше вреда чем пользы. Сейчас в Racket у меня каждая исправленная ошибка остаётся в коде в виде проверки функции (unit test). В Common Lisp воспроизводить данные для проверки было просто лень (зачем, вот они в памяти в отладчике в момент ошибки?).

den73 писал что его там нету, учитывая что картинки по запросу в гугле нету, я ему поверю.

Он от отладчика странного хотел. Трассировки и точек останова без изменения исходного кода.

А чем плох отладчик 1С? Там я вижу хотя бы дерево объектов есть, а я отладчик часто запускаю просто что бы осмотреть структуры «в работе».

По сравнению с Common Lisp? Невозможностью изменять процедуры и функции, невозможностью продолжить выполнение с текущей точки. Но, в отличие от Racket, хотя бы позволяет запускать произвольные функции. Потому что просто глядя на структуры получить что-то вроде «Таблица.НайтиСтроки(«Сотрудник», Справочники.Сотрудники.НайтиПоКоду(42))» достаточно проблематично.

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

Фактически, наличие отладчика позволяет идти в отладчик вместо того, чтобы подумать.

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

В 1С это оправдано: там программист сопровождает систему на миллион строк кода, по которой нет документации.

Ну так это типичная работа программистом. А не сопровождение студенческих лаб.

Сейчас в Racket у меня каждая исправленная ошибка остаётся в коде в виде проверки функции (unit test).

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

Он от отладчика странного хотел. Трассировки и точек останова без изменения исходного кода.

Больной человек, где он такое увидел, в любом другом отладчике что ли? Такое кстати в DrRacket есть 😱

Невозможностью изменять процедуры и функции ...
... позволяет запускать произвольные функции

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

невозможностью продолжить выполнение с текущей точки

Это как вообще? Там безостановочный одношаговый режим?

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

по zig'у «рабинович напел» - был я не точен - подразумевал что в зиге есть нечто что выглядит как часть сырца в том же синтаксисе и обладает «imediate» свойством сделать некоторое преобразование предкомпилируемое - (привычное в виде препроцессинга сырца - но это(препроцессинг обычно как трасформация текста по некоторым постороним(к языку программирования) правилам позволяющим отделять препроцессорные директивы от основного потока токенов :)

в первом издании на русском Кернигана и Ричи The Programming Language C ( в том же кодексе задачник их коллеги по лаборатории Фьюера) есть русское предпредисловие - в котором видимо ориентируяс на высоколобых прогеров союза явно подчёркивается что задача мультизадачности вынесенны на уровень операционной среды

так что фобия к пошаговым отладчикам у «Отцов» может быть обусловленна их практикой когда таже THE Дейкстры это уже середина 60ых :)

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