Можно ли написать функцию, которая будет принимать любой макрос и превращать его в функцию, чтобы ее можно было передать в качестве аргумента другой функции? - PullRequest
2 голосов
/ 28 апреля 2020

И и ИЛИ являются макросами, и поскольку макросы не являются первым классом в схеме / ракетке, их нельзя передавать в качестве аргументов другим функциям. Частичное решение состоит в том, чтобы использовать и-карту или или-карту. Можно ли написать функцию, которая будет принимать произвольный макрос и превращать его в функцию, чтобы ее можно было передать в качестве аргумента другой функции? Есть ли языки, в которых есть макросы первого класса?

Ответы [ 3 ]

6 голосов
/ 28 апреля 2020

В общем, нет. Учтите, что let реализован (или может быть) реализован как макрос поверх lambda:

(let ((x 1))
  (foo x))

может быть макросом, который расширяется до

((lambda (x) (foo x)) 1)

Теперь, что это будет похоже на преобразование let в функцию? Понятно, что это чепуха. Каким будет его вклад? Его возвращаемое значение?

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

4 голосов
/ 28 апреля 2020

Я согласен с @amalloy. Если что-то написано в виде макроса, оно, вероятно, выполняет то, что функции не могут сделать (например, вводить привязки, изменять порядок оценки). Поэтому автоматическое преобразование произвольного макроса в функцию - очень плохая идея, даже если это возможно.

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

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

#lang racket

(require (for-syntax racket/list))

(define-syntax (->proc stx)
  (syntax-case stx ()
    [(_ mac #:arity arity)
     (with-syntax ([(args ...) (generate-temporaries (range (syntax-e #'arity)))])
       #'(λ (args ...) (mac args ...)))]))

((->proc and #:arity 2) 42 12)
(apply (->proc and #:arity 2) '(#f 12))
((->proc and #:arity 2) #f (error 'not-short-circuit))

Вас также может заинтересовать макрос идентификатора , который позволяет использовать идентификатор в качестве макроса в некотором контексте и функцию в другом контексте. Это может быть использовано для создания первого класса and / or, который закорачивается при использовании в качестве макроса, но может передаваться как значение функции в нетрансформаторном положении.

На вершине c макроса первого класса, посмотрите на https://en.wikipedia.org/wiki/Fexpr. Известно, что это плохая идея.

2 голосов
/ 28 апреля 2020

Не так, как вы, вероятно, ожидаете

Чтобы понять почему, вот способ мышления о макросах: макрос - это функция, которая берет немного исходного кода и превращает его в другой кусочек исходного кода. : расширение макроса. Другими словами, макрос - это функция, область и диапазон которой исходный код .

Как только исходный код полностью раскрыт, он передается либо в оценщик, либо в компилятор. Давайте предположим, что он передается компилятору, потому что он облегчает ответ на вопрос: сам компилятор - это просто функция, домен которой является исходным кодом, а диапазон - это некоторая последовательность инструкций для машины (которая может быть или не быть реальной машиной) выполнить. Эти инструкции могут включать такие вещи, как «вызов этой функции для этих аргументов».

Итак, вы спрашиваете: может ли «this function» в «вызывать эту функцию для этих аргументов» быть каким-то макросом? Ну, да, это может быть, но любой исходный код, который он собирается преобразовать, безусловно, не может быть исходным кодом программы, которую вы выполняете, потому что этого уже нет: все, что осталось, это последовательность инструкций, которая была возвращаемым значением компилятор.

Итак, вы можете сказать: ОК, допустим, мы запрещаем компиляторы: можем ли мы сделать это сейчас? Ну, если оставить в стороне то, что «запрещение компиляторов» является своего рода серьезным ограничением, это было, на самом деле, то, что делали очень старые диалекты в Lisp, используя конструкцию, называемую FEXPR , как упомянуто в другой ответ. Важно понимать, что FEXPR существовали, потому что люди еще не изобрели макросы . Довольно скоро люди изобрели макросы, и хотя FEXPR и макросы некоторое время сосуществовали - в основном потому, что люди писали код, использующий FEXPR, который они хотели продолжать выполнять, и потому что написание макросов было серьезной болью до появления таких вещей, как обратная цитата - FEXPR умерли вне. И они вымерли, потому что были семантически ужасными: даже по стандартам Лиспов 1960-х годов они были семантически ужасными.

Вот один небольшой пример того, почему FEXPR такие ужасные: допустим, я пишу эту функцию на языке с FEXPR :

(define (foo f g x)
  (apply f (g x)))

Теперь: что происходит, когда я звоню foo? В частности, что произойдет, если f может быть FEXPR? . Ну, ответ в том, что я вообще не могу скомпилировать foo: мне нужно подождать до времени выполнения и принять какое-то решение на лету о том, что делать.

Конечно, это не так. То, что, вероятно, делали эти старые Лиспы с FEXPR: они просто молча предположили бы, что f была нормальной функцией (которую они назвали бы EXPR), и скомпилировали бы соответственно (и да, даже у очень старых Лиспов были компиляторы). Если вы передали что-то, что было FEXPR, вы только что потеряли: либо вещь обнаружила это, либо, скорее всего, она упала ужасно, либо дала вам какой-то ненужный ответ.

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


Кстати, и в Racket, и в Common Lisp макросы явно являются функциями. В Racket это функции, которые работают со специальными объектами «синтаксиса», потому что так вы получаете гигиену, но в Common Lisp, который намного менее гигиеничен c, это просто функции, которые работают с исходным кодом CL, где исходный код просто состоит из списков, символов & c.

Вот пример этого в Racket:

> (define foo (syntax-rules ()
                [(_ x) x]))
> foo
#<procedure:foo>

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

> (foo 1)
; ?: bad syntax
;   in: 1
; [,bt for context]

Это потому, что 1 не является синтаксическим объектом.

> (foo #'(x 1))
#<syntax:readline-input:5:10 1>
> (syntax-e (foo #'(x 1)))
1

А в CL это еще проще увидеть: вот определение макроса:

(defmacro foo (form) form)

И теперь я могу взять функцию макроса и вызвать ее из некоторого исходного кода CL:

> (macro-function 'foo)
#<Function foo 4060000B6C>

> (funcall (macro-function 'foo) '(x 1) nil)
1

И в Racket, и в CL макросы фактически являются первоклассными (или, как мне кажется, в случае с Racket: почти первоклассными): это функции, которые работают с исходным кодом, который сам по себе Первый класс: вы можете писать программы на Racket и CL, которые конструируют и манипулируют исходным кодом произвольным образом: вот что макросы являются на этих языках.

В случае с Racket я сказал: почти в первом классе », потому что я не вижу способа в Racket получить функцию, которая находится за макросом, определенным с помощью define-syntax & c.

...