Pergunta

Tenho tentado escrever uma macro Lisp que execute o equivalente a ++ em outras linguagens de programação por razões semânticas.Tentei fazer isso de várias maneiras diferentes, mas nenhuma delas parece funcionar e todas são aceitas pelo intérprete, então não sei se tenho a sintaxe correta ou não.Minha ideia de como isso seria definido seria

(defmacro ++ (variable)
  (incf variable))

mas isso me dá um ERRO DE TIPO SIMPLES ao tentar usá-lo.O que faria isso funcionar?

Foi útil?

Solução

Lembre-se de que uma macro retorna uma expressão a ser avaliada.Para fazer isso, você deve colocar aspas:

(defmacro ++ (variable)
   `(incf ,variable))

Outras dicas

Ambas as respostas anteriores funcionam, mas fornecem uma macro que você chama como

(++ varname)

em vez de varname++ ou ++varname, que suspeito que você queira.Não sei se você consegue o primeiro, mas para o último, você pode fazer uma macro de leitura.Como são dois caracteres, uma macro de despacho provavelmente é melhor.Não testado, já que não tenho um lisp em execução útil, mas algo como:

(defun plusplus-reader (stream subchar arg)
   (declare (ignore subchar arg))
   (list 'incf (read stream t nil t)))
(set-dispatch-macro-character #\+ #\+ #'plusplus-reader)

deveria fazer ++var na verdade ler como (incf var).

Eu desaconselho fortemente a criação de um alias para incf.Isso reduziria a legibilidade para qualquer pessoa que leia seu código e precise se perguntar "o que é isso?como é diferente do incf?"

Se você quiser um pós-incremento simples, tente isto:

(defmacro post-inc (number &optional (delta 1))
  "Returns the current value of number, and afterwards increases it by delta (default 1)."
  (let ((value (gensym)))
    `(let ((,value ,number))
       (incf ,number ,delta)
       ,value)))

A sintaxe (++ a) é um apelido inútil para (incf a).Mas suponha que você queira a semântica do pós-incremento:recuperar o valor antigo.No Common Lisp, isso é feito com prog1, como em: (prog1 i (incf i)).Common Lisp não sofre ordens de avaliação não confiáveis ​​ou ambíguas.A expressão anterior significa que i é avaliado e o valor é armazenado em algum lugar, então (incf i) é avaliado e, em seguida, o valor oculto é retornado.

Fazendo um completamente à prova de balas pincf (publicar-incf) não é totalmente trivial. (incf i) tem a bela propriedade que i é avaliado apenas uma vez.Gostaríamos (pincf i) também ter essa propriedade.E então a macro simples fica aquém:

(defmacro pincf (place &optional (increment 1))
  `(prog1 ,place (incf ,place ,increment))

Para fazer isso direito, temos que recorrer ao "analisador de local de atribuição" do Lisp chamado get-setf-expansion para obter materiais que permitam à nossa macro compilar o acesso corretamente:

(defmacro pincf (place-expression &optional (increment 1) &environment env)
  (multiple-value-bind (temp-syms val-forms
                        store-vars store-form access-form)
                        (get-setf-expansion place-expression env)
    (when (cdr store-vars)
      (error "pincf: sorry, cannot increment multiple-value place. extend me!"))
    `(multiple-value-bind (,@temp-syms) (values ,@val-forms)
       (let ((,(car store-vars) ,access-form))
         (prog1 ,(car store-vars)
                (incf ,(car store-vars) ,increment)
                ,store-form)))))

Alguns testes com CLISP.(Observação:expansões que dependem de materiais de get-setf-expansion pode conter código específico de implementação.Isso não significa que nossa macro não seja portátil!)

8]> (macroexpand `(pincf simple))
(LET* ((#:VALUES-12672 (MULTIPLE-VALUE-LIST (VALUES))))
 (LET ((#:NEW-12671 SIMPLE))
  (PROG1 #:NEW-12671 (INCF #:NEW-12671 1) (SETQ SIMPLE #:NEW-12671)))) ;
T
[9]> (macroexpand `(pincf (fifth list)))
(LET*
 ((#:VALUES-12675 (MULTIPLE-VALUE-LIST (VALUES LIST)))
  (#:G12673 (POP #:VALUES-12675)))
 (LET ((#:G12674 (FIFTH #:G12673)))
  (PROG1 #:G12674 (INCF #:G12674 1)
   (SYSTEM::%RPLACA (CDDDDR #:G12673) #:G12674)))) ;
T
[10]> (macroexpand `(pincf (aref a 42)))
(LET*
 ((#:VALUES-12679 (MULTIPLE-VALUE-LIST (VALUES A 42)))
  (#:G12676 (POP #:VALUES-12679)) (#:G12677 (POP #:VALUES-12679)))
 (LET ((#:G12678 (AREF #:G12676 #:G12677)))
  (PROG1 #:G12678 (INCF #:G12678 1)
   (SYSTEM::STORE #:G12676 #:G12677 #:G12678)))) ;
T

Agora, aqui está um caso de teste importante.Aqui, o local contém um efeito colateral: (aref a (incf i)).Isso deve ser avaliado exatamente uma vez!

[11]> (macroexpand `(pincf (aref a (incf i))))
(LET*
 ((#:VALUES-12683 (MULTIPLE-VALUE-LIST (VALUES A (INCF I))))
  (#:G12680 (POP #:VALUES-12683)) (#:G12681 (POP #:VALUES-12683)))
 (LET ((#:G12682 (AREF #:G12680 #:G12681)))
  (PROG1 #:G12682 (INCF #:G12682 1)
   (SYSTEM::STORE #:G12680 #:G12681 #:G12682)))) ;
T

Então o que acontece primeiro é que A e (INCF I) são avaliadas e se tornam as variáveis ​​temporárias #:G12680 e #:G12681.O array é acessado e o valor é capturado em #:G12682.Então temos o nosso PROG1 que retém esse valor para retorno.O valor é incrementado e armazenado de volta no local do array por meio do CLISP system::store função.Observe que esta chamada de armazenamento usa variáveis ​​temporárias, não as expressões originais A e I. (INCF I) aparece apenas uma vez.

Semanticamente, os operadores de prefixo ++ e -- em uma linguagem como c++ ou qualquer outra são equivalentes incf/decf em lisp comum.Se você perceber isso e, como sua macro (incorreta), estiver realmente procurando por uma mudança sintática, então já foi mostrado como fazer isso com crases como `(incf, x).Até foi mostrado a você como fazer o leitor contornar isso para obter algo mais próximo da sintaxe não-Lisp.Esse é o problema, já que nenhuma dessas coisas é uma boa ideia.Em geral, a codificação não idiomática para fazer com que uma linguagem se pareça mais com outra simplesmente não é uma boa ideia.

No entanto, se você estiver realmente procurando pela semântica, você já tem as versões do prefixo conforme observado, mas as versões do postfix não serão fáceis de combinar sintaticamente.Você poderia fazer isso com bastante hackeamento de leitores, mas não seria bonito.

Se é isso que você está procurando, sugiro a) usar nomes incf/decf, pois eles são idiomáticos e funcionam bem eb) escrever versões pós-incf, pós-decf, por exemplo (defmacro post-incf (x) `(prog1 ,x (incf ,x)) tipos de coisas.

Pessoalmente, não vejo como isso seria particularmente útil, mas sim.

Para pré-incremento, já existe incf, mas você pode definir o seu próprio com

(define-modify-macro my-incf () 1+)

Para pós-incremento, você poderia usar isto (de fare-utils):

(defmacro define-values-post-modify-macro (name val-vars lambda-list function)
 "Multiple-values variant on define-modify macro, to yield pre-modification values"
 (let ((env (gensym "ENV")))
   `(defmacro ,name (,@val-vars ,@lambda-list &environment ,env)
      (multiple-value-bind (vars vals store-vars writer-form reader-form)
          (get-setf-expansion `(values ,,@val-vars) ,env)
       (let ((val-temps (mapcar #'(lambda (temp) (gensym (symbol-name temp)))
                                 ',val-vars)))
          `(let* (,@(mapcar #'list vars vals)
                  ,@store-vars)
             (multiple-value-bind ,val-temps ,reader-form
               (multiple-value-setq ,store-vars
                 (,',function ,@val-temps ,,@lambda-list))
               ,writer-form
               (values ,@val-temps))))))))

(defmacro define-post-modify-macro (name lambda-list function)
 "Variant on define-modify-macro, to yield pre-modification values"
 `(define-values-post-modify-macro ,name (,(gensym)) ,lambda-list ,function))

(define-post-modify-macro post-incf () 1+)

Embora eu definitivamente tenha em mente os comentários e avisos que Simão comentários em seu post, eu realmente acho que usuário10029ainda vale a pena tentar a abordagem, então, apenas por diversão, tentei combiná-la com a resposta aceita para fazer o ++x trabalho do operador (ou seja, incrementar o valor de x em 1).De uma chance!

Explicação:O bom e velho SBCL não compilaria sua versão porque o símbolo '+' deve ser explicitamente definido na tabela de pesquisa dispatch-char com make-dispatch-macro-character, e a macro ainda é necessária para passar o nome da variável antes de avaliá-la.Então isso deve funcionar:

(defmacro increment (variable)
  "The accepted answer"
  `(incf ,variable))

(make-dispatch-macro-character #\+) ; make the dispatcher grab '+'

(defun |inc-reader| (stream subchar arg)
  "sets ++<NUM> as an alias for (incf <NUM>).
   Example: (setf x 1233.56) =>1233.56
            ++x => 1234.56
            x => 1234.56"
   (declare (ignore subchar arg))
   (list 'increment (read stream t nil t)))

(set-dispatch-macro-character #\+ #\+ #'|inc-reader|)

Ver |inc-reader|de doutrina para um exemplo de uso.A documentação (intimamente) relacionada pode ser encontrada aqui:

Esta implementação tem como consequência que entradas numéricas como +123 não são mais compreendidas (o depurador entra em ação com no dispatch function defined for #\Newline), mas outras soluções alternativas (ou até mesmo evitadas) parecem razoáveis:se você ainda quiser continuar com isso, talvez a melhor escolha não seja usar ++ como prefixo, mas ## ou qualquer outra solução mais DSL

saúde!

André

Isso deve funcionar, mas não sou um guru do ceceio.

(defmacro ++ (variable)
  `(setq ,variable (+ ,variable 1)))
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top