Qual a utilidade das macros Lisp?


22

O Lisp comum permite que você escreva macros que fazem a transformação de origem desejada.

O esquema fornece um sistema higiênico de correspondência de padrões que permite executar transformações também. Quão úteis são as macros na prática? Paul Graham disse em Beating the Averages que:

O código fonte do editor da Viaweb era provavelmente de 20 a 25% de macros.

Que tipo de coisas as pessoas realmente acabam fazendo com macros?


Acho que isso definitivamente se enquadra em uma boa subjetividade , editei sua pergunta para formatação. Isso pode ser uma duplicata, mas não foi possível encontrar uma.
Tim Post

1
Tudo repetitivo que não parece se encaixar em uma função, eu acho.

2
Você pode usar macros para transformar Lisp em qualquer outro idioma, com qualquer sintaxe e semântica: bit.ly/vqqvHU
SK-logic

programmers.stackexchange.com/questions/81202/… vale a pena olhar aqui, mas não é uma duplicata.
David Thornley

Respostas:


15

Dê uma olhada nesta postagem de Matthias Felleisen na lista de discussão do LL1 em ​​2002. Ele sugere três usos principais para macros:

  1. Sub - linguagens de dados : eu posso escrever expressões de aparência simples e criar listas / matrizes / tabelas aninhadas complexas com aspas, sem aspas, etc., elegantemente decoradas com macros.
  2. Construções de ligação : posso introduzir novas construções de ligação com macros. Isso me ajuda a me livrar das lambdas e a aproximar as coisas que pertencem uma à outra.
  3. Reordenação de avaliação : posso introduzir construções que atrasam / adiam a avaliação de expressões conforme necessário. Pense em loops, novas condicionais, atraso / força etc. [Nota: em Haskell ou em qualquer linguagem lenta, esta é desnecessária.]

18

Eu uso principalmente macros para adicionar novas construções de linguagem que economizam tempo, que exigiriam um monte de código padrão.

Por exemplo, recentemente me vi querendo um imperativo for-loopsemelhante ao C ++ / Java. No entanto, sendo uma linguagem funcional, Clojure não veio com uma fora da caixa. Então, eu apenas o implementei como uma macro:

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

E agora eu posso fazer:

 (for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

E aí está: uma nova construção de linguagem de tempo de compilação de uso geral em seis linhas de código.


13

Que tipo de coisas as pessoas realmente acabam fazendo com macros?

Extensões de linguagem de escrita ou DSLs.

Para ter uma idéia disso em idiomas do tipo Lisp, estude o Racket , que possui várias variantes de idioma: Raquete Digitada, R6RS e Registro de Dados.

Consulte também o idioma Boo, que fornece acesso ao pipeline do compilador com o objetivo específico de criar idiomas específicos do domínio por meio de macros.


4

aqui estão alguns exemplos:

Esquema:

  • definepara definições de função. Basicamente, faz uma maneira mais curta de definir uma função.
  • let para criar variáveis ​​com escopo lexical.

Clojure:

  • defn, de acordo com seus documentos:

    O mesmo que (nome do def (fn [params *] exprs *)) ou (nome do def (fn ([params *] exprs *) +)) com qualquer sequência de caracteres ou atributos adicionados aos metadados var

  • for: compreensão da lista
  • defmacro: irônico?
  • defmethod, defmulti: trabalhando com vários métodos
  • ns

Muitas dessas macros tornam muito mais fácil escrever código em um nível mais abstrato. Penso que as macros são semelhantes, em muitos aspectos, à sintaxe em não Lisps.

A biblioteca de plotagem Incanter fornece macros para algumas manipulações de dados complexas.


4

Macros são úteis para incorporar alguns padrões.

Por exemplo, Common Lisp não define o whileloop, mas possui o do que pode ser usado para defini-lo.

Aqui está um exemplo do On Lisp .

(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(let ((a 0))
  (while (< a 10)
    (princ (incf a))))

Isso imprimirá "12345678910" e se você tentar ver o que acontece com macroexpand-1:

(macroexpand-1 '(while (< a 10) (princ (incf a))))

Isso retornará:

(DO () ((NOT (< A 10))) (PRINC (INCF A)))

Essa é uma macro simples, mas como dito anteriormente, elas geralmente são usadas para definir novas linguagens ou DSLs, mas, a partir deste exemplo simples, você já pode imaginar o que pode fazer com elas.

A loopmacro é um bom exemplo do que as macros podem fazer.

(loop for i from 0 to 10
      if (and (= (mod i 2) 0) i)
        collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
      with a = 2
      collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)               

O Lisp comum possui outro tipo de macros chamado macro de leitor, que pode ser usado para modificar a maneira como o leitor interpreta o código, ou seja, você pode usá-los para usar # {e #} possui delimitadores como # (e #).


3

Aqui está um que eu uso para depuração (no Clojure):

user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil

Eu tive que lidar com uma tabela de hash rolada à mão em C ++, onde o getmétodo utilizou uma referência de cadeia não-const como argumento, o que significa que não posso chamá-lo com um literal. Para facilitar isso, escrevi algo como o seguinte:

#define LET(name, value, body)  \
    do {                        \
        string name(value);     \
        body;                   \
        assert(name == value);  \
    } while (false)

Embora seja improvável que algo como esse problema apareça no lisp, acho particularmente bom que você possa ter macros que não avaliam seus argumentos duas vezes, por exemplo, introduzindo uma verdadeira let-binding. (Admitido, aqui eu poderia ter contornado isso).

Também recorro ao truque horrivelmente feio de embrulhar coisas de do ... while (false)tal maneira que você possa usá-las na parte de um if e ainda assim fazer o resto funcionar como esperado. Você não precisa disso no lisp, que é uma função de macros operando em árvores de sintaxe, em vez de seqüências de caracteres (ou sequências de token, eu acho, no caso de C e C ++), que passam por análise.

Existem algumas macros de encadeamento embutidas que podem ser usadas para reorganizar seu código, para que ele seja lido de maneira mais limpa ('encadeamento' como em 'semear o código', não paralelismo). Por exemplo:

(->> (range 6) (filter even?) (map inc) (reduce *))

Ele pega a primeira forma, (range 6)e a torna o último argumento da próxima forma, (filter even?)que por sua vez é o último argumento da próxima forma e assim por diante, de modo que o acima seja reescrito em

(reduce * (map inc (filter even? (range 6))))

Acho que o primeiro lê com muito mais clareza: "pegue esses dados, faça isso, depois faça isso, depois faça o outro e pronto", mas isso é subjetivo; algo que é objetivamente verdadeiro é que você leia as operações na sequência em que são executadas (ignorando a preguiça).

Há também uma variante que insere o formulário anterior como o primeiro (e não o último) argumento. Um caso de uso é aritmético:

(-> 17 (- 2) (/ 3))

Lê como "pegue 17, subtraia 2 e divida por 3".

Falando em aritmética, você pode escrever uma macro que faz análise de notação de infixo, para que você possa dizer, por exemplo, (infix (17 - 2) / 3)e cuspirá (/ (- 17 2) 3)qual tem a desvantagem de ser menos legível e a vantagem de ser uma expressão lisp válida. Essa é a parte de sub-linguagem DSL / dados.


1
A aplicação de funções faz muito mais sentido para mim do que a segmentação, mas certamente é uma questão de hábito. Boa resposta.
Coredump
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.