Как работает funcall в Лиспе?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 02.07.24 04:04
Оценка:
В Common Lisp такой код с функцией funcall:
(print (funcall
        (lambda (x y)
                (format NIL "~d + ~d = ~d" x y (+ x y))
        )
        2
        3
       )
)

И такой без нее:
(print (
        (lambda (x y)
                (format NIL "~d + ~d = ~d" x y (+ x y))
        )
        2
        3
       )
)

Работает совершенно аналогично — вызывает лямбда-выражение с парой параметров и выводит "2 + 3 = 5".

Однако, когда я пытаюсь написать простой пример с ООП:
(setq User-counter 0)
(defun User (name)
    (let
     (
      (id (setq User-counter (+ User-counter 1)))
     )
     (lambda (method)
             (cond
                 ((eq method 'getId) (lambda () id))
                 ((eq method 'getName) (lambda () name))
                 ((eq method 'data) (lambda () (list id name)))
                 ((eq method 'format) (lambda (f) (format NIL f id name)))
             )
     )
    )
)

То вызов методов с funcall работает:
(print (funcall (funcall (User "Worminator") 'format) "id: ~d, name: ~s"))

А вот без funcall выдает ошибку:
(print (((User "Worminator") 'format) "id: ~d, name: ~s"))

EVAL: ((USER "Worminator") 'FORMAT) is not a function name; try using a symbol instead


Почему? Можно ли как-то использовать второй вариант? Что означает not a function name — вообще не понял. И что делает funcall? Зачем нужна apply — ясно, она берет аргументы для вызова из списка, а вот с этой функцией непонятно. В другом диалекте Лиспа, Scheme, кстати, не нашел funcall (или там другое имя для нее).

Как-то запутанно все... В учебнике Святослава Лаврова, по которому когда-то учил Лисп, funcall совершенно не упоминалась, там говорилось, что выражение (f a1 ... aN) просто применяет значение f (которое должно быть лямбдой) к аругментам a1-aN и выдает результат. И вообще весь Лисп описывался 10 базовыми функциями: car, cdr, cons, atom, eq, cond, lambda, defun, setq и quote.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re: Как работает funcall в Лиспе?
От: σ  
Дата: 02.07.24 05:50
Оценка:
https://stackoverflow.com/questions/36978694/why-must-i-funcall-a-function-returned-from-another
https://chatgpt.com/share/694e899c-6a62-4100-8640-213502335b7a
Re: Как работает funcall в Лиспе?
От: dsorokin Россия  
Дата: 05.07.24 17:31
Оценка:
Здравствуйте, Worminator X, Вы писали:

---

Схема — это Lisp-1. Common Lisp — это Lisp-N, т.е. буквально много пространств имен. Функции и переменные в разных пространствах имен.

В начале формы ищет название функции из пространства имен функций, а у вас, вообще, ячейка сons! Отсюда у вас недопонимание с компилятором.

Зачем это нужно? Сильно упрощает проверку кода при компиляции для динамического языка. Язык не статический, а именно динамический. Такая штука в итоге помогает создавать приложения с большим объемом кода, т.е. позволяет снизить сложность программирования.

По Common Lisp есть хороший учебник: Практическое использование Common Lisp

А что касается укороченной формы "((lambda ...) arg)", то это небольшое отступление от общих правил, упрощение, да и просто архаизм, хотя лично мне нравится.
Re[2]: Как работает funcall в Лиспе?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.07.24 00:20
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>В начале формы ищет название функции из пространства имен функций, а у вас, вообще, ячейка сons! Отсюда у вас недопонимание с компилятором.


Если несложно, то можно объяснить, почему в этом случае funcall для вызова не требуется, а в примере с ООП обязателен?

(print (
        (lambda (x y)
                (format NIL "~d + ~d = ~d" x y (+ x y))
        )
        2
        3
       )
)


У меня из функции-конструктора User возвращается та же самая lambda вроде? В чем разница между этими примерами?
Сначала думал, из-за замыканий (поля id и name), но нет, вроде без них то же самое.
То есть в 1-м случае можно использовать анонимную функцию, а во 2-м обязательно указывать имя.

В JavaScript никакой разницы, передавать имя функции или анонимную функцию, значение типа function:
function User(name) {
  var id;
  if (!('counter' in User)) {
    User.counter = 0;
  }
  User.counter++;
  id = User.counter;
  return {
    'getId': function() { return id; },
    'getName': function() { return name; },
    'data': function() { return [id, name]; },
    'format': function(f) { return f.replace('%d', id).replace('%s', name); } 
  };
}


Обязательное создание имен в глобальном namespace — это, скорее, больше походит на статические языки, в частности отсутствие анонимных функций (хотя бы без замыканий) это огромный, жирный минус Си. Частично исправленный в GCC (но это нестандартное расширение). На каждый вызов qsort, таймера и т.д. создавать отдельную функцию и придумывать ей имя, всегда это приводило в бешенство. Разумеется, в динамическом языке такой мусор недопустим, не нужно превращать код в помойку. Даже в Java, еще до появления лямбд, пытались что-то с этим сделать, ввели анонимные классы. А в C# изначально сдеалали делегаты.

D>По Common Lisp есть хороший учебник: Практическое использование Common Lisp


Спасибо, буду изучать. Похоже, там со времен Джона Маккарти в 1958 напридумывали всего... По статьям из интернета теперь не разберешься, а ведь изначально это был один из самых простых языков. Столяров, пожалуй, прав, когда призывает расстреливать любых стандартизаторов из комитетов.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Отредактировано 06.07.2024 1:18 Worminator X . Предыдущая версия . Еще …
Отредактировано 06.07.2024 0:52 Worminator X . Предыдущая версия .
Отредактировано 06.07.2024 0:50 Worminator X . Предыдущая версия .
Отредактировано 06.07.2024 0:48 Worminator X . Предыдущая версия .
Re[2]: Как работает funcall в Лиспе?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.07.24 00:26
Оценка:
Здравствуйте, σ, Вы писали:

σ>https://stackoverflow.com/questions/36978694/why-must-i-funcall-a-function-returned-from-another

σ>https://chatgpt.com/share/694e899c-6a62-4100-8640-213502335b7a

So something like

(((lambda (foo)
    (lambda (bar)
      (list foo bar)))
  1)
 2)

is not valid Common Lisp.


Это из-за замыканий? Такой код:

(setq User-counter 0)
(defun User (name)
    (let
     (
      (id (setq User-counter (+ User-counter 1)))
     )
     (lambda (method)
             (cond
                 ((eq method 'getId) (lambda () 1))
                 ((eq method 'getName) (lambda () "Worminator"))
                 ((eq method 'data) (lambda () (list 1 "W")))
                 ((eq method 'format) (lambda (f) (format NIL f 1 "Worminator")))
             )
     )
    )
)


тоже без funcall не работает. Не могу понять, в чем отличие от первого примера со сложением чисел.

Работает:
(print ((lambda(id name) (format NIL "id: ~d, name: ~s" id name)) 1 "Worminator"))


Не работает:
(print (((lambda(id name) (lambda (f) (format NIL f id name)) id name) 1 "Worminator") "id: ~d, name: ~s"))


(print (((lambda() (lambda (f) (format NIL f 1 "Worminator")))) "id: ~d, name: ~s"))


(print (((lambda() (lambda () (format NIL "id: ~d, name: ~s" 1 "Worminator"))))))


В последнем случае вообще нет имен, для исключения проблем с namespace'ами.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Отредактировано 06.07.2024 1:11 Worminator X . Предыдущая версия .
Re[3]: Как работает funcall в Лиспе?
От: dsorokin Россия  
Дата: 07.07.24 09:33
Оценка:
Здравствуйте, Worminator X, Вы писали:

-----

Готов подсказать сейчас.

У вас функция "User" создает лямбда-выражение. Чтобы его вызвать опосредованно, необходимо применить функцию funcall, передав аргументом соответствующее лямбда-выражение:

(funcall (User name))


Как я написал выше, первым элементом формы ожидается имя функции (которое ищется в пространстве имен функций). Сам "funcall" — это функция, такая же как и ваш "User". Поэтому все правильно.

Здесь мы для простоты опустим макросы и специальные формы, о которых читайте уже в книге, которую я посоветовал (кстати, в интернете должна быть легальная версия PDF на английском для всех желающих).

У вас там цепочка лямбда-выражений. Поэтому "funcall" — это именно то, что нужно.

Теперь если вы по ошибке первым поставили выражение, возвращаемое из User:

((User name) 'format)


То это так не работает в Common Lisp, потому что первым элементом формы опять-таки ожидается именно название функции. А где у вас здесь название функции? У вас там ячейка cons с содержимым "(User name)", и компилятору лиспа это не нравится. Все ожидаемо.

Теперь по поводу выражения типа:

((lambda (x y) 
    (format NIL "~d + ~d = ~d" x y (+ x y)))
  2 3)


Это допустимая сокращенная запись для более полной формы:

(funcall 
  (lambda (x y) 
    (format NIL "~d + ~d = ~d" x y (+ x y)))
  2 3)


То сокращение возникло давно в языке. Оно роднит Common Lisp с той же Схемой. Однако, это просто тот случай, который распознают компиляторы. В остальных случаях нужно использовать "funcall" или "apply", поскольку Lisp-N. За деталями прошу обратиться все-таки к посоветованной мною книге, а не к форуму.

Что касается Столярова. Не надо лисп изучать по его книгам! Я глубоко убежден, что автор должен учить тем языкам, которые ему нравятся, которые вызывают у него приятные чувства. Если языки автору противны, вызывают отторжение, то и писать о них ничего не надо — выглядит со стороны смешно и глупо, да и молодых людей может травмировать такая книга, повести по ложному пути. Пожалуй, на этом обсуждение талмуда Столярова закончу.
Re: Как работает funcall в Лиспе?
От: korvin_  
Дата: 22.07.24 06:39
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>Как-то запутанно все... В учебнике Святослава Лаврова, по которому когда-то учил Лисп, funcall совершенно не упоминалась, там говорилось, что выражение (f a1 ... aN) просто применяет значение f (которое должно быть лямбдой) к аругментам a1-aN и выдает результат. И вообще весь Лисп описывался 10 базовыми функциями: car, cdr, cons, atom, eq, cond, lambda, defun, setq и quote.


"вообще весь Лисп описывался 10 базовыми функциями" -- это Lisp 1.5, или типа того, конца 50-х — начала 60-х.

Стандарт Common Lisp вышел в 1994-м году и описывает гораздо более поздний Лисп, который описывается куда большим количеством функций, типов, особых форм и прочего.

WX>простой пример с ООП


Простой пример с ООП на Common Lisp выглядит так:

(defvar *user-counter* 0)

(defclass user ()
  ((id   :initform (incf *user-counter*) :reader get-id)
   (name :initarg  :name                 :reader get-name)))

(defgeneric data (obj))

(defmethod data ((u user))
  (list (get-id u) (get-name u)))

(defgeneric format-object (obj fmt))

(defmethod format-object ((u user) (fmt string))
  (format nil fmt (get-id u) (get-name u)))

(print (format-object (make-instance 'user :name "Worminator") "id: ~d, name: ~s"))


Или так:

(defclass user ()
  ((id                     :reader get-id)
   (name    :initarg :name :reader get-name)
   (counter :initform 0    :allocation :class)))
 
(defmethod initialize-instance :after ((u user) &rest args)
  (setf (slot-value u 'id) (incf (slot-value u 'counter))))
 
(defgeneric data (obj))
 
(defmethod data ((u user))
  (list (get-id u) (get-name u)))
 
(defmethod print-object ((u user) out)
  (format out "id: ~d, name: ~s" (get-id u) (get-name u)))
 
(print (make-instance 'user :name "Worminator"))
Отредактировано 22.07.2024 6:58 korvin_ . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.