LISP di McCarthy


39

LISP del 1959 di McCarthy

All'inizio del 1959, John McCarthy scrisse un innovativo documento che definiva solo nove funzioni primitive che, quando messe insieme, costituiscono ancora oggi la base per tutte le lingue simili a quelle del LISP. Il documento è disponibile digitalizzato qui:

http://www-formal.stanford.edu/jmc/recursive.pdf

Il vostro compito è quello di attuare pienamente un parser e interprete per LISP di McCarthy esattamente come descritto nel documento 1960: Cioè, le funzioni QUOTE, ATOM, EQ, CAR, CDR, CONS, COND, LAMBDA, e LABELdovrebbero essere funzionale. Il documento avrà la precedenza su questo testo di sfida se si considera la correttezza delle risposte, ma ho cercato di riassumere le nove funzioni di seguito. Si noti che la lingua sarà in TUTTI MAIUSCOLI e non è necessario il controllo degli errori, si presume che tutti gli input siano validi.

tipi

  • Esistono solo due tipi nel LISP di McCarthy: un atomo e un elenco collegato, che è definito ricorsivamente come una testa, che può essere un elenco o un atomo, e un elenco a cui è collegata la testa (coda). NILha la proprietà speciale di essere sia un atomo che un elenco.
  • Come per la carta, i nomi degli atomi saranno composti solo da lettere maiuscole, numeri e caratteri spaziali, sebbene le stringhe di spazi consecutivi debbano essere considerate come un solo spazio e tutti i caratteri spaziali iniziali e finali dovrebbero essere rimossi. Esempio nomi atomo equivalente (sostituzione di sottolineatura con carattere di spazio): ___ATOM__1__ = ATOM_1. Esempio di nomi di atomi non equivalenti:A_TOM_1 != ATOM_1
  • Gli elenchi sono indicati da parentesi e un implicito NILè alla fine di ogni elenco. Gli elementi in un elenco sono separati da virgole e non spazi bianchi come nella maggior parte dei moderni Lisps. Quindi l'elenco (ATOM 1, (ATOM 2))sarebbe {[ATOM 1] -> {[ATOM 2] -> NIL} -> NIL}.

QUOTE:

  • Accetta un argomento che può essere un atomo (singolo elemento) o un elenco collegato. Restituisce esattamente l'argomento.
  • Casi test:
  • (QUOTE, ATOM 1) -> ATOM 1
  • (QUOTE, (ATOM 1, ATOM 2)) -> (ATOM 1, ATOM 2)

ATOM:

  • Accetta un argomento che può essere un atomo (singolo elemento) o un elenco collegato. Restituisce T(true) se l'argomento è un atomo o NIL(false) se l'argomento non è un atomo.
  • Casi test:
  • (ATOM, (QUOTE, ATOM 1)) -> T
  • (ATOM, (QUOTE, (ATOM 1, ATOM 2))) -> NIL

EQ:

  • Accetta due argomenti che devono essere atomi (il comportamento è indefinito se uno degli argomenti non è un atomo). Restituisce T(vero) se i due atomi sono equivalenti o NIL(falso) se non lo sono.
  • Casi test:
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 1)) -> T
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 2)) -> NIL

CAR:

  • Accetta un argomento che deve essere un elenco (il comportamento non è definito se non è un elenco). Restituisce il primo atomo (head) di quell'elenco.
  • Casi test:
  • (CAR, (QUOTE, (ATOM 1, ATOM 2))) -> ATOM 1

CDR:

  • Accetta un argomento che deve essere un elenco (il comportamento non è definito se non è un elenco). Restituisce ogni atomo tranne il primo atomo dell'elenco, ovvero la coda. Si noti che ogni elenco termina in modo implicito NIL, quindi CDRverrà restituito l' esecuzione su un elenco che sembra avere solo un elemento NIL.
  • Casi test:
  • (CDR, (QUOTE, (ATOM 1, ATOM 2))) -> (ATOM 2)
  • (CDR, (QUOTE, (ATOM 1))) -> NIL

CONS:

  • Accetta due argomenti. Il primo può essere un atomo o un elenco, ma il secondo deve essere un elenco o NIL. Prepara il primo argomento al secondo argomento e restituisce l'elenco appena creato.
  • Casi test:
  • (CONS, (QUOTE, ATOM 1), (QUOTE, NIL)) -> (ATOM 1)
  • (CONS, (QUOTE, ATOM 1), (CONS, (QUOTE, ATOM 2), (QUOTE, NIL))) -> (ATOM 1, ATOM 2)

COND:

  • Questa è una sorta di dichiarazione "if-else" di LISP. Accetta una quantità di argomenti di lunghezza variabile, ognuno dei quali deve essere esattamente un elenco di lunghezza 2. Per ogni elenco di argomenti in ordine, valuta il primo termine e, se è vero (T), restituisce il secondo termine associato ed esce dalla funzione . Se il primo termine non è vero, passare all'argomento successivo e verificarne la condizione, e così via fino al raggiungimento della prima condizione vera. Almeno una delle condizioni dell'argomento può essere considerata vera - se sono tutte false, si tratta di un comportamento indefinito. Vedi pagina 4 per un buon esempio del comportamento di questa funzione.
  • Casi test:
  • (COND, ((ATOM, (QUOTE, ATOM 1)), (QUOTE, 1)), ((ATOM, (QUOTE, (ATOM 1, ATOM 2))), (QUOTE, 2))) -> 1
  • (COND, ((ATOM, (QUOTE, (ATOM 1, ATOM 2))), (QUOTE, 2)), ((ATOM, (QUOTE, ATOM 1)), (QUOTE, 1))) -> 1

LAMBDA:

  • Definisce una funzione anonima. Accetta due argomenti, il primo è un elenco di atomi che rappresentano gli argomenti della funzione e il secondo è qualsiasi espressione S (il corpo della funzione), che in genere utilizza gli argomenti.
  • Casi test:
  • Definizione e utilizzo di una funzione anonima "isNull":
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> T
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, ATOM 1)) -> NIL

LABEL:

  • Dà un nome a una LAMBDAfunzione anonima , che consente anche a quella funzione di essere chiamata ricorsivamente nel corpo di LAMBDA. Accetta due argomenti, il primo è un'etichetta e il secondo è la LAMBDAfunzione alla quale l'etichetta dovrebbe essere associata. Restituisce il nome fornito. L'ambito di tutti i LABELnomi è globale e ridefinire un LABELcomportamento non definito.
  • Curiosità, in LABELrealtà non è necessario creare funzioni ricorsive, poiché ora sappiamo che LAMBDApuò essere utilizzato con un "Y-Combinator" per svolgere questo compito, ma McCarthy non era a conoscenza di questo metodo durante la stesura del documento originale. Rende comunque i programmi molto più facili da scrivere.
  • Casi test:
  • (LABEL, SUBST, (LAMBDA, (X, Y, Z), (COND, ((ATOM, Z), (COND, ((EQ, Y, Z), X), ((QUOTE, T), Z))), ((QUOTE, T), (CONS, (SUBST, X, Y, (CAR, Z)), (SUBST, X, Y, (CDR, Z))))))) -> SUBST
  • (dopo aver eseguito quanto sopra) (SUBST, (QUOTE, A), (QUOTE, B), (QUOTE, (A, B, C))) -> (A, A, C)

Per aiutare a visualizzare la SUBSTfunzione sopra, potrebbe essere rappresentata come questo pseudocodice simile a Python:

def substitute(x, y, z): # substitute all instances of y (an atom) with x (any sexp) in z
    if isAtom(z):
        if y == z:
            return x
        elif True: 
            return z
    elif True:
        return substitute(x,y,z[0]) + substitute(x,y,z[1:])

CASO DI PROVA FINALE:

Se l'ho trascritto correttamente, il tuo interprete dovrebbe essere in grado di interpretare EVALcon questo codice:

(LABEL, CAAR, (LAMBDA, (X), (CAR, (CAR, X))))
(LABEL, CDDR, (LAMBDA, (X), (CDR, (CDR, X))))
(LABEL, CADR, (LAMBDA, (X), (CAR, (CDR, X))))
(LABEL, CDAR, (LAMBDA, (X), (CDR, (CAR, X))))
(LABEL, CADAR, (LAMBDA, (X), (CAR, (CDR, (CAR, X)))))
(LABEL, CADDR, (LAMBDA, (X), (CAR, (CDR, (CDR, X)))))
(LABEL, CADDAR, (LAMBDA, (X), (CAR, (CDR, (CDR, (CAR, X))))))

(LABEL, ASSOC, (LAMBDA, (X, Y), (COND, ((EQ, (CAAR, Y), X), (CADAR, Y)), ((QUOTE, T), (ASSOC, X, (CDR, Y))))))

(LABEL, AND, (LAMBDA, (X, Y), (COND, (X, (COND, (Y, (QUOTE, T)), ((QUOTE, T), (QUOTE, NIL)))), ((QUOTE, T), (QUOTE, NIL)))))
(LABEL, NOT, (LAMBDA, (X), (COND, (X, (QUOTE, NIL)), ((QUOTE, T), (QUOTE, T)))))

(LABEL, NULL, (LAMBDA, (X), (AND, (ATOM, X), (EQ, X, (QUOTE, NIL)))))

(LABEL, APPEND, (LAMBDA, (X, Y), (COND, ((NULL, X), Y), ((QUOTE, T), (CONS, (CAR, X), (APPEND, (CDR, X), Y))))))

(LABEL, LIST, (LAMBDA, (X, Y), (CONS, X, (CONS, Y, (QUOTE, NIL))))) 

(LABEL, PAIR, (LAMBDA, (X, Y), (COND, ((AND, (NULL, X), (NULL, Y)), (QUOTE, NIL)), ((AND, (NOT, (ATOM, X)), (NOT, (ATOM, Y))), (CONS, (LIST, (CAR, X), (CAR, Y)), (PAIR, (CDR, X), (CDR, Y)))))))

(LABEL, EVAL, (LAMBDA, (E, A), (COND, ((ATOM, E), (ASSOC, E, A)), ((ATOM, (CAR, E)), (COND, ((EQ, (CAR, E), (QUOTE, QUOTE)), (CADR, E)), ((EQ, (CAR, E), (QUOTE, ATOM)), (ATOM, (EVAL, ((CADR, E), A)))), ((EQ, (CAR, E), (QUOTE, EQ)), (EQ, (EVAL, (CADR, E, A)), (EVAL, (CADDR, E, A)))), ((EQ, (CAR, E), (QUOTE, COND)), (EVCON, (CDR, E), A)), ((EQ, (CAR, E), (QUOTE, CAR)), (CAR, (EVAL, (CADR, E), A))), ((EQ, (CAR, E), (QUOTE, CDR)), (CDR, (EVAL, (CADR, E), A))), ((EQ, (CAR, E), (QUOTE, CONS)), (CONS, (EVAL, (CADR, E), A), (EVAL, (CADDR, E), A))), ((QUOTE, T), (EVAL, (CONS, (ASSOC, (CAR, E), A), (EVLIS, (CDR, E), A)), A)))), ((EQ, (CAAR, E), (QUOTE, LABEL)), (EVAL, (CONS, (CADDAR, E), (CDR, E)), (CONS, (CONS, (CADAR, E), (CONS, (CAR, E), (CONS, A, (QUOTE, NIL))))))), ((EQ, (CAAR, E), (QUOTE, LAMBDA)), (EVAL, (CADDAR, E), (APPEND, (PAIR, (CADAR, E), (EVLIS, (CDR, E), A)), A))))))

(LABEL, EVCON, (LAMBDA, (C, A), (COND, ((EVAL, (CAAR, C), A), (EVAL, (CADAR, C), A)), ((QUOTE, T), (EVCON, (CDR, C), A)))))

(LABEL, EVLIS, (LAMBDA, (M, A), (COND, ((NULL, M), (QUOTE, NIL)), ((QUOTE, T), (CONS, (EVAL, (CAR, M), A), (EVLIS, (CDR, M), A))))))

Dopo aver eseguito quel colosso, questa riga dovrebbe restituire (A, B, C):

(EVAL, (QUOTE, (CONS, X, (QUOTE, (B, C)))), (QUOTE, ((X, A), (Y, B))))

Tuttavia, per citare John McCarthy stesso a pagina 16, sembra che stia esaurendo i personaggi sul suo computer:

Se sul computer fossero disponibili più personaggi, potrebbe essere notevolmente migliorato ...

Pertanto, questa sfida è contrassegnata da e la risposta più breve in caratteri sarà il vincitore. Si applicano scappatoie standard. In bocca al lupo!

Nota su String Evals : capisco che alcuni pensano che sia possibile vincere questa sfida usando un Lisp e modificando la sintassi per adattarla alla lingua host e quindi usando una stringa (eval). Non sono particolarmente convinto che questo approccio vincerà necessariamente soprattutto con le regole di denominazione dell'identificatore, e anche se pensasse che vietare le stringhe evalin tutte le lingue sarebbe una pendenza soggettiva e sfuggente. Ma non voglio punire le persone per aver fatto questa sfida nel modo "giusto", quindi potrei consentire a due vincitori di questa sfida, uno in un linguaggio simile a Lisp e uno in un linguaggio non Lispy, se questo diventa un problema .


1
Hai un esempio Lambda che definisce una funzione "IsNull", ma sembra che Nil restituisca Nil, quando mi sembra che dovrebbe restituire T?
nmjcman101,

1
Hai ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NILDove (QUOTE NIL)alla fine è l'input, quindi questo dovrebbe tornare T?
nmjcman101,

1
Bene, ma hai scritto-> NIL
nmjcman101,

1
Nella descrizione per CONSte dire "Aggiunge il primo argomento al secondo argomento e restituisce l'elenco appena creato", ma i casi di test mostrano che il secondo argomento è stato aggiunto al primo. Che è corretto?
Giordania,

1
Sto basando la mia implementazione sul tutorial lisp di kjetilvalle e la sintassi è leggermente diversa. Viene utilizzato il minuscolo e non sono presenti virgole. Potrei semplicemente eseguire una trasformazione minuscola e rimuovere le virgole dalla stringa di input in modo che sia più o meno conforme al design dell'interprete sopra? Sono abbastanza nuovo per Lisp, ma volevo esplorare questa sfida nella mia lingua. Finora ho implementato il parser . (La mia lingua sembra Lisp, ma è implementata in Node.js)
Andrakis,

Risposte:


17

Python 3, 770 byte

Questo è un REPL su stdin / stdout. Si aspetta che ogni riga sia un'istruzione completa o vuota. evalviene utilizzato per abbreviare l'implementazione, ma non è altrimenti necessario per la logica.

import re,sys;S=re.sub
P=lambda l:eval(S("([A-Z0-9][A-Z0-9 ]*)",r"' '.join('\1'.strip().split())",S("NIL","()",S("\)",",)",l))))
d={"QUOTE":'(v,L[1])[1]',"EQ":'[(),"T"][E(L[1],v)==E(L[2],v)]',
"CDR":'E(L[1],v)[1:]',"CONS":'(E(L[1],v),)+E(L[2],v)',"CAR":'E(L[1],v)[0]',
"LAMBDA":'("#",)+L[1:]',"LABEL":'[v.update({L[1]:E(L[2],v)}),L[1]][1]'}
def E(L,v):
 if L*0=="":return v[L]
 elif L[0]in d:return eval(d[L[0]])
 elif L[0]=="COND":return next(E(l[1],v)for l in L[1:]if E(l[0],v)=="T")
 elif L[0]=="ATOM":o=E(L[1],v);return[(),"T"][o*0in["",o]]
 else:l=E(L[0],v);n=v.copy();n.update({a:E(p,v)for a,p in zip(l[1],L[1:])});return E(l[2],n)
R=lambda o:o==()and"NIL"or 0*o==()and"(%s)"%", ".join(R(e)for e in o)or o
g={}
for l in sys.stdin:
 if l.strip():print(R(E(P(l),g)))

1
@Harry I primi due casi di test funzionano dopo aver corretto un piccolo bug che ho introdotto negli ultimi ritocchi. L'Eval funziona perfettamente. Ma l' SUBSTesempio è ancora rotto (per quanto ne sappia) come testcase. Una delle CONDs raggiunge la fine prima di trovare a T.
orlp,

1
Grazie per averlo risolto! Questo è davvero impressionante! Funziona per me su tutti i test ora, incluso EVAL(così piacevolmente sorpreso di averlo trovato subito al primo tentativo!) Ora ti assegnerò la generosità e la risposta accettata!
Harry,

2
Adoro anche l' R(E(P(l)installazione ;-)
Harry,

2
@Harry non ti ho preso in giro che è stato un incidente! R = repr, E = eval, P = parse, l = line.
orlp,

4
Volevo solo farti sapere, ho scritto un articolo che menziona la tua implementazione qui !
Harry il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.