Quando eu exigiria uma macro em vez de uma função?

7

Sou novo no Clojure, sou novo no Macros e não tenho experiência anterior no Lisp. Eu criei meu próprio caso de switch como formulário e acabei com isso:

(defmacro switch-case [v cases default] (if (cases v) (cases v) default )) 

e então tentei criar uma função e acabei com isso:

(defn fn-switch-case [v cases default] (if (cases v) (cases v) default ))

Ambos

(switch-case 5 {6 "six" 7 "seven"} "not there")

e

(fn-switch-case 5 {6 "six" 7 "seven"} "not there")

Funciona bem.

Qual poderia ser o cenário em que eu precisaria de uma macro e uma função não funcionam? Existe uma desvantagem na minha função ou implementações de macro?

Amogh Talpallikar
fonte
2
Não tenho um exemplo específico para você, mas a resposta simples é que as macros são necessárias para criar nova sintaxe (como em Idiomas específicos do domínio). Mas, como o exemplo ilustra, as funções Lisp comuns de primeira classe já são poderosas o suficiente para fazer o trabalho sem a necessidade de macros, em muitos casos. Você deve favorecer funções comuns sobre macros; use apenas macros quando uma função comum não funcionar.
Robert Harvey
1
Veja também stackoverflow.com/questions/2561221
Robert Harvey

Respostas:

6

Um benefício das macros é que elas não seguem as mesmas regras de avaliação que as funções, pois estão apenas realizando transformações no código.

Um exemplo de construção que você não pode criar usando apenas funções é a macro de encadeamento do Clojure .

Permite inserir uma expressão como o primeiro argumento em uma sequência de formulários:

(-> 5
  (+ 3)
  (* 4))

é equivalente a

(* (+ 5 3) 4)

Você não seria capaz de criar esse tipo de construção usando apenas funções, pois antes da ->expressão ser avaliada, o interior (+ 3)e (* 4)o significado ->seriam recebidos.

(-> 5
    3
    4)

e precisa ver as funções reais sendo usadas para funcionar.

No seu exemplo, considere se o resultado de algum caso teve efeitos colaterais ou chame alguma outra função. Uma versão de função não seria capaz de impedir que esse código fosse executado em uma situação em que esse resultado não fosse selecionado, enquanto uma macro poderia impedir que esse código fosse avaliado, a menos que fosse a opção escolhida.

Justin
fonte
1
Entendi. porque na minha mente, não consigo pensar além de uma função. Eu sempre acabo fazendo uma construção que se parece com uma função e pode ser feita com uma função. porque estou acostumado a escrever código que não cria linguagens e construções ou DSls.
Amogh Talpallikar
2

Um aspecto divertido das macros é que elas oferecem a possibilidade de expandir a sintaxe do seu lisp e adicionar novos recursos sintáticos a ela, e isso acontece apenas porque os argumentos passados ​​para uma macro são avaliados apenas no tempo de execução, e não no momento da compilando sua macro. Como exemplo, as primeiras / últimas macros de thread no clojure -> ->>não avaliam seus argumentos de expressão, caso contrário, esses resultados avaliados não poderiam aceitar mais nada (digamos (+ 3) => 3e 3não é uma função que poderia aceitar seu primeiro argumento principal em algo como (-> 4 (+ 3))).

Agora, se eu gosto dessa sintaxe e quero adicioná-la à minha implementação Common Lisp (que não a possui), posso adicioná-la definindo uma macro por conta própria. Algo assim:

;;; in SBCL

;;; first thread:
(defmacro -> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,x ,@(rest m)) then `(,(first m) ,n ,@(rest m))
     finally (return n)))

;;; last thread:
(defmacro ->> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,@(rest m) ,x) then `(,(first m) ,@(rest m) ,n)
     finally (return n)))

Agora eu seria capaz de usá-los no Common Lisp da mesma maneira que no Clojure:

(-> #'+
    (mapcar '(2 3 4) '(1 2 3))) ;; => (3 5 7)

(->> #'>
 (sort '(9 8 3 5 7 2 4))) ;; => (9 8 7 5 4 3 2)

Talvez você também queira ter uma nova sintaxe para a rangefunção do clojure com suas próprias palavras-chave para sua sintaxe, algo como:

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9)

(from 0 to 10 by 0.5) ;;=> (0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5)

pode ser definido como esta macro:

(defmacro from
  "Just another range!"
  [x y z & more]
  `(when (= '~y '~'to)
     (if '~more (range ~x ~z (second '~more))
         (range ~x ~z))))

ou adicione algo semelhante ao Common Lisp (é apenas por exemplo, pois tudo isso já pode ser feito nos idiomas, é claro!):

(defmacro from (x y z &rest r)
  `(when (eql ',y 'to)
     (if (and ',r (eql (first ',r) 'by))
     (loop for i from ,x to ,z by (second ',r) collect i)
     (loop for i from ,x to ,z collect i))))

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9 10)
(from 0 to 5 by 1/2) ;=> (0 1/2 1 3/2 2 5/2 3 7/2 4 9/2 5)

fonte
-2

Não sei exatamente o que você deseja que sua função de caixa de comutação faça. Qual deve ser o valor de:

(def x 5)
(switch-case x {5 :here}
             :not-there)

Pode ser útil se você visualizou a fonte das funções / macros principais relacionadas, usando o comando repl

(clojure.repl/source case)

Mas, em geral, uma razão pela qual você pode querer uma macro em vez de uma função é que o uso da casemacro principal

(case 6
  5 (print "hello")
  "not there")

não imprimirá olá. No entanto, se casedefinido como uma função, ele imprimirá "olá" na tela e toda a expressão será avaliada como "not there". Isso ocorre porque as funções avaliam seus argumentos antes de cada chamada da função.

WuHoUnited
fonte
3
Isso realmente não responde à pergunta principal. É apenas um exemplo no exemplo.
Florian Margaine 28/05
1
A última parte responde à pergunta. Diz algo que uma macro pode fazer e uma função não.
WuHoUnited
Você poderia ter sido um pouco mais claro sobre como a avaliação de argumentos difere das macros.
Robert Harvey
Eu estava aprendendo macros. Eu queria criar uma construção assumindo que não estava lá. Então, pensei em fazer um caso de mudança usando o formulário if. Eu não conseguia pensar em mais nada.
Amogh Talpallikar